dsh-agentone 0.5.21 → 0.5.22
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 +42 -6
- package/lib/page.js +35 -6
- package/lib/proc.js +168 -39
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -34,7 +34,7 @@ import { applyPlatformSkillhub, removePlatformSkillhub } from './skillhub.js';
|
|
|
34
34
|
import { beginCliAuth, cliAuthQrcode, completeCliAuth, parseCliAuthBeginOutput } from './cliauth.mjs';
|
|
35
35
|
import { platformFetch } from './http.js';
|
|
36
36
|
import { donePageHtml, statusPageHtml } from './page.js';
|
|
37
|
-
import { compareSemver, dshPluginCli, firstErrorLines, installCliTool, probeCliDetail, pnpmViewLatest, upgradeCliTool } from './proc.js';
|
|
37
|
+
import { compareSemver, dshPluginCli, firstErrorLines, installCliTool, isUnexpectedStoreError, probeCliDetail, pnpmViewLatest, runCliTool, unexpectedStoreGuidance, upgradeCliTool } from './proc.js';
|
|
38
38
|
|
|
39
39
|
import { clearCredentials, loadCredentials, resolveDshHome, saveCredentials, updateCredentials } from './store.js';
|
|
40
40
|
|
|
@@ -300,6 +300,11 @@ export function desktopRunPlugin(service, invokingDir) {
|
|
|
300
300
|
await sleep(2000 * attempt);
|
|
301
301
|
continue;
|
|
302
302
|
}
|
|
303
|
+
// pnpm store 主版本冲突(Windows 桌面版高发:用户全局 pnpm 与
|
|
304
|
+
// 受管 pnpm 版本不同):给可操作指引,不裸抛英文堆栈
|
|
305
|
+
if (isUnexpectedStoreError(`${stderr}\n${stdout}`)) {
|
|
306
|
+
throw new Error(unexpectedStoreGuidance());
|
|
307
|
+
}
|
|
303
308
|
// ERR_PNPM_IGNORED_BUILDS 等非零退出在这里抛出,由调用侧
|
|
304
309
|
// install/upgrade 的「以落盘版本为准」兜底接管
|
|
305
310
|
throw new Error(message || `命令执行失败(退出码 ${outcome.exitCode ?? '未知'})`);
|
|
@@ -807,8 +812,22 @@ export function apply(ctx, config) {
|
|
|
807
812
|
// 失败自愈:模型同步残留错误时由轮询驱动后台重试(成功即清,
|
|
808
813
|
// 下次轮询状态页恢复);错误已被登录/手动同步清除时取消待定
|
|
809
814
|
// 重试并复位退避计数。
|
|
810
|
-
if (credentials.model_error)
|
|
811
|
-
|
|
815
|
+
if (credentials.model_error) {
|
|
816
|
+
// 套餐已生效(active)却残留 model_error:多半是审批前同步失败
|
|
817
|
+
// 的旧错误(plan_required),此时重试必然成功——立即同步一次,
|
|
818
|
+
// 不走首轮 60s 的指数退避。常量幂等:同步成功清掉错误后不再进。
|
|
819
|
+
const planActive = capabilities?.plan_state === 'active';
|
|
820
|
+
if (planActive) {
|
|
821
|
+
syncPlatformModel(config, log)
|
|
822
|
+
.then(() => {
|
|
823
|
+
clearModelRetry();
|
|
824
|
+
modelRetryAttempts = 0;
|
|
825
|
+
})
|
|
826
|
+
.catch(() => scheduleModelRetry(config, log));
|
|
827
|
+
} else {
|
|
828
|
+
scheduleModelRetry(config, log);
|
|
829
|
+
}
|
|
830
|
+
} else {
|
|
812
831
|
clearModelRetry();
|
|
813
832
|
modelRetryAttempts = 0;
|
|
814
833
|
}
|
|
@@ -1191,12 +1210,29 @@ export function apply(ctx, config) {
|
|
|
1191
1210
|
}
|
|
1192
1211
|
// 真实探测本机 CLI:三态与平台 cli-deployd 判定口径一致,附带版本
|
|
1193
1212
|
// 与最新版(查询失败降级 null,不阻塞三态)。并发探测,单个失败不影
|
|
1194
|
-
//
|
|
1195
|
-
|
|
1213
|
+
// 响另一个。附带 Python 环境探测(技能脚本标准依赖,Windows GUI 启动
|
|
1214
|
+
// 时 PATH 常缺 python,缺失时前端给安装指引)。
|
|
1215
|
+
const pythonCandidates = process.platform === 'win32'
|
|
1216
|
+
? ['python', 'python3', 'py']
|
|
1217
|
+
: ['python3', 'python'];
|
|
1218
|
+
const probePython = async () => {
|
|
1219
|
+
for (const name of pythonCandidates) {
|
|
1220
|
+
try {
|
|
1221
|
+
const result = await runCliTool(name, ['--version'], { timeoutMs: 10_000 });
|
|
1222
|
+
const match = /(\d+\.\d+(?:\.\d+)?)/.exec(`${result.stdout || ''}${result.stderr || ''}`);
|
|
1223
|
+
if (match) return { available: true, command: name, version: match[1] };
|
|
1224
|
+
} catch {
|
|
1225
|
+
// 未安装或 Store 假别名:试下一个候选
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return { available: false, command: null, version: null };
|
|
1229
|
+
};
|
|
1230
|
+
const [lark, ccpg, python] = await Promise.all([
|
|
1196
1231
|
probeCliDetail('lark-cli').catch(() => ({ state: 'missing', version: null, latest: null })),
|
|
1197
1232
|
probeCliDetail('ccpg-cli').catch(() => ({ state: 'missing', version: null, latest: null })),
|
|
1233
|
+
probePython().catch(() => ({ available: false, command: null, version: null })),
|
|
1198
1234
|
]);
|
|
1199
|
-
sendJson(response, 200, { tools: { 'lark-cli': lark, 'ccpg-cli': ccpg } });
|
|
1235
|
+
sendJson(response, 200, { tools: { 'lark-cli': lark, 'ccpg-cli': ccpg }, python });
|
|
1200
1236
|
});
|
|
1201
1237
|
|
|
1202
1238
|
register('/agentone/api/cli/install', async (request, response) => {
|
package/lib/page.js
CHANGED
|
@@ -465,22 +465,26 @@ const renderCli = async (s) => {
|
|
|
465
465
|
// 真实探测本机安装与授权态(数秒),期间先展示读取中
|
|
466
466
|
body.innerHTML = '<div class="skill-sub">检测中…</div>';
|
|
467
467
|
let tools;
|
|
468
|
+
let python;
|
|
468
469
|
try {
|
|
469
470
|
const resp = await fetch('api/cli/status');
|
|
470
471
|
const data = await resp.json().catch(() => ({}));
|
|
471
472
|
if (!resp.ok) throw new Error(data.error || '检测失败');
|
|
472
473
|
tools = data.tools || {};
|
|
474
|
+
python = data.python || null;
|
|
473
475
|
} catch {
|
|
474
476
|
body.innerHTML = '<div class="skill-sub" style="color:var(--err)">CLI 状态检测失败,请稍后重试。</div>';
|
|
475
477
|
return;
|
|
476
478
|
}
|
|
477
|
-
renderCliData(s, tools);
|
|
479
|
+
renderCliData(s, tools, python);
|
|
478
480
|
};
|
|
479
|
-
// 纯渲染:tools 形如 { 'lark-cli': {state,version,latest}, ... }
|
|
481
|
+
// 纯渲染:tools 形如 { 'lark-cli': {state,version,latest}, ... },
|
|
482
|
+
// python 形如 { available, command, version }(null 表示未探测到数据)。
|
|
480
483
|
// 升级成功后端会带回升级后立即探测的单工具详情,直接合进缓存同步刷新,
|
|
481
484
|
// 不再整页二次探测(否则要再等一轮「检测中…」,用户以为没生效)。
|
|
482
|
-
const renderCliData = (s, tools) => {
|
|
485
|
+
const renderCliData = (s, tools, python) => {
|
|
483
486
|
renderCli._tools = { ...(renderCli._tools || {}), ...tools };
|
|
487
|
+
if (python !== undefined && python !== null) renderCli._python = python;
|
|
484
488
|
const allTools = renderCli._tools;
|
|
485
489
|
const cliEnabled = s.capabilities?.cli_deployd_enabled !== false;
|
|
486
490
|
const body = $('cli-body');
|
|
@@ -500,7 +504,6 @@ const renderCliData = (s, tools) => {
|
|
|
500
504
|
: state === 'unauthorized'
|
|
501
505
|
? '<span class="pill warn" style="font-size:11px">未授权</span>'
|
|
502
506
|
: '<span class="pill warn" style="font-size:11px">未安装</span>';
|
|
503
|
-
// 版本展示:当前版 · 有新版时「v旧 → v新」+ 升级按钮(查询失败静默退化)
|
|
504
507
|
const versionLine = meta.desc + ' · ' + meta.label
|
|
505
508
|
+ (info.version ? ' · v' + esc(info.version) : '')
|
|
506
509
|
+ (info.latest && info.latest !== info.version
|
|
@@ -523,7 +526,23 @@ const renderCliData = (s, tools) => {
|
|
|
523
526
|
+ esc(name) + status + '</div><div class="skill-sub">'
|
|
524
527
|
+ versionLine + '</div></div><div style="display:flex;gap:6px">' + actions + '</div></div>';
|
|
525
528
|
});
|
|
526
|
-
|
|
529
|
+
// Python 环境行:技能脚本的标准依赖,缺失时按平台给安装指引
|
|
530
|
+
const py = renderCli._python;
|
|
531
|
+
const isWin = navigator.platform && /win/i.test(navigator.platform);
|
|
532
|
+
const pythonRow = py
|
|
533
|
+
? '<div class="skill-item"><div class="skill-info"><div class="skill-name">Python '
|
|
534
|
+
+ (py.available
|
|
535
|
+
? '<span class="pill ok" style="font-size:11px">可用</span>'
|
|
536
|
+
: '<span class="pill warn" style="font-size:11px">未安装</span>')
|
|
537
|
+
+ '</div><div class="skill-sub">'
|
|
538
|
+
+ (py.available
|
|
539
|
+
? '技能脚本运行环境 · ' + esc(py.command) + ' · v' + esc(py.version)
|
|
540
|
+
: isWin
|
|
541
|
+
? '技能脚本运行环境 · 未检测到 Python,从 python.org 下载安装(勾选 Add to PATH)后重启 DSH Desktop'
|
|
542
|
+
: '技能脚本运行环境 · 未检测到 Python,可用系统包管理器安装(如 brew install python)后重启 dsh')
|
|
543
|
+
+ '</div></div></div>'
|
|
544
|
+
: '';
|
|
545
|
+
body.innerHTML = rows.join('') + pythonRow
|
|
527
546
|
+ '<div class="hint">在对话中直接使用(如 /lark 帮助),由 dsh 自动调用。</div>'
|
|
528
547
|
+ '<div class="hint">未安装时点「安装」自助安装;已安装未授权时点「去授权」用飞书扫码完成即可。</div>';
|
|
529
548
|
body.querySelectorAll('[data-cli-upgrade]').forEach((btn) => {
|
|
@@ -957,11 +976,21 @@ const refresh = async () => {
|
|
|
957
976
|
}
|
|
958
977
|
$('model-body').innerHTML = lines.join('')
|
|
959
978
|
+ (planBlocked
|
|
960
|
-
? '<div class="row" style="margin-top:10px"
|
|
979
|
+
? '<div class="row" style="margin-top:10px">'
|
|
980
|
+
+ '<button id="retry-model-btn" class="btn ghost small" data-loading="同步中…">重新同步模型</button>'
|
|
981
|
+
+ '<button id="goto-plan-btn" class="btn primary small">去申请套餐</button></div>'
|
|
961
982
|
: '');
|
|
962
983
|
$('sync-btn').hidden = !modelEnabled || planBlocked;
|
|
963
984
|
const gotoPlan = $('goto-plan-btn');
|
|
964
985
|
if (gotoPlan) gotoPlan.onclick = () => activateTab('plan');
|
|
986
|
+
// planBlocked 兜底手动出口:旧错误残留时(套餐已批但本地还挂着
|
|
987
|
+
// plan_required)可立即重同步,不等后端自愈的退避周期
|
|
988
|
+
const retryModel = $('retry-model-btn');
|
|
989
|
+
if (retryModel) retryModel.onclick = () => withLoading(
|
|
990
|
+
retryModel,
|
|
991
|
+
() => fetch('api/model/sync', { method: 'POST' }),
|
|
992
|
+
'平台模型已同步',
|
|
993
|
+
);
|
|
965
994
|
}
|
|
966
995
|
if (s.logged_in && s.skillhub) {
|
|
967
996
|
const sh = s.skillhub;
|
package/lib/proc.js
CHANGED
|
@@ -13,39 +13,80 @@ import { promisify } from 'node:util';
|
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
14
|
|
|
15
15
|
/** 常见安装目录兑底(环境 PATH 之外的补充):GUI 启动的 DSH Desktop 继承
|
|
16
|
-
* launchd 的 PATH(/usr/bin:/bin
|
|
16
|
+
* launchd 的 PATH(/usr/bin:/bin/…),不含 nvm/homebrew/.local —— 实测
|
|
17
17
|
* 从 Dock 启动时 pnpm/lark-cli 全部 ENOENT,插件页 latest 查不到、CLI
|
|
18
|
-
* 全显示未安装。
|
|
18
|
+
* 全显示未安装。Windows 同理:GUI 启动继承系统 PATH,常缺 %APPDATA%\npm
|
|
19
|
+
* (npm 全局 bin)与 %LOCALAPPDATA%\pnpm(pnpm 默认 home)。nvm 取最高
|
|
20
|
+
* node 版本的 bin;结果进程内缓存。 */
|
|
19
21
|
let extraBinDirsCache = null;
|
|
20
22
|
function extraBinDirs() {
|
|
21
23
|
if (extraBinDirsCache !== null) return extraBinDirsCache;
|
|
22
24
|
const dirs = [];
|
|
23
25
|
const home = homedir();
|
|
26
|
+
if (process.platform === 'win32') {
|
|
27
|
+
const appData = process.env.APPDATA || join(home, 'AppData', 'Roaming');
|
|
28
|
+
const localAppData = process.env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
29
|
+
// npm -g 默认 prefix 与 pnpm 默认 PNPM_HOME;nodejs.org 安装器的
|
|
30
|
+
// 系统级安装不在这,但那种环境 PATH 里有 node,不需要兑底。
|
|
31
|
+
dirs.push(join(appData, 'npm'), join(localAppData, 'pnpm'));
|
|
32
|
+
} else {
|
|
33
|
+
try {
|
|
34
|
+
// nvm 目录名形如 v24.18.0,用 compareSemver 取最高版(compareSemver
|
|
35
|
+
// 在本文件后文定义,函数提升可用)
|
|
36
|
+
const versions = readdirSync(join(home, '.nvm/versions/node'))
|
|
37
|
+
.filter((v) => /^v?\d/.test(v))
|
|
38
|
+
.sort((a, b) => compareSemver(a.replace(/^v/, ''), b.replace(/^v/, '')));
|
|
39
|
+
if (versions.length) dirs.push(join(home, '.nvm/versions/node', versions[versions.length - 1], 'bin'));
|
|
40
|
+
} catch {
|
|
41
|
+
// 无 nvm:跳过,走后面的通用目录
|
|
42
|
+
}
|
|
43
|
+
dirs.push(join(home, '.local/bin'), '/opt/homebrew/bin', '/usr/local/bin');
|
|
44
|
+
}
|
|
45
|
+
// DSH Desktop 自带的 node/pnpm/dsh shim(Electron 当 node 用):宿主只把
|
|
46
|
+
// 这目录注入桌面终端的 PATH,插件子进程看不到——不兑底的话 dsh 自身有
|
|
47
|
+
// node 环境但 lark-cli 安装仍报「未找到 npm」。目录名是 profile 的 hash,
|
|
48
|
+
// 扫一层取含 bin 的子目录。Windows 桌面版把 shim 放在
|
|
49
|
+
// %LOCALAPPDATA%\\DSH Desktop\\shims(按官方约定),一并覆盖。
|
|
24
50
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (versions.length) dirs.push(join(home, '.nvm/versions/node', versions[versions.length - 1], 'bin'));
|
|
51
|
+
const cliRoot = join(home, 'Library', 'Application Support', 'DSH Desktop', 'cli');
|
|
52
|
+
for (const entry of readdirSync(cliRoot)) {
|
|
53
|
+
const shimBin = join(cliRoot, entry, 'bin');
|
|
54
|
+
if (existsSync(join(shimBin, 'node'))) dirs.push(shimBin);
|
|
55
|
+
}
|
|
31
56
|
} catch {
|
|
32
|
-
//
|
|
57
|
+
// 非 macOS / 未生成终端 shim:跳过
|
|
58
|
+
}
|
|
59
|
+
if (process.platform === 'win32') {
|
|
60
|
+
try {
|
|
61
|
+
const shimDir = join(
|
|
62
|
+
process.env.LOCALAPPDATA || join(home, 'AppData', 'Local'),
|
|
63
|
+
'DSH Desktop', 'shims',
|
|
64
|
+
);
|
|
65
|
+
if (existsSync(join(shimDir, 'node.exe')) || existsSync(join(shimDir, 'node.cmd'))) dirs.push(shimDir);
|
|
66
|
+
} catch {
|
|
67
|
+
// 目录不存在:跳过
|
|
68
|
+
}
|
|
33
69
|
}
|
|
34
|
-
dirs.push(join(home, '.local/bin'), '/opt/homebrew/bin', '/usr/local/bin');
|
|
35
70
|
extraBinDirsCache = dirs;
|
|
36
71
|
return dirs;
|
|
37
72
|
}
|
|
38
73
|
|
|
39
|
-
/** 命令解析:PATH
|
|
40
|
-
*
|
|
41
|
-
|
|
74
|
+
/** 命令解析:PATH 里找不到时从兑底目录解析绝对路径(找不到返回 null)。
|
|
75
|
+
* 所有 execFile 调用统一过这里;返回 null 时调用方走「未找到命令」的
|
|
76
|
+
* 中文指引分支。 */
|
|
77
|
+
export function resolveCommandOrNull(tool) {
|
|
42
78
|
const pathDirs = String(process.env.PATH || '').split(pathDelimiter).filter(Boolean);
|
|
43
79
|
const candidates = [];
|
|
44
80
|
for (const dir of [...pathDirs, ...extraBinDirs()]) {
|
|
45
81
|
candidates.push(join(dir, tool));
|
|
46
82
|
if (process.platform === 'win32') candidates.push(join(dir, `${tool}.exe`), join(dir, `${tool}.cmd`));
|
|
47
83
|
}
|
|
48
|
-
return candidates.find((p) => existsSync(p)) ||
|
|
84
|
+
return candidates.find((p) => existsSync(p)) || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 兼容旧签名:找不到时返回原名(后续 ENOENT 走既有 missing/降级语义)。 */
|
|
88
|
+
export function resolveCommand(tool) {
|
|
89
|
+
return resolveCommandOrNull(tool) || tool;
|
|
49
90
|
}
|
|
50
91
|
|
|
51
92
|
/** 统一的子进程执行:解析命令路径,并把解析到的目录前置进子进程 PATH
|
|
@@ -76,6 +117,21 @@ export function firstErrorLines(raw) {
|
|
|
76
117
|
return picked.slice(0, 300);
|
|
77
118
|
}
|
|
78
119
|
|
|
120
|
+
/** pnpm store 版本冲突(受管 pnpm 与用户全局 pnpm 主版本不同,store 目录
|
|
121
|
+
* 格式不兼容)。Windows 桌面版高发:用户装了独立 pnpm 且主版本与 DSH
|
|
122
|
+
* Desktop 内置的不同。识别后给可操作指引而非裸抛英文堆栈。 */
|
|
123
|
+
export function isUnexpectedStoreError(message) {
|
|
124
|
+
return /ERR_PNPM_UNEXPECTED_STORE/i.test(String(message || ''));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** 翻译 pnpm store 冲突为人话 + 自愈指引。 */
|
|
128
|
+
export function unexpectedStoreGuidance() {
|
|
129
|
+
return '检测到 pnpm 存储版本冲突(DSH Desktop 内置的 pnpm 与你电脑上安装的 pnpm 主版本不一致)。'
|
|
130
|
+
+ '请在 DSH Desktop 外自行执行一次插件安装即可自动重建存储:关闭本窗口后运行 '
|
|
131
|
+
+ (process.platform === 'win32' ? 'dsh plugin --profile desktop add dsh-agentone' : 'dsh plugin --profile web add dsh-agentone')
|
|
132
|
+
+ ',或删除 profile 目录下的 node_modules 后重试。';
|
|
133
|
+
}
|
|
134
|
+
|
|
79
135
|
/**
|
|
80
136
|
* 运行一次性命令。webServer 注册的处理器是同步 Node handler 场景下的
|
|
81
137
|
* async 函数;execFile 不经过 shell,参数数组中的每个元素都是独立参数,
|
|
@@ -91,16 +147,36 @@ export async function runCliTool(tool, args, { timeoutMs = 300000, cwd, env } =
|
|
|
91
147
|
return { stdout, stderr };
|
|
92
148
|
} catch (error) {
|
|
93
149
|
if (error.code === 'ENOENT') {
|
|
94
|
-
throw new Error(
|
|
150
|
+
throw new Error(missingCommandGuidance(tool));
|
|
95
151
|
}
|
|
96
152
|
if (error.killed) {
|
|
97
153
|
throw new Error('命令执行超时,请稍后重试');
|
|
98
154
|
}
|
|
99
|
-
const
|
|
155
|
+
const raw = `${error.stderr || ''}\n${error.stdout || ''}`;
|
|
156
|
+
if (isUnexpectedStoreError(raw)) {
|
|
157
|
+
throw new Error(unexpectedStoreGuidance());
|
|
158
|
+
}
|
|
159
|
+
const message = firstErrorLines(raw);
|
|
100
160
|
throw new Error(message || `命令执行失败(退出码 ${error.code ?? '未知'})`);
|
|
101
161
|
}
|
|
102
162
|
}
|
|
103
163
|
|
|
164
|
+
/** 工具缺失时的中文指引:不同工具有不同的安装出路。 */
|
|
165
|
+
function missingCommandGuidance(tool) {
|
|
166
|
+
if (tool === 'npm' || tool === 'npx') {
|
|
167
|
+
return process.platform === 'win32'
|
|
168
|
+
? '未找到 npm/npx 命令:lark-cli 依赖 Node.js 环境,请先从 https://nodejs.org 下载安装 Node.js(LTS 版,安装时保持默认选项),安装完成后重启 DSH Desktop 再点「安装」'
|
|
169
|
+
: '未找到 npm/npx 命令:lark-cli 依赖 Node.js 环境,请先安装 Node.js(推荐 nvm:nvm install --lts),安装完成后重启 DSH Desktop 再点「安装」';
|
|
170
|
+
}
|
|
171
|
+
if (tool === 'pnpm') {
|
|
172
|
+
return '未找到 pnpm 命令,无法查询插件最新版本。可安装 pnpm(npm install -g pnpm)后重启 DSH Desktop;不影响插件正常使用,仅影响升级提示。';
|
|
173
|
+
}
|
|
174
|
+
if (tool === 'dsh') {
|
|
175
|
+
return '未找到 dsh 命令,请确认已安装 dsh 并在终端可用';
|
|
176
|
+
}
|
|
177
|
+
return `未找到 ${tool} 命令,请确认已安装`;
|
|
178
|
+
}
|
|
179
|
+
|
|
104
180
|
/** dsh plugin 子命令:转发给官方 CLI(profile 由调用方给出)。 */
|
|
105
181
|
export function dshPluginCli(args, profile = 'web') {
|
|
106
182
|
return runCliTool('dsh', ['plugin', '--profile', profile, ...args]);
|
|
@@ -157,16 +233,25 @@ export function compareSemver(a, b) {
|
|
|
157
233
|
}
|
|
158
234
|
|
|
159
235
|
/**
|
|
160
|
-
* 查询包在 registry
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
236
|
+
* 查询包在 registry 的最新版本。优先 `pnpm view`;pnpm 不可用(Windows
|
|
237
|
+
* 桌面版常见——受管 pnpm 不在 PATH)时退化 `npm view`(输出同为版本行,
|
|
238
|
+
* parseLatestVersionLine 通吃)。为什么不用 `pnpm outdated` /
|
|
239
|
+
* `update -L` / `add pkg@latest`:实测 pnpm 11 里 outdated 受 dist-tag
|
|
240
|
+
* 元数据缓存与 minimumReleaseAge 冷却期影响(新版发布后一段时间查不到),
|
|
241
|
+
* update -L 对精确 pin 的依赖是 no-op,add@latest 会解析到缓存旧版;只有
|
|
242
|
+
* 显式版本号可用。view 单字段查询每次真实打 registry,是这里唯一的实时
|
|
243
|
+
* 数据源。查询失败返回 null(前端按钮不出现)。
|
|
166
244
|
*/
|
|
167
245
|
export async function pnpmViewLatest(name, cwd) {
|
|
168
|
-
|
|
169
|
-
|
|
246
|
+
try {
|
|
247
|
+
const { stdout } = await pnpmCli(['view', `${name}@latest`, 'version'], 30000, cwd);
|
|
248
|
+
return parseLatestVersionLine(stdout);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
// pnpm 缺失(含 store 冲突指引类可操作错误)不影响升级检测本身:
|
|
251
|
+
// npm view 同样实时打 registry,版本号全局一致
|
|
252
|
+
const { stdout } = await runCliTool('npm', ['view', `${name}@latest`, 'version'], { timeoutMs: 30_000, cwd });
|
|
253
|
+
return parseLatestVersionLine(stdout);
|
|
254
|
+
}
|
|
170
255
|
}
|
|
171
256
|
|
|
172
257
|
/**
|
|
@@ -233,7 +318,9 @@ async function cliLatestVersion(tool, timeoutMs) {
|
|
|
233
318
|
return typeof data?.latest_version === 'string' ? data.latest_version : null;
|
|
234
319
|
}
|
|
235
320
|
if (tool === 'lark-cli') {
|
|
236
|
-
|
|
321
|
+
// runCliTool 而非裸 execResolved:走 Windows 兑底目录解析 npm,
|
|
322
|
+
// ENOENT 时抛中文指引由上层 catch 降级(按钮不出现)而非静默挂死
|
|
323
|
+
const { stdout } = await runCliTool('npm', ['view', '@larksuite/cli', 'version'], { timeoutMs });
|
|
237
324
|
return parseCliVersion(stdout);
|
|
238
325
|
}
|
|
239
326
|
return null;
|
|
@@ -276,26 +363,42 @@ async function installLarkSkills({ timeoutMs = 180_000 } = {}) {
|
|
|
276
363
|
export async function installCliTool(tool, { timeoutMs = 300_000 } = {}) {
|
|
277
364
|
let stdout;
|
|
278
365
|
if (tool === 'lark-cli') {
|
|
279
|
-
|
|
366
|
+
// 预检+降级链:优先独立 npm;没有 npm 但有 pnpm(含 DSH Desktop 自带
|
|
367
|
+
// shim)时用 pnpm 全局装——lark-cli 官方支持 pnpm 安装方式,装出来的
|
|
368
|
+
// 二进制与 npm 路径一致,后续 lark-cli update 也能自升级。
|
|
369
|
+
// 两者都没有才报「装 Node.js」指引。
|
|
370
|
+
if (resolveCommandOrNull('npm')) {
|
|
371
|
+
({ stdout } = await runCliTool('npm', ['install', '-g', '@larksuite/cli@latest'], { timeoutMs }));
|
|
372
|
+
} else if (resolveCommandOrNull('pnpm')) {
|
|
373
|
+
({ stdout } = await runCliTool('pnpm', ['add', '-g', '@larksuite/cli@latest'], { timeoutMs }));
|
|
374
|
+
} else {
|
|
375
|
+
throw new Error(missingCommandGuidance('npm'));
|
|
376
|
+
}
|
|
280
377
|
const skillsNote = await installLarkSkills().catch((error) => {
|
|
281
378
|
// 软失败:技能装不上不影响 CLI 本体已装成功(如 GitHub 不可达)
|
|
282
379
|
return `lark-cli skills install soft-failed: ${error?.message || error}`;
|
|
283
380
|
});
|
|
284
381
|
stdout = `${stdout}\n${skillsNote}`;
|
|
285
382
|
} else if (tool === 'ccpg-cli') {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
timeoutMs
|
|
297
|
-
|
|
298
|
-
|
|
383
|
+
if (process.platform === 'win32') {
|
|
384
|
+
({ stdout } = await installCcpgCliWindows(timeoutMs));
|
|
385
|
+
} else {
|
|
386
|
+
// 下载 auto-install.sh(curl 失败退化 wget)后交 bash 执行;脚本文本
|
|
387
|
+
// 来自固定公司 URL(非用户输入),作为 bash -c 的单一参数,与平台
|
|
388
|
+
// 侧 `| bash` 等价。
|
|
389
|
+
const scriptUrl = `${CCPG_CLI_INSTALL_URL_PREFIX}/auto-install.sh`;
|
|
390
|
+
const fetchScript = () => runCliTool('curl', [
|
|
391
|
+
'-fsSL', '--retry', '3', '--retry-delay', '5', '--max-time', '120',
|
|
392
|
+
scriptUrl,
|
|
393
|
+
], { timeoutMs: 120_000 })
|
|
394
|
+
.catch(() => runCliTool('wget', ['-qO-', scriptUrl], { timeoutMs: 120_000 }));
|
|
395
|
+
const fetched = (await fetchScript()).stdout.trim();
|
|
396
|
+
if (!fetched) throw new Error('安装脚本下载失败,请检查网络后重试');
|
|
397
|
+
({ stdout } = await runCliTool('bash', ['-c', fetched], {
|
|
398
|
+
timeoutMs,
|
|
399
|
+
env: { CCPG_CLI_URL_PREFIX: CCPG_CLI_INSTALL_URL_PREFIX },
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
299
402
|
} else {
|
|
300
403
|
throw new Error('不支持的 CLI 工具');
|
|
301
404
|
}
|
|
@@ -303,6 +406,32 @@ export async function installCliTool(tool, { timeoutMs = 300_000 } = {}) {
|
|
|
303
406
|
return lines.slice(-6).join('\n');
|
|
304
407
|
}
|
|
305
408
|
|
|
409
|
+
/** ccpg-cli Windows 安装(官方教程口径,202607151817):
|
|
410
|
+
* 1) Windows 10 1803+ 自带 curl.exe 下载官方自安装 exe
|
|
411
|
+
* (https://bms.einwin.com/media/ccpg-cli/202607151817/ccpg-cli-windows-amd64.exe)
|
|
412
|
+
* 2) 静默运行安装(/S 安静模式,装到用户目录并注册 PATH)
|
|
413
|
+
* 3) 教程要求装完必须 ccpg-cli update(拉最新功能)——自动补上。
|
|
414
|
+
* 全程 execFile 参数数组,不经 shell。 */
|
|
415
|
+
const CCPG_CLI_WINDOWS_INSTALLER_URL = 'https://bms.einwin.com/media/ccpg-cli/202607151817/ccpg-cli-windows-amd64.exe';
|
|
416
|
+
|
|
417
|
+
async function installCcpgCliWindows(timeoutMs) {
|
|
418
|
+
const tempDir = join(homedir(), 'AppData', 'Local', 'Temp');
|
|
419
|
+
const installer = join(tempDir, 'ccpg-cli-setup.exe');
|
|
420
|
+
await runCliTool('curl', [
|
|
421
|
+
'-fsSL', '--retry', '3', '--retry-delay', '5', '--max-time', '300',
|
|
422
|
+
'-o', installer, CCPG_CLI_WINDOWS_INSTALLER_URL,
|
|
423
|
+
], { timeoutMs });
|
|
424
|
+
// NSIS 风格安装器:/S 静默安装;退出码非 0 视为失败
|
|
425
|
+
await runCliTool(installer, ['/S'], { timeoutMs });
|
|
426
|
+
// 官方教程的必做步骤:安装完成后立即自升级到最新版(软失败不阻断)
|
|
427
|
+
const updateNote = await runCliTool('ccpg-cli', ['update'], { timeoutMs: 120_000 })
|
|
428
|
+
.then((r) => String(r.stdout || '').trim().split('\n').slice(-2).join('\n'))
|
|
429
|
+
.catch(() => 'ccpg-cli update 未完成,安装后请手动执行 ccpg-cli update');
|
|
430
|
+
return {
|
|
431
|
+
stdout: `ccpg-cli 安装完成\n${updateNote}\n如命令不可用,请重启 DSH Desktop 后点「重新检测」`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
306
435
|
/**
|
|
307
436
|
* 触发 CLI 官方自升级(`tool update`):ccpg-cli 从更新服务器下载二进制
|
|
308
437
|
* (~17MB),lark-cli 自动探测 npm/pnpm/手动安装方式(手动时只打印
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agentone",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.22",
|
|
4
4
|
"description": "AgentOne 平台集成插件:飞书登录、平台模型(同步失败自愈重试)、SkillHub 技能(升级检测一键升级、装后即时刷新、套餐技能自动同步)、套餐申请、插件管理与自助升级、lark-cli / ccpg-cli 探测与一键安装(请求超时与错误码透传、令牌轮换 single-flight、凭据损坏容错与并发安全写入、状态页 UX 增强)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|