@co0ontty/wand 1.41.2 → 1.41.4
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/dist/build-info.json +3 -3
- package/dist/cli.js +0 -0
- package/dist/npm-update-utils.d.ts +2 -2
- package/dist/npm-update-utils.js +271 -54
- package/dist/pwa.js +2 -22
- package/dist/server.js +9 -9
- package/dist/structured-session-manager.d.ts +3 -3
- package/dist/structured-session-manager.js +9 -7
- package/dist/types.d.ts +1 -1
- package/dist/web-ui/content/scripts.js +1 -1
- package/dist/web-ui/embedded-assets.d.ts +23 -0
- package/dist/web-ui/embedded-assets.js +27 -0
- package/dist/web-ui/index.d.ts +2 -0
- package/dist/web-ui/index.js +3 -26
- package/dist/web-ui/scripts.js +16 -3
- package/dist/web-ui/styles.js +4 -5
- package/package.json +5 -4
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-05-
|
|
4
|
-
"version": "1.41.
|
|
2
|
+
"commit": "7c55e89bdac5b0ba4588b4dc43a7dd2ab5f812ea",
|
|
3
|
+
"builtAt": "2026-05-31T01:09:26.532Z",
|
|
4
|
+
"version": "1.41.4",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* 如果安装中途失败,这个备份目录会留下,之后每次 npm install 都会因为目标 dest 已存在
|
|
9
9
|
* 报 `ENOTEMPTY: directory not empty, rename ...`。
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* 我们的策略:安装前备份当前全局包,补齐 npm 子进程 PATH,清掉
|
|
12
|
+
* `@co0ontty/.wand-*` 残留目录;失败时恢复备份,避免运行中的服务被半成品安装拆掉。
|
|
13
13
|
*/
|
|
14
14
|
/**
|
|
15
15
|
* 解析当前 `npm root -g` 的目录。失败返回 null。
|
package/dist/npm-update-utils.js
CHANGED
|
@@ -8,24 +8,64 @@
|
|
|
8
8
|
* 如果安装中途失败,这个备份目录会留下,之后每次 npm install 都会因为目标 dest 已存在
|
|
9
9
|
* 报 `ENOTEMPTY: directory not empty, rename ...`。
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* 我们的策略:安装前备份当前全局包,补齐 npm 子进程 PATH,清掉
|
|
12
|
+
* `@co0ontty/.wand-*` 残留目录;失败时恢复备份,避免运行中的服务被半成品安装拆掉。
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
15
|
-
import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
14
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
15
|
+
import { chmodSync, cpSync, existsSync, mkdtempSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
16
|
+
import os from "node:os";
|
|
16
17
|
import path from "node:path";
|
|
17
18
|
import process from "node:process";
|
|
18
19
|
import { promisify } from "node:util";
|
|
19
|
-
const
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
20
21
|
const PACKAGE_NAME = "@co0ontty/wand";
|
|
21
22
|
const PACKAGE_SCOPE = "@co0ontty";
|
|
22
23
|
const PACKAGE_BASENAME = "wand";
|
|
24
|
+
const NPM_BIN = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
25
|
+
const COMMON_UNIX_PATHS = ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"];
|
|
26
|
+
const INSTALL_MAX_BUFFER = 10 * 1024 * 1024;
|
|
27
|
+
function getChildEnv() {
|
|
28
|
+
const entries = [
|
|
29
|
+
path.dirname(process.execPath),
|
|
30
|
+
...(process.env.PATH || "").split(path.delimiter),
|
|
31
|
+
...(process.platform === "win32" ? [] : COMMON_UNIX_PATHS),
|
|
32
|
+
];
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const pathEntries = [];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry || seen.has(entry))
|
|
37
|
+
continue;
|
|
38
|
+
seen.add(entry);
|
|
39
|
+
pathEntries.push(entry);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...process.env,
|
|
43
|
+
PATH: pathEntries.join(path.delimiter),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function runNpmSync(args, timeoutMs) {
|
|
47
|
+
const options = {
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
timeout: timeoutMs,
|
|
50
|
+
env: getChildEnv(),
|
|
51
|
+
maxBuffer: INSTALL_MAX_BUFFER,
|
|
52
|
+
};
|
|
53
|
+
return spawnSync(NPM_BIN, args, options);
|
|
54
|
+
}
|
|
55
|
+
async function runNpmAsync(args, timeoutMs) {
|
|
56
|
+
const options = {
|
|
57
|
+
timeout: timeoutMs,
|
|
58
|
+
env: getChildEnv(),
|
|
59
|
+
maxBuffer: INSTALL_MAX_BUFFER,
|
|
60
|
+
};
|
|
61
|
+
await execFileAsync(NPM_BIN, args, options);
|
|
62
|
+
}
|
|
23
63
|
/**
|
|
24
64
|
* 解析当前 `npm root -g` 的目录。失败返回 null。
|
|
25
65
|
*/
|
|
26
66
|
export function getNpmGlobalRoot() {
|
|
27
67
|
try {
|
|
28
|
-
const res =
|
|
68
|
+
const res = runNpmSync(["root", "-g"], 10_000);
|
|
29
69
|
if (res.status !== 0)
|
|
30
70
|
return null;
|
|
31
71
|
const out = (res.stdout || "").trim();
|
|
@@ -77,6 +117,123 @@ export function cleanupNpmLeftovers() {
|
|
|
77
117
|
}
|
|
78
118
|
return { removed, errors };
|
|
79
119
|
}
|
|
120
|
+
const REQUIRED_RUNTIME_FILES = [
|
|
121
|
+
"package.json",
|
|
122
|
+
path.join("dist", "cli.js"),
|
|
123
|
+
path.join("dist", "server.js"),
|
|
124
|
+
path.join("dist", "web-ui", "index.js"),
|
|
125
|
+
path.join("dist", "web-ui", "embedded-assets.js"),
|
|
126
|
+
path.join("dist", "web-ui", "scripts.js"),
|
|
127
|
+
path.join("dist", "web-ui", "styles.js"),
|
|
128
|
+
path.join("dist", "web-ui", "content", "scripts.js"),
|
|
129
|
+
path.join("dist", "web-ui", "content", "styles.css"),
|
|
130
|
+
path.join("dist", "web-ui", "content", "vendor", "wterm", "wterm.bundle.js"),
|
|
131
|
+
path.join("dist", "web-ui", "content", "vendor", "qrcode", "qrcode.bundle.js"),
|
|
132
|
+
];
|
|
133
|
+
function getGlobalPackageDir() {
|
|
134
|
+
const root = getNpmGlobalRoot();
|
|
135
|
+
return root ? path.join(root, PACKAGE_SCOPE, PACKAGE_BASENAME) : null;
|
|
136
|
+
}
|
|
137
|
+
function validateGlobalWandInstall() {
|
|
138
|
+
const packageDir = getGlobalPackageDir();
|
|
139
|
+
if (!packageDir) {
|
|
140
|
+
return { ok: false, message: "无法解析 npm 全局安装目录。" };
|
|
141
|
+
}
|
|
142
|
+
const missing = [];
|
|
143
|
+
for (const rel of REQUIRED_RUNTIME_FILES) {
|
|
144
|
+
const fullPath = path.join(packageDir, rel);
|
|
145
|
+
try {
|
|
146
|
+
if (!statSync(fullPath).isFile()) {
|
|
147
|
+
missing.push(rel);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
missing.push(rel);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (missing.length > 0) {
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
message: `全局 wand 安装不完整: ${packageDir} 缺少 ${missing.join(", ")}`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (process.platform !== "win32") {
|
|
161
|
+
const cliPath = path.join(packageDir, "dist", "cli.js");
|
|
162
|
+
try {
|
|
163
|
+
const mode = statSync(cliPath).mode;
|
|
164
|
+
if ((mode & 0o111) === 0) {
|
|
165
|
+
chmodSync(cliPath, mode | 0o755);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
message: `全局 wand CLI 无法设置执行权限: ${cliPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { ok: true, packageDir };
|
|
176
|
+
}
|
|
177
|
+
function assertGlobalWandInstallComplete() {
|
|
178
|
+
const result = validateGlobalWandInstall();
|
|
179
|
+
if (!result.ok) {
|
|
180
|
+
throw new Error(result.message);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function createGlobalInstallBackup(note) {
|
|
184
|
+
const packageDir = getGlobalPackageDir();
|
|
185
|
+
if (!packageDir) {
|
|
186
|
+
return { packageDir: "", backupDir: null };
|
|
187
|
+
}
|
|
188
|
+
if (!existsSync(packageDir)) {
|
|
189
|
+
return { packageDir, backupDir: null };
|
|
190
|
+
}
|
|
191
|
+
const backupRoot = mkdtempSync(path.join(os.tmpdir(), "wand-global-backup-"));
|
|
192
|
+
const backupDir = path.join(backupRoot, PACKAGE_BASENAME);
|
|
193
|
+
try {
|
|
194
|
+
cpSync(packageDir, backupDir, {
|
|
195
|
+
recursive: true,
|
|
196
|
+
dereference: false,
|
|
197
|
+
verbatimSymlinks: true,
|
|
198
|
+
});
|
|
199
|
+
note?.(`[wand] 已备份当前全局安装: ${backupDir}`);
|
|
200
|
+
return { packageDir, backupDir };
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
rmSync(backupRoot, { recursive: true, force: true });
|
|
204
|
+
note?.(`[wand] 全局安装备份失败,继续尝试更新: ${err instanceof Error ? err.message : String(err)}`);
|
|
205
|
+
return { packageDir, backupDir: null };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function cleanupGlobalInstallBackup(backup) {
|
|
209
|
+
if (!backup.backupDir)
|
|
210
|
+
return;
|
|
211
|
+
rmSync(path.dirname(backup.backupDir), { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
function restoreGlobalInstallBackup(backup, note) {
|
|
214
|
+
if (!backup.packageDir || !backup.backupDir || !existsSync(backup.backupDir))
|
|
215
|
+
return false;
|
|
216
|
+
try {
|
|
217
|
+
rmSync(backup.packageDir, { recursive: true, force: true });
|
|
218
|
+
cpSync(backup.backupDir, backup.packageDir, {
|
|
219
|
+
recursive: true,
|
|
220
|
+
dereference: false,
|
|
221
|
+
verbatimSymlinks: true,
|
|
222
|
+
});
|
|
223
|
+
note?.(`[wand] 已恢复更新前的全局安装: ${backup.packageDir}`);
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
note?.(`[wand] 恢复更新前安装失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function npmInstallGlobalAsync(pkg, timeoutMs, extra = []) {
|
|
232
|
+
await runNpmAsync(["install", "-g", ...extra, pkg], timeoutMs);
|
|
233
|
+
}
|
|
234
|
+
function isRecoverableInstallError(message) {
|
|
235
|
+
return /ENOTEMPTY|EEXIST|全局 wand 安装不完整|无法解析 npm 全局安装目录|全局 wand CLI 无法设置执行权限/.test(message);
|
|
236
|
+
}
|
|
80
237
|
/**
|
|
81
238
|
* 异步版本的全局安装:
|
|
82
239
|
* 1. 清理残留
|
|
@@ -93,44 +250,67 @@ export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
|
|
|
93
250
|
if (log)
|
|
94
251
|
log(line);
|
|
95
252
|
};
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
note(`[wand] 清理 npm 残留目录: ${cleanup.removed.join(", ")}`);
|
|
99
|
-
}
|
|
253
|
+
const backup = createGlobalInstallBackup(note);
|
|
254
|
+
let success = false;
|
|
100
255
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
catch (error) {
|
|
105
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
106
|
-
if (!/ENOTEMPTY|EEXIST/.test(msg)) {
|
|
107
|
-
throw error;
|
|
256
|
+
const cleanup = cleanupNpmLeftovers();
|
|
257
|
+
if (cleanup.removed.length > 0) {
|
|
258
|
+
note(`[wand] 清理 npm 残留目录: ${cleanup.removed.join(", ")}`);
|
|
108
259
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
118
|
-
if (!/ENOTEMPTY|EEXIST/.test(msg)) {
|
|
119
|
-
throw error;
|
|
260
|
+
try {
|
|
261
|
+
await npmInstallGlobalAsync(pkg, timeoutMs);
|
|
262
|
+
assertGlobalWandInstallComplete();
|
|
263
|
+
success = true;
|
|
264
|
+
return;
|
|
120
265
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
266
|
+
catch (error) {
|
|
267
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
268
|
+
if (!isRecoverableInstallError(msg)) {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
if (/全局 wand 安装不完整|无法解析 npm 全局安装目录|全局 wand CLI 无法设置执行权限/.test(msg)) {
|
|
272
|
+
note(`[wand] npm install 后安装目录不完整,尝试强制重装...`);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
note(`[wand] npm install 遇到 ENOTEMPTY/EEXIST,清理后重试一次...`);
|
|
276
|
+
cleanupNpmLeftovers();
|
|
277
|
+
try {
|
|
278
|
+
await npmInstallGlobalAsync(pkg, timeoutMs);
|
|
279
|
+
assertGlobalWandInstallComplete();
|
|
280
|
+
success = true;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
catch (retryError) {
|
|
284
|
+
const retryMsg = retryError instanceof Error ? retryError.message : String(retryError);
|
|
285
|
+
if (!isRecoverableInstallError(retryMsg)) {
|
|
286
|
+
throw retryError;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
note(`[wand] 重试仍失败,尝试先卸载再强制安装...`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// 终极兜底:uninstall + force install
|
|
293
|
+
// 卸载用固定包名 PACKAGE_NAME,而不是从 install spec 反推:spec 可能是 git
|
|
294
|
+
// 形式(`github:co0ontty/wand#beta`),用正则 strip @tag 反推会得到错误的卸载目标。
|
|
295
|
+
try {
|
|
296
|
+
await runNpmAsync(["uninstall", "-g", PACKAGE_NAME], timeoutMs);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
/* 卸载失败也继续,下一步 --force 可能仍然能装上 */
|
|
300
|
+
}
|
|
301
|
+
cleanupNpmLeftovers();
|
|
302
|
+
await npmInstallGlobalAsync(pkg, timeoutMs, ["--force"]);
|
|
303
|
+
assertGlobalWandInstallComplete();
|
|
304
|
+
success = true;
|
|
128
305
|
}
|
|
129
|
-
|
|
130
|
-
|
|
306
|
+
finally {
|
|
307
|
+
if (!success) {
|
|
308
|
+
if (restoreGlobalInstallBackup(backup, note)) {
|
|
309
|
+
cleanupNpmLeftovers();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
cleanupGlobalInstallBackup(backup);
|
|
131
313
|
}
|
|
132
|
-
cleanupNpmLeftovers();
|
|
133
|
-
await execAsync(`npm install -g --force ${pkg}`, { timeout: timeoutMs });
|
|
134
314
|
}
|
|
135
315
|
/**
|
|
136
316
|
* 同步版本,给 TUI installUpdate 用。
|
|
@@ -139,35 +319,72 @@ export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
|
|
|
139
319
|
*/
|
|
140
320
|
export function installPackageGloballySync(pkg, timeoutMs) {
|
|
141
321
|
const attempts = [];
|
|
322
|
+
const backupNotes = [];
|
|
323
|
+
const backup = createGlobalInstallBackup((line) => backupNotes.push(line));
|
|
324
|
+
const withValidation = (res) => {
|
|
325
|
+
if (res.status !== 0)
|
|
326
|
+
return res;
|
|
327
|
+
const validation = validateGlobalWandInstall();
|
|
328
|
+
if (validation.ok)
|
|
329
|
+
return res;
|
|
330
|
+
return {
|
|
331
|
+
status: 1,
|
|
332
|
+
stdout: res.stdout,
|
|
333
|
+
stderr: `${res.stderr ? `${res.stderr}\n` : ""}${validation.message}`,
|
|
334
|
+
};
|
|
335
|
+
};
|
|
142
336
|
const tryInstall = (extra) => {
|
|
143
337
|
const args = ["install", "-g", ...extra, pkg];
|
|
144
338
|
attempts.push(`npm ${args.join(" ")}`);
|
|
145
|
-
const r =
|
|
146
|
-
return {
|
|
339
|
+
const r = runNpmSync(args, timeoutMs);
|
|
340
|
+
return withValidation({
|
|
147
341
|
status: r.status,
|
|
148
342
|
stdout: r.stdout || "",
|
|
149
343
|
stderr: r.stderr || "",
|
|
150
|
-
};
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
const withBackupNotes = (res) => ({
|
|
347
|
+
...res,
|
|
348
|
+
stderr: [res.stderr, ...backupNotes].filter(Boolean).join("\n"),
|
|
349
|
+
attempts,
|
|
350
|
+
});
|
|
351
|
+
const finishSuccess = (res) => {
|
|
352
|
+
cleanupGlobalInstallBackup(backup);
|
|
353
|
+
return withBackupNotes(res);
|
|
354
|
+
};
|
|
355
|
+
const finishFailure = (res) => {
|
|
356
|
+
if (restoreGlobalInstallBackup(backup, (line) => backupNotes.push(line))) {
|
|
357
|
+
cleanupNpmLeftovers();
|
|
358
|
+
}
|
|
359
|
+
cleanupGlobalInstallBackup(backup);
|
|
360
|
+
return withBackupNotes(res);
|
|
151
361
|
};
|
|
152
362
|
cleanupNpmLeftovers();
|
|
153
363
|
let res = tryInstall([]);
|
|
154
|
-
if (res.status === 0)
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
364
|
+
if (res.status === 0) {
|
|
365
|
+
return finishSuccess(res);
|
|
366
|
+
}
|
|
367
|
+
const hitRecoverableInstallError = (r) => isRecoverableInstallError(r.stdout + r.stderr);
|
|
368
|
+
if (!hitRecoverableInstallError(res)) {
|
|
369
|
+
return finishFailure(res);
|
|
370
|
+
}
|
|
159
371
|
cleanupNpmLeftovers();
|
|
160
372
|
res = tryInstall([]);
|
|
161
|
-
if (res.status === 0)
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
|
|
373
|
+
if (res.status === 0) {
|
|
374
|
+
return finishSuccess(res);
|
|
375
|
+
}
|
|
376
|
+
if (!hitRecoverableInstallError(res)) {
|
|
377
|
+
return finishFailure(res);
|
|
378
|
+
}
|
|
165
379
|
// 终极兜底(卸载用固定包名,兼容 git spec,见 async 版同样注释)
|
|
166
380
|
attempts.push(`npm uninstall -g ${PACKAGE_NAME}`);
|
|
167
|
-
|
|
381
|
+
runNpmSync(["uninstall", "-g", PACKAGE_NAME], timeoutMs);
|
|
168
382
|
cleanupNpmLeftovers();
|
|
169
383
|
res = tryInstall(["--force"]);
|
|
170
|
-
|
|
384
|
+
if (res.status === 0) {
|
|
385
|
+
return finishSuccess(res);
|
|
386
|
+
}
|
|
387
|
+
return finishFailure(res);
|
|
171
388
|
}
|
|
172
389
|
/**
|
|
173
390
|
* 解析「刚装好的全局 wand CLI 入口」(dist/cli.js) 的绝对路径。
|
|
@@ -192,7 +409,7 @@ export function resolveGlobalWandCli() {
|
|
|
192
409
|
}
|
|
193
410
|
try {
|
|
194
411
|
const tool = process.platform === "win32" ? "where" : "which";
|
|
195
|
-
const r = spawnSync(tool, ["wand"], { encoding: "utf8", timeout: 10_000 });
|
|
412
|
+
const r = spawnSync(tool, ["wand"], { encoding: "utf8", timeout: 10_000, env: getChildEnv() });
|
|
196
413
|
if (r.status === 0) {
|
|
197
414
|
const first = (r.stdout || "").split(/\r?\n/).find((line) => line.trim().length > 0);
|
|
198
415
|
if (first)
|
package/dist/pwa.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PWA manifest and Service Worker generation.
|
|
3
3
|
*/
|
|
4
|
-
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
5
4
|
import { createHash } from "node:crypto";
|
|
6
|
-
import
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
10
|
-
const pkgVersion = JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "0";
|
|
5
|
+
import { EMBEDDED_WEB_ASSET_VERSION } from "./web-ui/embedded-assets.js";
|
|
11
6
|
/** Cache version: package version + content fingerprint.
|
|
12
7
|
*
|
|
13
8
|
* 之前只用 pkgVersion 派生,本地 dev 时同一个 1.36.0 下改了几次 CSS / scripts,
|
|
@@ -18,22 +13,7 @@ const pkgVersion = JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "0";
|
|
|
18
13
|
* 正式发版时由于 pkgVersion 也会变,效果叠加,无副作用。
|
|
19
14
|
*/
|
|
20
15
|
function buildCacheVersion() {
|
|
21
|
-
const h = createHash("md5").update(
|
|
22
|
-
const fingerprintTargets = [
|
|
23
|
-
path.join(__dirname, "web-ui", "content", "scripts.js"),
|
|
24
|
-
path.join(__dirname, "web-ui", "content", "styles.css"),
|
|
25
|
-
];
|
|
26
|
-
for (const p of fingerprintTargets) {
|
|
27
|
-
try {
|
|
28
|
-
if (existsSync(p)) {
|
|
29
|
-
const s = statSync(p);
|
|
30
|
-
h.update(":").update(p).update(":").update(String(s.mtimeMs)).update(":").update(String(s.size));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
// best effort — fingerprint 只是为了 bust 缓存,失败就退化成 pkg-only
|
|
35
|
-
}
|
|
36
|
-
}
|
|
16
|
+
const h = createHash("md5").update(EMBEDDED_WEB_ASSET_VERSION);
|
|
37
17
|
return h.digest("hex").slice(0, 8);
|
|
38
18
|
}
|
|
39
19
|
// 不 freeze 进模块加载时——SW JS 是每次请求 generateServiceWorker() 现拼的,
|
package/dist/server.js
CHANGED
|
@@ -30,6 +30,7 @@ import { optimizePrompt, PromptOptimizeError } from "./prompt-optimizer.js";
|
|
|
30
30
|
import { resolveDatabasePath, WandStorage } from "./storage.js";
|
|
31
31
|
import { deepRepairRuntimePath, formatPathRepairSummary, repairRuntimePath } from "./path-repair.js";
|
|
32
32
|
import { isLogBusActive, wandTuiLog } from "./tui/log-bus.js";
|
|
33
|
+
import { EMBEDDED_WEB_ASSETS } from "./web-ui/embedded-assets.js";
|
|
33
34
|
import { renderApp } from "./web-ui/index.js";
|
|
34
35
|
import { WsBroadcastManager } from "./ws-broadcast.js";
|
|
35
36
|
import { checkRateLimit, recordFailedLogin, resetRateLimit } from "./middleware/rate-limit.js";
|
|
@@ -930,12 +931,14 @@ export async function startServer(config, configPath) {
|
|
|
930
931
|
const nodeModulesDir = path.join(RUNTIME_ROOT_DIR, "node_modules");
|
|
931
932
|
app.use(express.json({ limit: "1mb" }));
|
|
932
933
|
app.use(compression({ threshold: 1024 }));
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
app.
|
|
934
|
+
const sendEmbeddedVendorAsset = (assetPath, _req, res) => {
|
|
935
|
+
const asset = EMBEDDED_WEB_ASSETS.vendor[assetPath];
|
|
936
|
+
res.setHeader("Cache-Control", "public, max-age=604800, immutable");
|
|
937
|
+
res.type(asset.contentType).send(asset.content);
|
|
938
|
+
};
|
|
939
|
+
app.get("/vendor/wterm/wterm.bundle.js", (req, res) => sendEmbeddedVendorAsset("/vendor/wterm/wterm.bundle.js", req, res));
|
|
940
|
+
app.get("/vendor/wterm/terminal.css", (req, res) => sendEmbeddedVendorAsset("/vendor/wterm/terminal.css", req, res));
|
|
941
|
+
app.get("/vendor/qrcode/qrcode.bundle.js", (req, res) => sendEmbeddedVendorAsset("/vendor/qrcode/qrcode.bundle.js", req, res));
|
|
939
942
|
// ── Web UI and PWA endpoints ──
|
|
940
943
|
app.get("/", (_req, res) => {
|
|
941
944
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
@@ -994,9 +997,6 @@ export async function startServer(config, configPath) {
|
|
|
994
997
|
res.type("image/svg+xml").send(getAvatarSvg(avatarSeed, size));
|
|
995
998
|
});
|
|
996
999
|
}
|
|
997
|
-
const iconsDir = path.resolve(existsSync(path.join(SERVER_MODULE_DIR, "web-ui", "content"))
|
|
998
|
-
? path.join(SERVER_MODULE_DIR, "web-ui", "content")
|
|
999
|
-
: path.join(RUNTIME_ROOT_DIR, "src", "web-ui", "content"));
|
|
1000
1000
|
app.get("/sw.js", (_req, res) => {
|
|
1001
1001
|
res.setHeader("Content-Type", "application/javascript");
|
|
1002
1002
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
@@ -32,8 +32,8 @@ export declare function thinkingEffortToSdkBudget(effort: SessionSnapshot["think
|
|
|
32
32
|
* off → 原 prompt 不变。
|
|
33
33
|
*/
|
|
34
34
|
export declare function applyThinkingEffortToPrompt(prompt: string, effort: SessionSnapshot["thinkingEffort"]): string;
|
|
35
|
-
/** Codex CLI 用:把 thinkingEffort 映射到
|
|
36
|
-
export declare function
|
|
35
|
+
/** Codex CLI 用:把 thinkingEffort 映射到 model_reasoning_effort 配置。off → minimal。 */
|
|
36
|
+
export declare function thinkingEffortToCodexReasoningEffort(effort: SessionSnapshot["thinkingEffort"]): string | null;
|
|
37
37
|
export declare class StructuredSessionManager {
|
|
38
38
|
private readonly storage;
|
|
39
39
|
private readonly config;
|
|
@@ -110,7 +110,7 @@ export declare class StructuredSessionManager {
|
|
|
110
110
|
/**
|
|
111
111
|
* Update the thinking-effort level for a structured session. Takes effect on
|
|
112
112
|
* the next spawn / next message (SDK runner injects `thinking`, CLI runner
|
|
113
|
-
* prepends magic words, codex runner
|
|
113
|
+
* prepends magic words, codex runner overrides `model_reasoning_effort`).
|
|
114
114
|
*/
|
|
115
115
|
setSessionThinkingEffort(sessionId: string, effort: SessionSnapshot["thinkingEffort"]): SessionSnapshot;
|
|
116
116
|
/** Toggle auto-approve for the session. */
|
|
@@ -74,8 +74,8 @@ export function applyThinkingEffortToPrompt(prompt, effort) {
|
|
|
74
74
|
return prompt;
|
|
75
75
|
return prefix + trimmed;
|
|
76
76
|
}
|
|
77
|
-
/** Codex CLI 用:把 thinkingEffort 映射到
|
|
78
|
-
export function
|
|
77
|
+
/** Codex CLI 用:把 thinkingEffort 映射到 model_reasoning_effort 配置。off → minimal。 */
|
|
78
|
+
export function thinkingEffortToCodexReasoningEffort(effort) {
|
|
79
79
|
switch (effort) {
|
|
80
80
|
case "standard": return "low";
|
|
81
81
|
case "deep": return "medium";
|
|
@@ -862,7 +862,7 @@ export class StructuredSessionManager {
|
|
|
862
862
|
/**
|
|
863
863
|
* Update the thinking-effort level for a structured session. Takes effect on
|
|
864
864
|
* the next spawn / next message (SDK runner injects `thinking`, CLI runner
|
|
865
|
-
* prepends magic words, codex runner
|
|
865
|
+
* prepends magic words, codex runner overrides `model_reasoning_effort`).
|
|
866
866
|
*/
|
|
867
867
|
setSessionThinkingEffort(sessionId, effort) {
|
|
868
868
|
const session = this.requireSession(sessionId);
|
|
@@ -1117,10 +1117,12 @@ export class StructuredSessionManager {
|
|
|
1117
1117
|
if (modelChoice && modelChoice !== "default") {
|
|
1118
1118
|
args.push("--model", modelChoice);
|
|
1119
1119
|
}
|
|
1120
|
-
// 思考深度 →
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1120
|
+
// 思考深度 → model_reasoning_effort(off → minimal,standard → low,deep → medium,max → high)
|
|
1121
|
+
// Newer Codex CLI versions removed the old dedicated exec flag, but still
|
|
1122
|
+
// accept config overrides through `-c`.
|
|
1123
|
+
const reasoningEffort = thinkingEffortToCodexReasoningEffort(session.thinkingEffort);
|
|
1124
|
+
if (reasoningEffort) {
|
|
1125
|
+
args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
|
|
1124
1126
|
}
|
|
1125
1127
|
if (session.claudeSessionId) {
|
|
1126
1128
|
args.push("resume", session.claudeSessionId, "-");
|
package/dist/types.d.ts
CHANGED
|
@@ -440,7 +440,7 @@ export interface SessionSnapshot {
|
|
|
440
440
|
selectedModel?: string | null;
|
|
441
441
|
/**
|
|
442
442
|
* 用户选定的思考深度。
|
|
443
|
-
* - off: 不启用思考(SDK: 不传 thinking;CLI: 不插魔法词;Codex:
|
|
443
|
+
* - off: 不启用思考(SDK: 不传 thinking;CLI: 不插魔法词;Codex: model_reasoning_effort minimal)
|
|
444
444
|
* - standard: 标准(SDK: budget 4096;CLI: think;Codex: low)
|
|
445
445
|
* - deep: 深度(SDK: budget 16000;CLI: think hard;Codex: medium)
|
|
446
446
|
* - max: 最深(SDK: budget 31999;CLI: ultrathink;Codex: high)
|
|
@@ -8888,7 +8888,7 @@
|
|
|
8888
8888
|
// 标签直接用 Claude CLI 原生 magic word:think / think hard / ultrathink。
|
|
8889
8889
|
// 这样用户一眼能对上官方文档里的思考强度档位,PTY 模式下也是这几个词被注入到 prompt 前缀。
|
|
8890
8890
|
var THINKING_LEVELS = [
|
|
8891
|
-
{ id: "off", label: "off", hint: "不启用思考(CLI 无前缀;SDK 关闭 thinking;Codex
|
|
8891
|
+
{ id: "off", label: "off", hint: "不启用思考(CLI 无前缀;SDK 关闭 thinking;Codex minimal)" },
|
|
8892
8892
|
{ id: "standard", label: "think", hint: "Claude CLI: think · SDK budget 4096 · Codex low" },
|
|
8893
8893
|
{ id: "deep", label: "think hard", hint: "Claude CLI: think hard · SDK budget 16000 · Codex medium" },
|
|
8894
8894
|
{ id: "max", label: "ultrathink", hint: "Claude CLI: ultrathink · SDK budget 31999 · Codex high" }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const EMBEDDED_WEB_ASSET_VERSION = "5bac58cd1f59";
|
|
2
|
+
export declare const EMBEDDED_WEB_ASSETS: {
|
|
3
|
+
readonly scriptsJs: string;
|
|
4
|
+
readonly stylesCss: string;
|
|
5
|
+
readonly vendor: {
|
|
6
|
+
readonly "/vendor/wterm/wterm.bundle.js": {
|
|
7
|
+
readonly content: string;
|
|
8
|
+
readonly contentType: "application/javascript";
|
|
9
|
+
readonly hash: "5c8595b1";
|
|
10
|
+
};
|
|
11
|
+
readonly "/vendor/wterm/terminal.css": {
|
|
12
|
+
readonly content: string;
|
|
13
|
+
readonly contentType: "text/css; charset=utf-8";
|
|
14
|
+
readonly hash: "e6459118";
|
|
15
|
+
};
|
|
16
|
+
readonly "/vendor/qrcode/qrcode.bundle.js": {
|
|
17
|
+
readonly content: string;
|
|
18
|
+
readonly contentType: "application/javascript";
|
|
19
|
+
readonly hash: "8be76aad";
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export type EmbeddedVendorAssetPath = keyof typeof EMBEDDED_WEB_ASSETS.vendor;
|