dsh-agentone 0.5.6 → 0.5.7

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 { dshPluginCli, pnpmViewLatest, probeCliTool } from './proc.js';
35
+ import { dshPluginCli, probeCliDetail, pnpmViewLatest, upgradeCliTool } from './proc.js';
36
36
 
37
37
  import { clearCredentials, loadCredentials, resolveDshHome, saveCredentials, updateCredentials } from './store.js';
38
38
 
@@ -218,7 +218,22 @@ function specLabel(spec) {
218
218
  return '';
219
219
  }
220
220
 
221
- /** dsh 官方 in-box bundle:profile 模板自带,不是用户可管理的插件。 */
221
+ /** dsh 插件与 CLI 操作白名单:用户输入不得进入 execFile 的 tool 位。 */
222
+ const CLI_TOOLS = new Set(['lark-cli', 'ccpg-cli']);
223
+
224
+ /**
225
+ * 插件自身是否由平台托管镜像管理:镜像 Dockerfile 固有 ENV
226
+ * `DSH_FORWARDER_LISTEN_PORT`(forwarder 是镜像特有组件,独立桌面版不存在)。
227
+ * 托管镜像内插件版本随镜像走,禁自助升级;独立 dsh(用户自装)没有镜像
228
+ * 通道,允许在插件页自助升级 —— 否则用户会被卡在旧版只能命令行破局。
229
+ */
230
+ function isSelfManaged() {
231
+ return Boolean(process.env.DSH_FORWARDER_LISTEN_PORT);
232
+ }
233
+
234
+ /**
235
+ * dsh 官方 in-box bundle:profile 模板自带,不是用户可管理的插件。
236
+ */
222
237
  const INBOX_BUNDLES = new Set([
223
238
  '@deepseek-ai/dsh-base',
224
239
  '@deepseek-ai/dsh-web-app',
@@ -274,7 +289,7 @@ async function listProfilePlugins(config) {
274
289
  active: bundle && bundles.includes(name),
275
290
  builtin: BUILTIN_PLUGINS.includes(name),
276
291
  // 自身随平台镜像升级:前端据此隐藏卸载/升级按钮(与后端拒绝逻辑同源)
277
- self_managed: name === PLUGIN_PACKAGE_NAME,
292
+ self_managed: name === PLUGIN_PACKAGE_NAME && isSelfManaged(),
278
293
  description: BUILTIN_PLUGIN_DESCRIPTIONS[name] || null,
279
294
  });
280
295
  }
@@ -347,7 +362,7 @@ async function upgradeProfilePlugin(config, name) {
347
362
  if (INBOX_BUNDLES.has(name)) {
348
363
  throw new Error('dsh 内置组件随 dsh 一同升级,请等待平台更新 dsh');
349
364
  }
350
- if (name === PLUGIN_PACKAGE_NAME) {
365
+ if (name === PLUGIN_PACKAGE_NAME && isSelfManaged()) {
351
366
  throw new Error('AgentOne 插件随平台镜像一同升级,无需手动操作');
352
367
  }
353
368
  const dir = pluginProfileDir(config);
@@ -900,15 +915,39 @@ export function apply(ctx, config) {
900
915
  response.end();
901
916
  return;
902
917
  }
903
- // 真实探测本机 CLI:与平台 cli-deployd 的判定口径一致(tokenStatus
904
- // valid/needs_refresh = 已授权)。并发探测,单个失败不影响另一个。
918
+ // 真实探测本机 CLI:三态与平台 cli-deployd 判定口径一致,附带版本
919
+ // 与最新版(查询失败降级 null,不阻塞三态)。并发探测,单个失败不影
920
+ // 响另一个。
905
921
  const [lark, ccpg] = await Promise.all([
906
- probeCliTool('lark-cli').catch(() => 'missing'),
907
- probeCliTool('ccpg-cli').catch(() => 'missing'),
922
+ probeCliDetail('lark-cli').catch(() => ({ state: 'missing', version: null, latest: null })),
923
+ probeCliDetail('ccpg-cli').catch(() => ({ state: 'missing', version: null, latest: null })),
908
924
  ]);
909
925
  sendJson(response, 200, { tools: { 'lark-cli': lark, 'ccpg-cli': ccpg } });
910
926
  });
911
927
 
928
+ register('/agentone/api/cli/upgrade', async (request, response) => {
929
+ if (request.method !== 'POST') {
930
+ response.writeHead(405, { allow: 'POST' });
931
+ response.end();
932
+ return;
933
+ }
934
+ try {
935
+ const body = await readJsonBody(request);
936
+ const tool = String(body.tool || '');
937
+ if (!CLI_TOOLS.has(tool)) {
938
+ sendJson(response, 400, { error: '不支持的 CLI 工具' });
939
+ return;
940
+ }
941
+ // 官方自升级(ccpg 下载二进制可达分钟级),输出尾部透传给前端
942
+ // (lark-cli 手动安装时含 GitHub 下载地址)
943
+ const output = await upgradeCliTool(tool);
944
+ const detail = await probeCliDetail(tool).catch(() => null);
945
+ sendJson(response, 200, { ok: true, output, tool: detail });
946
+ } catch (error) {
947
+ sendJson(response, 502, { error: error?.message || 'CLI 升级失败,请稍后重试' });
948
+ }
949
+ });
950
+
912
951
  register('/agentone/api/plan', async (request, response) => {
913
952
  if (request.method !== 'GET') {
914
953
  response.writeHead(405, { allow: 'GET' });
package/lib/page.js CHANGED
@@ -423,19 +423,56 @@ const renderCli = async (s) => {
423
423
  return;
424
424
  }
425
425
  const rows = Object.entries(CLI_TOOL_META).map(([name, meta]) => {
426
- const state = tools[name] || 'missing';
426
+ const info = tools[name] || { state: 'missing' };
427
+ const state = info.state || 'missing';
427
428
  const status = state === 'authorized'
428
429
  ? '<span class="pill ok" style="font-size:11px">已授权</span>'
429
430
  : state === 'unauthorized'
430
431
  ? '<span class="pill warn" style="font-size:11px">未授权</span>'
431
432
  : '<span class="pill warn" style="font-size:11px">未安装</span>';
433
+ // 版本展示:当前版 · 有新版时「v旧 → v新」+ 升级按钮(查询失败静默退化)
434
+ const versionLine = meta.desc + ' · ' + meta.label
435
+ + (info.version ? ' · v' + esc(info.version) : '')
436
+ + (info.latest && info.latest !== info.version
437
+ ? ' → <b>v' + esc(info.latest) + '</b>'
438
+ : '');
439
+ const upgradable = info.latest && info.latest !== info.version;
440
+ const actions = upgradable
441
+ ? '<button class="btn small primary" data-cli-upgrade="' + esc(name) + '">升级</button>'
442
+ : '';
432
443
  return '<div class="skill-item"><div class="skill-info"><div class="skill-name">'
433
444
  + esc(name) + status + '</div><div class="skill-sub">'
434
- + esc(meta.desc) + ' · ' + esc(meta.label) + '</div></div></div>';
445
+ + versionLine + '</div></div><div>' + actions + '</div></div>';
435
446
  });
436
447
  body.innerHTML = rows.join('')
437
448
  + '<div class="hint">在对话中直接使用(如 /lark 帮助),由 dsh 自动调用。</div>'
438
- + '<div class="hint">未安装/未授权时,请联系管理员在镜像或本机完成 lark-cli、ccpg-cli 的安装与授权。</div>';
449
+ + '<div class="hint">未安装/未授权时,请联系管理员在镜像或本机完成安装与授权;有新版本时可在此一键升级。</div>';
450
+ body.querySelectorAll('[data-cli-upgrade]').forEach((btn) => {
451
+ btn.onclick = () => cliUpgrade(btn.dataset.cliUpgrade, btn);
452
+ });
453
+ };
454
+
455
+ /** CLI 一键升级:官方 tool update(ccpg 下载二进制可达分钟级)。 */
456
+ const cliUpgrade = async (tool, btn) => {
457
+ btn.disabled = true;
458
+ btn.textContent = '升级中,可能需要 1–2 分钟…';
459
+ try {
460
+ const resp = await fetch('api/cli/upgrade', {
461
+ method: 'POST',
462
+ headers: { 'Content-Type': 'application/json' },
463
+ body: JSON.stringify({ tool }),
464
+ });
465
+ const data = await resp.json().catch(() => ({}));
466
+ if (!resp.ok) throw new Error(data.error || 'CLI 升级失败');
467
+ // lark-cli 手动安装时 update 只会打印下载地址,原样展示给用户
468
+ if (data.output) alert(data.output);
469
+ renderCli._probedAt = 0;
470
+ renderCli(lastStatus);
471
+ } catch (error) {
472
+ alert(error.message || 'CLI 升级失败,请稍后重试');
473
+ btn.disabled = false;
474
+ btn.textContent = '升级';
475
+ }
439
476
  };
440
477
  const pluginAction = (name, action, btn) => withLoading(
441
478
  btn,
package/lib/proc.js CHANGED
@@ -89,7 +89,29 @@ export async function pnpmViewLatest(name, cwd) {
89
89
  * ∈ {valid, needs_refresh}(access 过期但 refresh 可用仍算已授权)
90
90
  */
91
91
  export async function probeCliTool(tool, { timeoutMs = 15000 } = {}) {
92
+ const detail = await probeCliDetail(tool, { timeoutMs });
93
+ return detail.state;
94
+ }
95
+
96
+ /** --version 输出解析:提取首个 x.y[.z…] 版本号(两 CLI 均为
97
+ * "xxx version 1.2.3" 形态)。 */
98
+ export function parseCliVersion(stdout) {
99
+ const match = /(\d+\.\d+(?:\.\d+)*)/.exec(String(stdout || ''));
100
+ return match ? match[1] : null;
101
+ }
102
+
103
+ /**
104
+ * 三态探测 + 版本 + 最新版(任一失败降级为 null,不阻塞三态):
105
+ * version: `tool --version`
106
+ * latest: ccpg-cli 走官方 `update --check --json`(结构化,为 agent
107
+ * 设计);lark-cli 无 check 子命令,用 `npm view @larksuite/cli
108
+ * version`(版本号全局一致,对 npm/pnpm/手动安装都成立)。
109
+ * npm view 需要 npm 在 PATH(node 环境下成立);查询超时/失败返回 null,
110
+ * 前端只显示当前版本不显示升级按钮。
111
+ */
112
+ export async function probeCliDetail(tool, { timeoutMs = 15000 } = {}) {
92
113
  let stdout;
114
+ let state = 'unauthorized';
93
115
  try {
94
116
  ({ stdout } = await execFileAsync(tool, ['auth', 'status'], {
95
117
  timeout: timeoutMs,
@@ -97,11 +119,55 @@ export async function probeCliTool(tool, { timeoutMs = 15000 } = {}) {
97
119
  env: tool === 'ccpg-cli' ? { ...process.env, CCPG_FULL_CLI: '1' } : process.env,
98
120
  }));
99
121
  } catch (error) {
100
- if (error.code === 'ENOENT') return 'missing';
122
+ if (error.code === 'ENOENT') return { state: 'missing', version: null, latest: null };
101
123
  // 探测失败不区分超时/非零退出:统一按已装但状态未知处理
102
124
  stdout = `${error.stdout || ''}`;
103
125
  }
104
- return larkStyleAuthorized(stdout) || ccpgStyleAuthorized(stdout) ? 'authorized' : 'unauthorized';
126
+ if (larkStyleAuthorized(stdout) || ccpgStyleAuthorized(stdout)) state = 'authorized';
127
+
128
+ const [version, latest] = await Promise.all([
129
+ execFileAsync(tool, ['--version'], { timeout: 10_000, killSignal: 'SIGKILL' })
130
+ .then((r) => parseCliVersion(r.stdout))
131
+ .catch(() => null),
132
+ cliLatestVersion(tool, timeoutMs).catch(() => null),
133
+ ]);
134
+ return { state, version, latest };
135
+ }
136
+
137
+ /** 最新版查询:ccpg-cli 官方 check 接口;lark-cli 查 npm 包 dist-tag。 */
138
+ async function cliLatestVersion(tool, timeoutMs) {
139
+ if (tool === 'ccpg-cli') {
140
+ const { stdout } = await execFileAsync(
141
+ tool,
142
+ ['update', '--check', '--json'],
143
+ { timeout: timeoutMs, killSignal: 'SIGKILL', env: { ...process.env, CCPG_FULL_CLI: '1' } },
144
+ );
145
+ const data = JSON.parse(String(stdout).trim());
146
+ return typeof data?.latest_version === 'string' ? data.latest_version : null;
147
+ }
148
+ if (tool === 'lark-cli') {
149
+ const { stdout } = await execFileAsync(
150
+ 'npm',
151
+ ['view', '@larksuite/cli', 'version'],
152
+ { timeout: timeoutMs, killSignal: 'SIGKILL' },
153
+ );
154
+ return parseCliVersion(stdout);
155
+ }
156
+ return null;
157
+ }
158
+
159
+ /**
160
+ * 触发 CLI 官方自升级(`tool update`):ccpg-cli 从更新服务器下载二进制
161
+ * (~17MB),lark-cli 自动探测 npm/pnpm/手动安装方式(手动时只打印
162
+ * GitHub 下载地址)。返回输出尾部给前端展示(含手动安装的下载链接)。
163
+ */
164
+ export async function upgradeCliTool(tool, { timeoutMs = 300_000 } = {}) {
165
+ const { stdout } = await runCliTool(tool, ['update'], {
166
+ timeoutMs,
167
+ env: tool === 'ccpg-cli' ? { ...process.env, CCPG_FULL_CLI: '1' } : process.env,
168
+ });
169
+ const lines = String(stdout).split('\n').map((l) => l.trim()).filter(Boolean);
170
+ return lines.slice(-6).join('\n');
105
171
  }
106
172
 
107
173
  /** lark-cli 形态:identities 下的 tokenStatus 或新版 status/available 组合。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agentone",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "AgentOne 平台集成插件:飞书登录、平台模型、SkillHub 技能、套餐申请、插件管理、lark-cli / ccpg-cli 探测(请求超时与错误码透传、令牌轮换 single-flight、凭据损坏容错、状态页 UX 增强)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",