@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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "14a762e2a36d3d1d7eb73887c33456f8bd7b3a0e",
3
- "builtAt": "2026-05-30T23:17:58.441Z",
4
- "version": "1.41.2",
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
- * 我们的策略:每次 npm install 之前先清掉 `@co0ontty/.wand-*` 残留目录;
12
- * 如果第一次安装仍然撞上 ENOTEMPTY,清理后重试;再不行就 uninstall + force install。
11
+ * 我们的策略:安装前备份当前全局包,补齐 npm 子进程 PATH,清掉
12
+ * `@co0ontty/.wand-*` 残留目录;失败时恢复备份,避免运行中的服务被半成品安装拆掉。
13
13
  */
14
14
  /**
15
15
  * 解析当前 `npm root -g` 的目录。失败返回 null。
@@ -8,24 +8,64 @@
8
8
  * 如果安装中途失败,这个备份目录会留下,之后每次 npm install 都会因为目标 dest 已存在
9
9
  * 报 `ENOTEMPTY: directory not empty, rename ...`。
10
10
  *
11
- * 我们的策略:每次 npm install 之前先清掉 `@co0ontty/.wand-*` 残留目录;
12
- * 如果第一次安装仍然撞上 ENOTEMPTY,清理后重试;再不行就 uninstall + force install。
11
+ * 我们的策略:安装前备份当前全局包,补齐 npm 子进程 PATH,清掉
12
+ * `@co0ontty/.wand-*` 残留目录;失败时恢复备份,避免运行中的服务被半成品安装拆掉。
13
13
  */
14
- import { exec, spawnSync } from "node:child_process";
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 execAsync = promisify(exec);
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 = spawnSync("npm", ["root", "-g"], { encoding: "utf8", timeout: 10_000 });
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 cleanup = cleanupNpmLeftovers();
97
- if (cleanup.removed.length > 0) {
98
- note(`[wand] 清理 npm 残留目录: ${cleanup.removed.join(", ")}`);
99
- }
253
+ const backup = createGlobalInstallBackup(note);
254
+ let success = false;
100
255
  try {
101
- await execAsync(`npm install -g ${pkg}`, { timeout: timeoutMs });
102
- return;
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
- note(`[wand] npm install 遇到 ENOTEMPTY/EEXIST,清理后重试一次...`);
110
- }
111
- cleanupNpmLeftovers();
112
- try {
113
- await execAsync(`npm install -g ${pkg}`, { timeout: timeoutMs });
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
- note(`[wand] 重试仍失败,尝试先卸载再强制安装...`);
122
- }
123
- // 终极兜底:uninstall + force install
124
- // 卸载用固定包名 PACKAGE_NAME,而不是从 install spec 反推:spec 可能是 git
125
- // 形式(`github:co0ontty/wand#beta`),用正则 strip @tag 反推会得到错误的卸载目标。
126
- try {
127
- await execAsync(`npm uninstall -g ${PACKAGE_NAME}`, { timeout: timeoutMs });
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
- catch {
130
- /* 卸载失败也继续,下一步 --force 可能仍然能装上 */
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 = spawnSync("npm", args, { encoding: "utf8", timeout: timeoutMs });
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 { ...res, attempts };
156
- const hitENOTEMPTY = (r) => /ENOTEMPTY|EEXIST/.test(r.stdout + r.stderr);
157
- if (!hitENOTEMPTY(res))
158
- return { ...res, attempts };
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 { ...res, attempts };
163
- if (!hitENOTEMPTY(res))
164
- return { ...res, attempts };
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
- spawnSync("npm", ["uninstall", "-g", PACKAGE_NAME], { encoding: "utf8", timeout: timeoutMs });
381
+ runNpmSync(["uninstall", "-g", PACKAGE_NAME], timeoutMs);
168
382
  cleanupNpmLeftovers();
169
383
  res = tryInstall(["--force"]);
170
- return { ...res, attempts };
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 path from "node:path";
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(pkgVersion);
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 vendorCacheOpts = { maxAge: "7d", immutable: true };
934
- const contentDir = existsSync(path.join(SERVER_MODULE_DIR, "web-ui", "content"))
935
- ? path.join(SERVER_MODULE_DIR, "web-ui", "content")
936
- : path.join(RUNTIME_ROOT_DIR, "src", "web-ui", "content");
937
- app.use("/vendor/wterm", express.static(path.join(contentDir, "vendor", "wterm"), vendorCacheOpts));
938
- app.use("/vendor/qrcode", express.static(path.join(contentDir, "vendor", "qrcode"), vendorCacheOpts));
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 映射到 --reasoning-effort 参数。off → minimal(不显式思考)。 */
36
- export declare function thinkingEffortToCodexFlag(effort: SessionSnapshot["thinkingEffort"]): string | null;
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 adds --reasoning-effort).
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 映射到 --reasoning-effort 参数。off → minimal(不显式思考)。 */
78
- export function thinkingEffortToCodexFlag(effort) {
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 adds --reasoning-effort).
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
- // 思考深度 → --reasoning-effort(off → minimal,standard → low,deep → medium,max → high)
1121
- const reasoningFlag = thinkingEffortToCodexFlag(session.thinkingEffort);
1122
- if (reasoningFlag) {
1123
- args.push("--reasoning-effort", reasoningFlag);
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: --reasoning-effort minimal)
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 --reasoning-effort minimal)" },
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;