dsh-agentone 0.5.41 → 0.5.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -131,17 +131,52 @@ dsh plugin --profile web add dsh-agentone
131
131
  lib/
132
132
  index.js 路由与业务编排(登录/状态/技能/套餐/插件/CLI)
133
133
  page.js 状态页与授权完成页的 HTML/CSS/JS 模板(纯静态)
134
+ desktop.js 桌面壳探测与 profile 解析(全仓唯一一份,见下节)
134
135
  http.js 平台请求统一通道(超时、错误码透传)
135
136
  auth.js 登录流、令牌轮换/吊销、能力面/套餐 API
136
137
  language.js 全局中文回复默认($DSH_HOME/AGENTS.md 受标记管理注入)
137
138
  model.js 平台模型 → settings.yaml / .credentials.yaml
138
139
  skill.js 技能下载/解包/卸载(zip 穿越清洗、原子落盘)
140
+ skill-state.js 技能本机记录($DSH_HOME/agentone/skill-state/<slug>.json)与状态判定
141
+ defaults-sync.js 平台默认能力协调器(清单拉取/对齐/缓存)
142
+ profile-plugins.js 当前 profile 的插件操作(通道选择见下节)
139
143
  store.js 凭据持久化(0600;损坏自动移开)
140
144
  proc.js dsh/pnpm CLI 转发与 lark-cli/ccpg-cli 探测
141
145
  qrcode.mjs 授权 URL 的二维码 SVG(vendor qrcode-generator,MIT;lark/ccpg 通用)
142
146
  client.js dsh 浏览器端 bundle(设置菜单 iframe + 首启引导 + 设置导航 workbench 图标换肤)
143
147
  ```
144
148
 
149
+ ## 桌面宿主契约(两类壳,判定只有一份)
150
+
151
+ 宿主服务面差异是 0.5.39/0.5.40 两次事故的根因类别(品牌正则抄了两份漏改一份;
152
+ 只接了一类壳的契约)。**品牌判定与 profile 解析只允许 `lib/desktop.js` 一份实现**,
153
+ `test/desktop-srcdoc.test.mjs` 用源码扫描守卫这一点。
154
+
155
+ | | 第三方壳(dsh-plugin-desktop) | 官方 Electron 应用(DeepSeek Harness / DSH NEXT) |
156
+ |---|---|---|
157
+ | profile 来源 | `ctx.get('desktopProfiles').current`(name + dir) | `ctx.get('profileContext')`(name/dir/packageManager,launcher 发布) |
158
+ | 安装通道 | `ctx.get('desktopPnpm').runPlugin(argv, dir, signal)` → `{stdout, stderr, done, cancel}` | `ctx.get('pluginManager').installBundle(spec, {requestId})` / `removeBundle(name)` / `cancelInstall(requestId)` → `{application: applied\|restart-required\|overridden\|cancelled\|failed, packageResult, error}` |
159
+ | 不发布 | `pluginManager` | `desktopProfiles`、`desktopPnpm`(app.asar 均 0 命中,实测 2026-10) |
160
+ | CLI 兜底 | dsh CLI 拒绝 `--profile desktop` | 同左;profileContext 是唯一答案 |
161
+
162
+ 要点:
163
+
164
+ - 官方 `pluginManager` 与应用内「设置 → 插件」同一管线,**不接受 pnpm 选项**;
165
+ `--allow-build` 不转发(构建审批由官方管线自理),其余附加参数必须拒绝而不是
166
+ 静默丢弃(`profile-plugins.js` 的 `desktopManagerRun`)。
167
+ - `application: 'overridden'` 是「改动保留、被别的层覆盖显示」,不是失败。
168
+ - 非桌面宿主(`dsh web`/CLI):插件配置 `profileName` 优先,launcher 的
169
+ `profileContext` 次之(`desktop.js` 的 `resolveProfileTarget`)。
170
+ - **installed ≠ enabled**(读官方 `dsh-plugin-manager` 源码确认):
171
+ `dsh.profile.bundles` 是**启用集**,`node_modules` 里的依赖只是「已安装」。
172
+ 官方「已安装列表」= 启用集 ∪ profile 依赖 ∪ 应用自带依赖
173
+ (`installation.dependencies`——rc.2 那批 browser-use/computer-use 由应用
174
+ 供给、不可卸载),因此会出现「已安装但未启用」条目;`installBundle` 装新包时
175
+ reconcile 会**自动启用**带 `dsh.bundle` 声明的新依赖,但不会悄悄启用存量
176
+ 未启用包。前端术语用「已启用/未启用」,别叫「已注册」——和官方 UI 对不上。
177
+ peer 不兼容的启用会被宿主拒绝(结构化错误 `incompatible-version`),需在
178
+ 应用内授权该精确版本(version exemptions)或等插件发兼容版本。
179
+
145
180
  ## 开发
146
181
 
147
182
  ```bash
@@ -152,3 +187,16 @@ dsh plugin --profile web add /path/to/plugins/dsh/dsh-agentone
152
187
  无构建步骤(纯 ESM JS);运行时 peer 依赖 `@deepseek-ai/cordis` /
153
188
  `@deepseek-ai/schemastery` 由 dsh host 提供。测试:`npm install && node --test`
154
189
  (YAML 合并/登出清理语义、路由冒烟、凭据容错、令牌轮换等)。
190
+
191
+ 发版前真实宿主门禁(单测全绿但宿主内翻车的两类事故都由它拦):
192
+
193
+ ```bash
194
+ # 1) 把候选版本装进 desktop profile(用应用自带 pnpm,与应用内安装同源)
195
+ # RT="/Applications/DeepSeek Harness.app/Contents/Resources/runtime"
196
+ # cd ~/.dsh/profiles/desktop && \
197
+ # DSH_DESKTOP_NODE_EXECUTABLE="/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness" \
198
+ # "$RT/bin/node" "$RT/pnpm/bin/pnpm.mjs" add 'dsh-agentone@<版本>'
199
+ # 2) 完全退出并重开 DeepSeek Harness(插件代码 restart-required)
200
+ # 3) 跑门禁:逐条断言本次事故签名(旧代码未重启/profile 解析/插件清单/技能搁浅)
201
+ node scripts/desktop-host-smoke.mjs
202
+ ```
package/lib/index.js CHANGED
@@ -679,7 +679,17 @@ export function apply(ctx, config) {
679
679
  // 模块级重试定时器跨 apply 存活:重入先清,避免旧定时器引用旧 config
680
680
  clearModelRetry();
681
681
  modelRetryAttempts = 0;
682
- const register = (path, handler) => ctx.webServer.register({ kind: 'exact', path, handler });
682
+ // 路由注册必须接住宿主返回的 disposer:cordis 的服务代理不自动收集
683
+ // 服务调用的返回值(getTraceable 只做包装),直接丢弃意味着 fiber 销毁
684
+ // 后路由仍留在 webserver 表里——live patch reload 重挂插件时,新激活的
685
+ // register('/agentone/') 撞 duplicate exact route,整条 entry 报
686
+ // 「启用失败」(0.5.42 桌面端实测)。每条路由单独包一层 ctx.effect:
687
+ // 正常销毁时随 fiber 倒序释放;装配中途抛错时已挂路由也已登记在
688
+ // fiber 上,随失败 fiber 的销毁一并回收。
689
+ const register = (path, handler) => ctx.effect(
690
+ () => ctx.webServer.register({ kind: 'exact', path, handler }),
691
+ `dsh-agentone: route ${path}`,
692
+ );
683
693
 
684
694
  register('/agentone/', (request, response) => {
685
695
  if (request.method !== 'GET') {
@@ -1649,14 +1659,20 @@ export function apply(ctx, config) {
1649
1659
  }
1650
1660
  })();
1651
1661
 
1652
- return async () => {
1662
+ // 销毁回调必须同步返回:cordis 对 disposables 倒序逐项 await,任何一
1663
+ // 个返回 Promise 都会拖住其后的销毁步骤。旧实现在这里 await 排空
1664
+ // defaults/profiles 队列(升级场景队列里正是 pnpm 操作),一旦宿主把
1665
+ // 路由释放链挂在它后面,热重载的新激活就会在等待窗口里撞 duplicate
1666
+ // route。队列排空是纯后台清理(中止在途同步、等待安装队列),不产出
1667
+ // 任何销毁链需要的结果,改为 fire-and-forget,不再阻塞销毁链。
1668
+ return () => {
1653
1669
  disposed = true;
1654
- await defaults.dispose();
1655
- await profiles.dispose();
1656
1670
  pendingStates.clear();
1657
1671
  pendingCliAuth.clear();
1658
1672
  clearModelRetry();
1659
1673
  modelRetryAttempts = 0;
1674
+ void defaults.dispose().catch(() => {});
1675
+ void profiles.dispose().catch(() => {});
1660
1676
  log.info('[dsh-agentone] routes unmounted');
1661
1677
  };
1662
1678
  }, 'dsh-agentone: http routes');
package/lib/page.js CHANGED
@@ -1049,15 +1049,18 @@ const renderPlugins = async () => {
1049
1049
  return;
1050
1050
  }
1051
1051
  const rows = data.plugins.map((plugin) => {
1052
- // 状态区分三种:未安装 → 安装按钮;插件(带 bundle 声明)→ 激活态;
1052
+ // 状态区分三种:未安装 → 安装按钮;插件(带 bundle 声明)→ 启用态;
1053
1053
  // 纯依赖(无 bundle 声明,如 @larksuite/cli 这类 CLI 工具包)→ 「依赖」
1054
- // 标签——它永远不进 bundle 层,不能显示「未激活/重启生效」误导用户
1054
+ // 标签——它永远不进 bundle 层,不能显示「未启用/重启生效」误导用户。
1055
+ // 术语与官方「设置 → 插件」对齐:installed(在 node_modules)和
1056
+ // enabled(在 dsh.profile.bundles)是两个维度,官方列表把两类都显示为
1057
+ // 「已安装」,这里的「未启用」= 已安装但没挂进 profile。
1055
1058
  const statePill = !plugin.version
1056
1059
  ? '<span class="pill warn" style="font-size:11px">未安装</span>'
1057
1060
  : plugin.registered
1058
- ? '<span class="pill ok" style="font-size:11px">已注册</span>'
1061
+ ? '<span class="pill ok" style="font-size:11px">已启用</span>'
1059
1062
  : plugin.bundled
1060
- ? '<span class="pill warn" style="font-size:11px">未注册</span>'
1063
+ ? '<span class="pill warn" style="font-size:11px">未启用</span>'
1061
1064
  : '<span class="tag">依赖</span>';
1062
1065
  const builtinTag = plugin.builtin
1063
1066
  ? '<span class="tag">受管</span>'
@@ -1087,7 +1090,7 @@ const renderPlugins = async () => {
1087
1090
  return '<div class="skill-item"><div class="skill-info"><div class="skill-name">'
1088
1091
  + esc(plugin.name) + builtinTag + statePill
1089
1092
  + '</div><div class="skill-sub">' + desc + esc(plugin.source) + ' · ' + version
1090
- + (plugin.registered ? ' · 已安装并注册,重启后生效' : plugin.bundled ? ' · 未注册到当前 profile' : '')
1093
+ + (plugin.registered ? ' · 已启用,重启后生效' : plugin.bundled ? ' · 已安装,未启用当前 profile(「升级」或重装可启用)' : '')
1091
1094
  + '</div></div>' + actionHtml + '</div>';
1092
1095
  });
1093
1096
  // 指纹保护(与 renderSkills 同思路):内容没变不碰 innerHTML——升级后
@@ -32,9 +32,25 @@ export const TARGET_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[
32
32
  * 不是失败(市场侧踩过这个坑)。 */
33
33
  const MANAGER_APPLIED = new Set(['applied', 'restart-required', 'overridden']);
34
34
 
35
+ /** 官方宿主的结构化错误 → 中文可操作文案。宿主的 error 是 JSON 对象
36
+ * (实测 dsh-im@1.0.5 在 dsh 0.2.0-rc.2 上返回 incompatible-version),
37
+ * 直接 stringify 会把英文原文甩给用户(AGENTS.md 报错规范)。 */
35
38
  function failureDetail(error) {
36
39
  if (typeof error === 'string') return error;
37
40
  if (error instanceof Error) return error.message;
41
+ if (error && typeof error === 'object' && !Array.isArray(error)) {
42
+ if (error.code === 'incompatible-version' && Array.isArray(error.incompatible) && error.incompatible.length) {
43
+ return error.incompatible.map((item) => {
44
+ const peers = Object.entries(item.peers || {})
45
+ .map(([name, range]) => `${name}@${range}`).join('、');
46
+ return `${item.name || '插件'}@${item.version || '?'} 与当前宿主 dsh ${item.runtimeVersion || '?'} 不兼容`
47
+ + (peers ? `(peer 依赖要求 ${peers})` : '')
48
+ + ':请在「设置 → 插件」为该版本授权,或等插件发布兼容新版本';
49
+ }).join(';');
50
+ }
51
+ if (typeof error.message === 'string' && error.message) return error.message;
52
+ if (typeof error.code === 'string' && error.code) return `插件操作未完成(${error.code}),请在「设置 → 插件」里查看详情`;
53
+ }
38
54
  if (error === undefined) return '';
39
55
  try { return JSON.stringify(error); } catch { return String(error); }
40
56
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-agentone",
3
- "version": "0.5.41",
4
- "description": "AgentOne 平台集成:平台模型、SkillHub 技能、套餐与 CLI;lark-cli 首装一键初始化飞书应用;修复官方桌面壳 profile 解析失效并支持旧版技能记录一键重建",
3
+ "version": "0.5.43",
4
+ "description": "AgentOne 平台集成:平台模型、SkillHub 技能、套餐与 CLI;修复热重载后路由未释放导致的启用失败",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "smoke": "node scripts/env-matrix-smoke.mjs"