dsh-agentone 0.5.23 → 0.5.24
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/page.js +4 -3
- package/lib/proc.js +130 -30
- package/package.json +1 -1
package/lib/page.js
CHANGED
|
@@ -672,11 +672,12 @@ const cliAuthDialog = async (tool) => {
|
|
|
672
672
|
}
|
|
673
673
|
};
|
|
674
674
|
|
|
675
|
-
/** CLI 一键安装:lark 走 npm
|
|
676
|
-
*
|
|
675
|
+
/** CLI 一键安装:lark 走 npm 全局安装(机器无 Node.js 时先自动补装)、
|
|
676
|
+
* ccpg/python 走官方安装器,耗时可达数分钟。响应自带安装后立即探测的
|
|
677
|
+
* 详情,直接合进缓存刷新界面。 */
|
|
677
678
|
const cliInstall = async (tool, btn) => {
|
|
678
679
|
btn.disabled = true;
|
|
679
|
-
btn.textContent = '安装中,可能需要
|
|
680
|
+
btn.textContent = '安装中,可能需要 3–5 分钟…';
|
|
680
681
|
try {
|
|
681
682
|
const resp = await fetch('api/cli/install', {
|
|
682
683
|
method: 'POST',
|
package/lib/proc.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 例外——按 CVE-2024-27980 后的 Node 约定经 cmd.exe /d /s /c 执行,
|
|
7
7
|
// 参数仍是数组形态)与错误翻译;包名/版本白名单在 index.js 的调用侧执行。
|
|
8
8
|
import { execFile } from 'node:child_process';
|
|
9
|
-
import { existsSync, mkdirSync, readdirSync, renameSync } from 'node:fs';
|
|
9
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync } from 'node:fs';
|
|
10
10
|
import { homedir, tmpdir } from 'node:os';
|
|
11
11
|
import { join, isAbsolute, delimiter as pathDelimiter } from 'node:path';
|
|
12
12
|
import { promisify } from 'node:util';
|
|
@@ -40,8 +40,13 @@ function extraBinDirs() {
|
|
|
40
40
|
);
|
|
41
41
|
// nodejs.org 官方安装器默认系统级安装位(机器级 Program Files;
|
|
42
42
|
// 机器上没有 node.exe 时不会注入 PATH,但兑底目录里如果找到了,
|
|
43
|
-
// 就能解析出 node/npm,不用再要求用户装 Node
|
|
44
|
-
|
|
43
|
+
// 就能解析出 node/npm,不用再要求用户装 Node)+ 本插件一键补装的
|
|
44
|
+
// 用户级安装位(%LOCALAPPDATA%\Programs\nodejs,免管理员)。
|
|
45
|
+
dirs.push(
|
|
46
|
+
join(programFiles, 'nodejs'),
|
|
47
|
+
join(programFilesX86, 'nodejs'),
|
|
48
|
+
join(localAppData, 'Programs', 'nodejs'),
|
|
49
|
+
);
|
|
45
50
|
// npm -g 默认 prefix、pnpm 默认 PNPM_HOME,以及 ccpg-cli Windows 官方
|
|
46
51
|
// 安装器的落点 %USERPROFILE%\bin(本机实测 C:\Users\<user>\bin\
|
|
47
52
|
// ccpg-cli.exe;装完不重启也能立即探测/自升级)。nodejs.org 安装器的
|
|
@@ -88,25 +93,29 @@ function extraBinDirs() {
|
|
|
88
93
|
}
|
|
89
94
|
// DSH Desktop 自带的 node/pnpm/dsh shim(Electron 当 node 用):宿主只把
|
|
90
95
|
// 这目录注入桌面终端的 PATH,插件子进程看不到——不兑底的话 dsh 自身有
|
|
91
|
-
// node 环境但 lark-cli 安装仍报「未找到 npm
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (process.platform === 'win32') {
|
|
96
|
+
// node 环境但 lark-cli 安装仍报「未找到 npm」。macOS 位
|
|
97
|
+
// ~/Library/Application Support/DSH Desktop/cli/<profile hash>/bin;
|
|
98
|
+
// Windows 实测在 Roaming 下(真实用户机报错的命令路径前缀就是
|
|
99
|
+
// C:\Users\<user>\AppData\Roaming\DSH…),Local 也一并扫。两种布局都
|
|
100
|
+
// 兼容:shims/ 直挂、cli/<hash>/bin。
|
|
101
|
+
const desktopRoots = process.platform === 'win32'
|
|
102
|
+
? [
|
|
103
|
+
join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'DSH Desktop'),
|
|
104
|
+
join(process.env.LOCALAPPDATA || join(home, 'AppData', 'Local'), 'DSH Desktop'),
|
|
105
|
+
]
|
|
106
|
+
: [join(home, 'Library', 'Application Support', 'DSH Desktop')];
|
|
107
|
+
for (const root of desktopRoots) {
|
|
104
108
|
try {
|
|
105
|
-
const shimDir = join(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
const shimDir = join(root, 'shims');
|
|
110
|
+
if (existsSync(join(shimDir, 'node')) || existsSync(join(shimDir, 'node.exe')) || existsSync(join(shimDir, 'node.cmd'))) {
|
|
111
|
+
dirs.push(shimDir);
|
|
112
|
+
}
|
|
113
|
+
for (const entry of readdirSync(join(root, 'cli'))) {
|
|
114
|
+
const shimBin = join(root, 'cli', entry, 'bin');
|
|
115
|
+
if (existsSync(join(shimBin, 'node')) || existsSync(join(shimBin, 'node.exe')) || existsSync(join(shimBin, 'node.cmd'))) {
|
|
116
|
+
dirs.push(shimBin);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
110
119
|
} catch {
|
|
111
120
|
// 目录不存在:跳过
|
|
112
121
|
}
|
|
@@ -133,12 +142,18 @@ export function candidateSuffixes(platform) {
|
|
|
133
142
|
/** win32 下 .cmd/.bat shim 的 execFile 等价调用形态(纯函数,供测试):
|
|
134
143
|
* Node ≥ 18.20/20.12(CVE-2024-27980 修复,DSH Desktop 内置 Node 同样
|
|
135
144
|
* 受影响)起 execFile 直接执行 .cmd/.bat 会同步抛 spawn EINVAL,必须经
|
|
136
|
-
* cmd.exe /d /s /c
|
|
137
|
-
*
|
|
145
|
+
* cmd.exe /d /s /c 解释。
|
|
146
|
+
* 形态必须整行单参数 + 外层引号 + windowsVerbatimArguments(与 Node
|
|
147
|
+
* shell:true 的内部构造一致):逐参数传数组时,含空格路径(如
|
|
148
|
+
* "C:\Users\x\AppData\Roaming\DSH Desktop\shims\npm.cmd")会被 cmd /s 的
|
|
149
|
+
* 引号剥离规则在空格处拆断,报「不是内部或外部命令」(真实用户机实测)。
|
|
150
|
+
* 参数不拼接用户输入(白名单 tool + 固定 flag + 校验过的设备码/URL)。 */
|
|
138
151
|
export function cmdShimInvocation(platform, resolved, args) {
|
|
139
152
|
if (platform !== 'win32') return null;
|
|
140
153
|
if (!/\.(cmd|bat)$/i.test(resolved)) return null;
|
|
141
|
-
|
|
154
|
+
const quote = (s) => (/[\s"]/.test(s) ? `"${String(s).replace(/"/g, '""')}"` : String(s));
|
|
155
|
+
const line = [resolved, ...args].map(quote).join(' ');
|
|
156
|
+
return { file: 'cmd.exe', args: ['/d', '/s', '/c', `"${line}"`], verbatim: true };
|
|
142
157
|
}
|
|
143
158
|
|
|
144
159
|
/** PATH 环境变量键名(纯函数,供测试):win32 下实际键通常是 Path,注入
|
|
@@ -192,12 +207,29 @@ export function resolveCommand(tool) {
|
|
|
192
207
|
return resolveCommandOrNull(tool) || tool;
|
|
193
208
|
}
|
|
194
209
|
|
|
210
|
+
/** 子进程输出解码(纯函数,供测试):Windows 中文系统的 cmd/批处理内部
|
|
211
|
+
* 错误是 GBK 编码(如「'xx' 不是内部或外部命令」),而 npm/node 自身输出
|
|
212
|
+
* 是 UTF-8——先按 UTF-8 严格解码,失败退化 GBK,乱码(♦/锟斤拷)不进
|
|
213
|
+
* 用户可见的错误提示。 */
|
|
214
|
+
export function decodeOutput(buf) {
|
|
215
|
+
const bytes = Buffer.isBuffer(buf) ? buf : Buffer.from(String(buf ?? ''));
|
|
216
|
+
try {
|
|
217
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
218
|
+
} catch {
|
|
219
|
+
try {
|
|
220
|
+
return new TextDecoder('gbk').decode(bytes);
|
|
221
|
+
} catch {
|
|
222
|
+
return bytes.toString('utf-8');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
195
227
|
/** 统一的子进程执行:解析命令路径,并把**所有兑底目录**前置进子进程
|
|
196
228
|
* PATH——win32 下 npm/lark-cli 是 .cmd shim(`%~dp0\node.exe` 找 node),
|
|
197
229
|
* node 可能在另一个兑底目录(nodejs.org 系统安装位);.cmd 又经
|
|
198
230
|
* cmd.exe /d /s /c 路由,cmd.exe 在第三个兑底目录(System32)。只前置
|
|
199
|
-
* 解析到的那个目录会让 shim
|
|
200
|
-
*
|
|
231
|
+
* 解析到的那个目录会让 shim 内部找不到依赖,必须整组前置。输出按
|
|
232
|
+
* encoding:buffer 收回后 decodeOutput(GBK 容错)。opts.env 只传增量变量。 */
|
|
201
233
|
function execResolved(tool, args, { timeoutMs, cwd, env } = {}) {
|
|
202
234
|
const resolved = resolveCommand(tool);
|
|
203
235
|
const childEnv = { ...process.env, ...(env || {}) };
|
|
@@ -212,7 +244,18 @@ function execResolved(tool, args, { timeoutMs, cwd, env } = {}) {
|
|
|
212
244
|
killSignal: 'SIGKILL',
|
|
213
245
|
cwd,
|
|
214
246
|
env: childEnv,
|
|
215
|
-
|
|
247
|
+
encoding: 'buffer',
|
|
248
|
+
windowsVerbatimArguments: shim?.verbatim === true,
|
|
249
|
+
}).then(
|
|
250
|
+
({ stdout, stderr }) => ({ stdout: decodeOutput(stdout), stderr: decodeOutput(stderr) }),
|
|
251
|
+
(error) => {
|
|
252
|
+
if (error && (error.stdout !== undefined || error.stderr !== undefined)) {
|
|
253
|
+
error.stdout = decodeOutput(error.stdout);
|
|
254
|
+
error.stderr = decodeOutput(error.stderr);
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
},
|
|
258
|
+
);
|
|
216
259
|
}
|
|
217
260
|
|
|
218
261
|
/** 从 pnpm 冗长输出里提取对用户有意义的错误行。 */
|
|
@@ -503,13 +546,18 @@ export async function installCliTool(tool, { timeoutMs = 600_000 } = {}) {
|
|
|
503
546
|
let stdout;
|
|
504
547
|
if (tool === 'lark-cli') {
|
|
505
548
|
// 预检+降级链:优先独立 npm;没有 npm 但有 pnpm(含 DSH Desktop 自带
|
|
506
|
-
// shim)时用 pnpm
|
|
507
|
-
//
|
|
508
|
-
//
|
|
549
|
+
// shim)时用 pnpm 全局装;两者都没有时 Windows 先一键补装 Node.js
|
|
550
|
+
// (用户目录免管理员,npmmirror 镜像),再用其自带 npm 装 lark-cli。
|
|
551
|
+
// lark-cli 官方支持 pnpm 安装方式,装出来的二进制与 npm 路径一致,
|
|
552
|
+
// 后续 lark-cli update 也能自升级。
|
|
509
553
|
if (resolveCommandOrNull('npm')) {
|
|
510
554
|
({ stdout } = await runCliTool('npm', ['install', '-g', '@larksuite/cli@latest'], { timeoutMs }));
|
|
511
555
|
} else if (resolveCommandOrNull('pnpm')) {
|
|
512
556
|
({ stdout } = await runCliTool('pnpm', ['add', '-g', '@larksuite/cli@latest'], { timeoutMs }));
|
|
557
|
+
} else if (process.platform === 'win32') {
|
|
558
|
+
const nodeNote = await installNodeWindows(timeoutMs);
|
|
559
|
+
({ stdout } = await runCliTool('npm', ['install', '-g', '@larksuite/cli@latest'], { timeoutMs }));
|
|
560
|
+
stdout = `${nodeNote}\n${stdout}`;
|
|
513
561
|
} else {
|
|
514
562
|
throw new Error(missingCommandGuidance('npm'));
|
|
515
563
|
}
|
|
@@ -677,6 +725,58 @@ async function installPythonWindows(timeoutMs) {
|
|
|
677
725
|
};
|
|
678
726
|
}
|
|
679
727
|
|
|
728
|
+
/** Node.js Windows 一键补装(lark-cli 的运行时依赖:机器无 node/npm/pnpm
|
|
729
|
+
* 时自动补,用户不再被要求「先自行安装 Node.js」):
|
|
730
|
+
* 1) npmmirror 二进制镜像优先(官网 nodejs.org 国内慢/不稳),失败退官网;
|
|
731
|
+
* zip 包绿色解压,不走 MSI(免管理员/UAC)
|
|
732
|
+
* 2) 解压到 %LOCALAPPDATA%\Programs\nodejs(与 python.org 每用户安装同
|
|
733
|
+
* 级的标准位置),写用户 PATH(注册表 User 作用域)
|
|
734
|
+
* 3) npm 全局 prefix 即该目录,后续装的 lark-cli 也落这里——兑底目录已
|
|
735
|
+
* 覆盖,装完本进程内即可探测
|
|
736
|
+
* 全程 execFile 参数数组,不经 shell。 */
|
|
737
|
+
const NODE_WINDOWS_VERSION = 'v22.17.0';
|
|
738
|
+
const NODE_WINDOWS_ZIP_URLS = [
|
|
739
|
+
`https://registry.npmmirror.com/-/binary/node/${NODE_WINDOWS_VERSION}/node-${NODE_WINDOWS_VERSION}-win-x64.zip`,
|
|
740
|
+
`https://nodejs.org/dist/${NODE_WINDOWS_VERSION}/node-${NODE_WINDOWS_VERSION}-win-x64.zip`,
|
|
741
|
+
];
|
|
742
|
+
|
|
743
|
+
async function installNodeWindows(timeoutMs) {
|
|
744
|
+
const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local');
|
|
745
|
+
const programsDir = join(localAppData, 'Programs');
|
|
746
|
+
const nodeHome = join(programsDir, 'nodejs');
|
|
747
|
+
const zip = join(tmpdir(), 'node-setup.zip');
|
|
748
|
+
let downloaded = false;
|
|
749
|
+
let lastError = null;
|
|
750
|
+
for (const url of NODE_WINDOWS_ZIP_URLS) {
|
|
751
|
+
try {
|
|
752
|
+
await runCliTool('curl', [
|
|
753
|
+
'-fsSL', '--retry', '2', '--retry-delay', '3', '--max-time', '600',
|
|
754
|
+
'--speed-limit', '65536', '--speed-time', '30',
|
|
755
|
+
'-o', zip, url,
|
|
756
|
+
], { timeoutMs });
|
|
757
|
+
downloaded = true;
|
|
758
|
+
break;
|
|
759
|
+
} catch (error) {
|
|
760
|
+
lastError = error;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (!downloaded) throw lastError || new Error('Node.js 安装包下载失败,请检查网络后重试');
|
|
764
|
+
mkdirSync(programsDir, { recursive: true });
|
|
765
|
+
// PowerShell Expand-Archive(Win10+ 自带)。路径单引号内嵌转义防空格/引号
|
|
766
|
+
const psQuote = (p) => `'${p.replace(/'/g, "''")}'`;
|
|
767
|
+
await runCliTool('powershell', [
|
|
768
|
+
'-NoProfile', '-Command',
|
|
769
|
+
`Expand-Archive -LiteralPath ${psQuote(zip)} -DestinationPath ${psQuote(programsDir)} -Force`,
|
|
770
|
+
], { timeoutMs });
|
|
771
|
+
// zip 内层目录名 node-vX.Y.Z-win-x64 → 平铺为 nodejs(重装时先清旧目录)
|
|
772
|
+
rmSync(nodeHome, { recursive: true, force: true });
|
|
773
|
+
renameSync(join(programsDir, `node-${NODE_WINDOWS_VERSION}-win-x64`), nodeHome);
|
|
774
|
+
rmSync(zip, { force: true });
|
|
775
|
+
await ensureUserPathContains(nodeHome).catch(() => {});
|
|
776
|
+
resetExtraBinDirsCache();
|
|
777
|
+
return `Node.js ${NODE_WINDOWS_VERSION} 安装完成(${nodeHome})`;
|
|
778
|
+
}
|
|
779
|
+
|
|
680
780
|
/**
|
|
681
781
|
* 触发 CLI 官方自升级(`tool update`):ccpg-cli 从更新服务器下载二进制
|
|
682
782
|
* (~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.24",
|
|
4
4
|
"description": "AgentOne 平台集成插件:飞书登录、平台模型(同步失败自愈重试)、SkillHub 技能(升级检测一键升级、装后即时刷新、套餐技能自动同步)、套餐申请、插件管理与自助升级、lark-cli / ccpg-cli 探测与一键安装(请求超时与错误码透传、令牌轮换 single-flight、凭据损坏容错与并发安全写入、状态页 UX 增强)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|