dsh-agentone 0.5.11 → 0.5.13

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/lib/index.js CHANGED
@@ -32,7 +32,7 @@ import { applyPlatformModel, removePlatformModel } from './model.js';
32
32
  import { installPlatformSkill, listPlatformSkills, removePlatformSkill } from './skill.js';
33
33
  import { platformFetch } from './http.js';
34
34
  import { donePageHtml, statusPageHtml } from './page.js';
35
- import { compareSemver, dshPluginCli, probeCliDetail, pnpmViewLatest, upgradeCliTool } from './proc.js';
35
+ import { compareSemver, dshPluginCli, firstErrorLines, probeCliDetail, pnpmViewLatest, upgradeCliTool } from './proc.js';
36
36
 
37
37
  import { clearCredentials, loadCredentials, resolveDshHome, saveCredentials, updateCredentials } from './store.js';
38
38
 
@@ -158,7 +158,7 @@ async function ensureFreshToken(config, log) {
158
158
  * DSH Desktop 用 desktopProfiles.current(apply 时探测填充)。
159
159
  */
160
160
  let PLUGIN_PROFILE = 'web';
161
- /** desktop 受管插件操作通道(desktopPnpm.runPlugin),非 desktop 为 null。 */
161
+ /** desktop 受管插件操作通道(desktopPnpm.runPlugin 包装),非 desktop 为 null。 */
162
162
  let desktopPluginCli = null;
163
163
 
164
164
  function pluginProfileDir(config) {
@@ -173,6 +173,48 @@ async function runPluginCli(args) {
173
173
  return dshPluginCli(args, PLUGIN_PROFILE);
174
174
  }
175
175
 
176
+ /**
177
+ * 包装宿主 desktopPnpm.runPlugin,对齐 dshPluginCli 的返回契约(成功
178
+ * {stdout, stderr},失败 throw Error)。宿主契约(dsh-plugin-desktop 的
179
+ * DesktopPnpmService)有两点与直觉不同,历史 bug 均源于此:
180
+ * 1. runPlugin(argv, invokingDir, signal) 的 invokingDir 是必填绝对路径
181
+ * (作为子进程 cwd)——只传 argv 会在宿主内抛
182
+ * "Cannot read properties of undefined (reading 'length')";
183
+ * 2. 返回 {stdout, stderr, done, cancel} 句柄而非 Promise,done 结算
184
+ * {exitCode, signal} 且不因非零退出 reject——直接 await 句柄会立即
185
+ * 返回,装完前的落盘校验必然读到旧版本,误报「升级未生效」。
186
+ */
187
+ export function desktopRunPlugin(service, invokingDir) {
188
+ return async (args) => {
189
+ const handle = service.runPlugin([...args], invokingDir);
190
+ let stdout = '';
191
+ let stderr = '';
192
+ const append = (acc, chunk) => (acc + String(chunk)).slice(-64 * 1024);
193
+ handle.stdout?.on?.('data', (chunk) => { stdout = append(stdout, chunk); });
194
+ handle.stderr?.on?.('data', (chunk) => { stderr = append(stderr, chunk); });
195
+ // 与 web 通道 runCliTool 同宽的超时(300s):宿主自身不设整体超时
196
+ let timedOut = false;
197
+ const timer = setTimeout(() => {
198
+ timedOut = true;
199
+ handle.cancel();
200
+ }, 300_000);
201
+ try {
202
+ const outcome = await handle.done;
203
+ if (timedOut) throw new Error('命令执行超时,请稍后重试');
204
+ if (outcome.exitCode !== 0 || outcome.signal != null) {
205
+ // ERR_PNPM_IGNORED_BUILDS 等非零退出在这里抛出,由调用侧
206
+ // install/upgrade 的「以落盘版本为准」兜底接管
207
+ const message = firstErrorLines(`${stderr}\n${stdout}`)
208
+ || `命令执行失败(退出码 ${outcome.exitCode ?? '未知'})`;
209
+ throw new Error(message);
210
+ }
211
+ return { stdout, stderr };
212
+ } finally {
213
+ clearTimeout(timer);
214
+ }
215
+ };
216
+ }
217
+
176
218
  /**
177
219
  * npm 包名白名单(scoped 与非 scoped)。用户输入会进入 CLI 参数,必须先
178
220
  * 收敛到 npm 官方命名规则,杜绝以 `-` 开头的值被 dsh/pnpm 当成 flag。
@@ -586,7 +628,7 @@ export function apply(ctx, config) {
586
628
  if (desktopProfiles?.current?.name) {
587
629
  PLUGIN_PROFILE = desktopProfiles.current.name;
588
630
  ctx.inject(['desktopPnpm'], (desktopCtx) => {
589
- desktopPluginCli = (args) => desktopCtx.desktopPnpm.runPlugin(args);
631
+ desktopPluginCli = desktopRunPlugin(desktopCtx.desktopPnpm, pluginProfileDir(config));
590
632
  log.info(`[dsh-agentone] desktop profile: ${PLUGIN_PROFILE}`);
591
633
  });
592
634
  }
@@ -923,6 +965,33 @@ export function apply(ctx, config) {
923
965
  }
924
966
  });
925
967
 
968
+ register('/agentone/api/plugin/self', async (request, response) => {
969
+ if (request.method !== 'GET') {
970
+ response.writeHead(405, { allow: 'GET' });
971
+ response.end();
972
+ return;
973
+ }
974
+ // 「关于」页的轻量版本接口:落盘已装版本 + registry 实时 latest + 运行
975
+ // 中版本。升级成功后落盘先变、运行代码要重启宿主才换——两者差值即
976
+ // 前端「已安装待重启」提示的依据。
977
+ try {
978
+ const dir = pluginProfileDir(config);
979
+ const [version, latest] = await Promise.all([
980
+ installedVersion(dir, PLUGIN_PACKAGE_NAME),
981
+ pnpmViewLatest(PLUGIN_PACKAGE_NAME, dir).catch(() => null),
982
+ ]);
983
+ sendJson(response, 200, {
984
+ name: PLUGIN_PACKAGE_NAME,
985
+ version,
986
+ running: PLUGIN_VERSION,
987
+ latest: latest && version && compareSemver(latest, version) > 0 ? latest : null,
988
+ self_managed: isSelfManaged(),
989
+ });
990
+ } catch (error) {
991
+ sendJson(response, 500, { error: error?.message || '读取插件版本失败' });
992
+ }
993
+ });
994
+
926
995
  register('/agentone/api/cli/status', async (request, response) => {
927
996
  if (request.method !== 'GET') {
928
997
  response.writeHead(405, { allow: 'GET' });
package/lib/page.js CHANGED
@@ -204,6 +204,10 @@ const activateTab = (name) => {
204
204
  renderPlugins._loaded = true;
205
205
  renderPlugins();
206
206
  }
207
+ // 关于页版本/升级信息按需拉取(pnpm view 有网络开销,1 分钟内不重复)
208
+ if (name === 'about') {
209
+ refreshAboutSelf();
210
+ }
207
211
  // CLI 状态按需探测(auth status 有进程开销,不进 5s 轮询;1 分钟内不重复)
208
212
  if (name === 'cli') {
209
213
  const now = Date.now();
@@ -481,7 +485,10 @@ const pluginAction = (name, action, btn) => withLoading(
481
485
  headers: { 'Content-Type': 'application/json' },
482
486
  body: JSON.stringify({ name }),
483
487
  }),
484
- action === 'install' ? '插件已安装' : action === 'remove' ? '插件已卸载' : '插件已升级',
488
+ // 装/升级只落盘,运行中的插件代码要重启 dsh 才换新——成功提示必须带重启说明
489
+ action === 'install' ? '插件已安装,重启 DSH 后生效'
490
+ : action === 'remove' ? '插件已卸载'
491
+ : '插件已升级,重启 DSH 后生效',
485
492
  ).then(() => renderPlugins());
486
493
  const renderPlugins = async () => {
487
494
  const list = $('plugins-list');
@@ -558,14 +565,74 @@ const renderPlugins = async () => {
558
565
  }).then(() => renderPlugins()), '插件已卸载');
559
566
  });
560
567
  };
568
+ // 关于页版本行:aboutSelf(GET api/plugin/self)有值时展示升级入口或
569
+ // 「已安装待重启」提示;尚未取到时退化为运行中版本(状态轮询自带字段)。
570
+ let aboutSelf = null;
571
+ const aboutVersionRow = () => {
572
+ const self = aboutSelf;
573
+ if (!self) {
574
+ return '<div class="kv"><span class="k">插件版本</span><span class="v">dsh-agentone v' + esc(lastStatus?.plugin_version || '?') + '</span></div>';
575
+ }
576
+ let value;
577
+ if (self.latest && !self.self_managed) {
578
+ value = 'v' + esc(self.version || '?') + ' → <b>v' + esc(self.latest) + '</b>'
579
+ + ' <button id="about-upgrade-btn" class="btn small primary" data-loading="升级中…">升级</button>';
580
+ } else if (self.version && self.running && self.version !== self.running) {
581
+ value = 'v' + esc(self.running) + '(运行中)· v' + esc(self.version) + ' 已安装,重启 DSH 后生效';
582
+ } else {
583
+ value = 'v' + esc(self.version || self.running || '?');
584
+ }
585
+ return '<div class="kv"><span class="k">插件版本</span><span class="v">' + value + '</span></div>';
586
+ };
587
+ const wireAboutUpgrade = () => {
588
+ const btn = $('about-upgrade-btn');
589
+ if (!btn || btn.onclick) return;
590
+ btn.onclick = async () => {
591
+ btn.disabled = true;
592
+ btn.innerHTML = '<span class="spin"></span>升级中…';
593
+ try {
594
+ const resp = await fetch('api/plugin/upgrade', {
595
+ method: 'POST',
596
+ headers: { 'Content-Type': 'application/json' },
597
+ body: JSON.stringify({ name: 'dsh-agentone' }),
598
+ });
599
+ const data = await resp.json().catch(() => ({}));
600
+ if (!resp.ok) throw new Error(data.error || '插件升级失败');
601
+ notify('插件已升级,重启 DSH 后生效');
602
+ await refreshAboutSelf(true);
603
+ } catch (error) {
604
+ notify(error.message || '插件升级失败', 'err');
605
+ btn.disabled = false;
606
+ btn.innerHTML = '升级';
607
+ }
608
+ };
609
+ };
610
+ // 版本 + latest 查询有网络开销(pnpm view 打 registry):1 分钟节流,
611
+ // 切到「关于」tab 或升级完成后刷新
612
+ const refreshAboutSelf = async (force = false) => {
613
+ const now = Date.now();
614
+ if (!force && refreshAboutSelf._at && now - refreshAboutSelf._at < 60000) return;
615
+ refreshAboutSelf._at = now;
616
+ try {
617
+ const resp = await fetch('api/plugin/self');
618
+ const data = await resp.json().catch(() => ({}));
619
+ if (!resp.ok) throw new Error(data.error || '版本查询失败');
620
+ aboutSelf = data;
621
+ } catch {
622
+ return; // 查询失败保持现状,版本行退化为运行中版本
623
+ }
624
+ if (!$('about-body') || document.getElementById('pane-about').hidden) return;
625
+ renderAbout(lastStatus);
626
+ };
561
627
  const renderAbout = (s) => {
562
628
  const lines = [
563
- '<div class="kv"><span class="k">插件版本</span><span class="v">dsh-agentone v' + esc(s.plugin_version || '?') + '</span></div>',
629
+ aboutVersionRow(),
564
630
  '<div class="kv"><span class="k">登录账号</span><span class="v">' + esc(s.user_email || '未登录') + '</span></div>',
565
631
  '<div class="kv"><span class="k">所属套餐</span><span class="v">' + esc(s.capabilities?.plan_name || '—') + '</span></div>',
566
632
  '<div class="kv"><span class="k">平台地址</span><span class="v muted">' + esc(s.platform_url || '—') + '</span></div>',
567
633
  ];
568
634
  $('about-body').innerHTML = lines.join('');
635
+ wireAboutUpgrade();
569
636
  };
570
637
  const searchCatalog = async () => {
571
638
  const btn = $('search-btn');
package/lib/proc.js CHANGED
@@ -66,7 +66,7 @@ function execResolved(tool, args, { timeoutMs, cwd, env } = {}) {
66
66
  }
67
67
 
68
68
  /** 从 pnpm 冗长输出里提取对用户有意义的错误行。 */
69
- function firstErrorLines(raw) {
69
+ export function firstErrorLines(raw) {
70
70
  const lines = String(raw).split('\n').map((line) => line.trim()).filter(Boolean);
71
71
  const meaningful = lines.filter(
72
72
  (line) => /ERR_|error|ERROR|404|ENOTFOUND|ETIMEDOUT|ECONNREFUSED/.test(line)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-agentone",
3
- "version": "0.5.11",
4
- "description": "AgentOne 平台集成插件:飞书登录、平台模型、SkillHub 技能、套餐申请、插件管理、lark-cli / ccpg-cli 探测(请求超时与错误码透传、令牌轮换 single-flight、凭据损坏容错、状态页 UX 增强)",
3
+ "version": "0.5.13",
4
+ "description": "AgentOne 平台集成插件:飞书登录、平台模型、SkillHub 技能、套餐申请、插件管理与自助升级、lark-cli / ccpg-cli 探测(请求超时与错误码透传、令牌轮换 single-flight、凭据损坏容错、状态页 UX 增强)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {