@co0ontty/wand 1.21.8 → 1.21.10
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/config.d.ts +1 -1
- package/dist/config.js +13 -0
- package/dist/env-utils.d.ts +9 -0
- package/dist/env-utils.js +47 -0
- package/dist/process-manager.js +3 -3
- package/dist/structured-session-manager.js +48 -2
- package/dist/types.d.ts +6 -0
- package/dist/web-ui/content/scripts.js +24 -5
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { WandStorage } from "./storage.js";
|
|
|
7
7
|
* 升级路径:老 JSON 里仍存有这些字段时,首次启动会被搬到 DB(见 migrateLegacyPreferencesToDb),
|
|
8
8
|
* 然后下一次 saveConfig 写回 JSON 时它们会被剥离(见 stripPreferenceFields)。
|
|
9
9
|
*/
|
|
10
|
-
export declare const PREFERENCE_KEYS: readonly ["defaultMode", "defaultCwd", "defaultModel", "structuredRunner", "language", "cardDefaults"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultMode", "defaultCwd", "defaultModel", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
11
11
|
export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
|
|
12
12
|
export declare function isPreferenceKey(key: string): key is PreferenceKey;
|
|
13
13
|
export declare const defaultConfig: () => WandConfig;
|
package/dist/config.js
CHANGED
|
@@ -19,6 +19,7 @@ export const PREFERENCE_KEYS = [
|
|
|
19
19
|
"structuredRunner",
|
|
20
20
|
"language",
|
|
21
21
|
"cardDefaults",
|
|
22
|
+
"inheritEnv",
|
|
22
23
|
];
|
|
23
24
|
const PREFERENCE_KEY_SET = new Set(PREFERENCE_KEYS);
|
|
24
25
|
function preferenceStorageKey(key) {
|
|
@@ -43,6 +44,7 @@ export const defaultConfig = () => ({
|
|
|
43
44
|
cardDefaults: defaultCardExpandDefaults(),
|
|
44
45
|
defaultModel: "",
|
|
45
46
|
structuredRunner: "cli",
|
|
47
|
+
inheritEnv: true,
|
|
46
48
|
commandPresets: [
|
|
47
49
|
{
|
|
48
50
|
label: "Claude",
|
|
@@ -201,6 +203,10 @@ export function applyStoragePreferences(config, storage) {
|
|
|
201
203
|
const v = storage.getPreference(preferenceStorageKey("cardDefaults"), defaults.cardDefaults);
|
|
202
204
|
config.cardDefaults = normalizeCardDefaults(v);
|
|
203
205
|
}
|
|
206
|
+
if (storage.hasPreference(preferenceStorageKey("inheritEnv"))) {
|
|
207
|
+
const v = storage.getPreference(preferenceStorageKey("inheritEnv"), defaults.inheritEnv ?? true);
|
|
208
|
+
config.inheritEnv = v === false ? false : true;
|
|
209
|
+
}
|
|
204
210
|
return config;
|
|
205
211
|
}
|
|
206
212
|
/** Write a single preference value to DB and (in-place) update the live config object. */
|
|
@@ -244,6 +250,12 @@ export function writePreferenceToStorage(config, storage, key, value) {
|
|
|
244
250
|
config.cardDefaults = normalized;
|
|
245
251
|
break;
|
|
246
252
|
}
|
|
253
|
+
case "inheritEnv": {
|
|
254
|
+
const v = value === false ? false : true;
|
|
255
|
+
storage.setPreference(dbKey, v);
|
|
256
|
+
config.inheritEnv = v;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
247
259
|
}
|
|
248
260
|
}
|
|
249
261
|
function defaultCardExpandDefaults() {
|
|
@@ -349,6 +361,7 @@ function mergeWithDefaults(input) {
|
|
|
349
361
|
cardDefaults: normalizeCardDefaults(input.cardDefaults),
|
|
350
362
|
defaultModel: typeof input.defaultModel === "string" ? input.defaultModel.trim() : defaults.defaultModel,
|
|
351
363
|
structuredRunner: (input.structuredRunner === "sdk" || input.structuredRunner === "cli") ? input.structuredRunner : defaults.structuredRunner,
|
|
364
|
+
inheritEnv: typeof input.inheritEnv === "boolean" ? input.inheritEnv : (defaults.inheritEnv ?? true),
|
|
352
365
|
};
|
|
353
366
|
}
|
|
354
367
|
export function isExecutionMode(value) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 根据 inheritEnv 配置组装子进程的环境变量。
|
|
3
|
+
*
|
|
4
|
+
* - inheritEnv=true(默认):继承父进程全部 env,再合并 extras 覆盖。
|
|
5
|
+
* - inheritEnv=false:仅保留 MINIMAL_ENV_KEYS 中存在的字段,再合并 extras 覆盖。
|
|
6
|
+
*
|
|
7
|
+
* extras 中的 undefined 字段会被剔除(spawn 不允许 env 值为 undefined)。
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildChildEnv(inheritEnv: boolean, extras?: Record<string, string | undefined>): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
/**
|
|
3
|
+
* 用于子进程 spawn 时的环境变量白名单(当用户关闭"继承环境变量"时使用)。
|
|
4
|
+
* 仅保留运行 CLI 工具所需的最小集合,避免把 API key、token 等敏感凭据继承到子命令。
|
|
5
|
+
*/
|
|
6
|
+
const MINIMAL_ENV_KEYS = [
|
|
7
|
+
"PATH",
|
|
8
|
+
"HOME",
|
|
9
|
+
"USER",
|
|
10
|
+
"LOGNAME",
|
|
11
|
+
"SHELL",
|
|
12
|
+
"LANG",
|
|
13
|
+
"LC_ALL",
|
|
14
|
+
"LC_CTYPE",
|
|
15
|
+
"TERM",
|
|
16
|
+
"TZ",
|
|
17
|
+
"TMPDIR",
|
|
18
|
+
"TMP",
|
|
19
|
+
"TEMP",
|
|
20
|
+
"PWD",
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* 根据 inheritEnv 配置组装子进程的环境变量。
|
|
24
|
+
*
|
|
25
|
+
* - inheritEnv=true(默认):继承父进程全部 env,再合并 extras 覆盖。
|
|
26
|
+
* - inheritEnv=false:仅保留 MINIMAL_ENV_KEYS 中存在的字段,再合并 extras 覆盖。
|
|
27
|
+
*
|
|
28
|
+
* extras 中的 undefined 字段会被剔除(spawn 不允许 env 值为 undefined)。
|
|
29
|
+
*/
|
|
30
|
+
export function buildChildEnv(inheritEnv, extras = {}) {
|
|
31
|
+
const base = {};
|
|
32
|
+
if (inheritEnv) {
|
|
33
|
+
Object.assign(base, process.env);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
for (const key of MINIMAL_ENV_KEYS) {
|
|
37
|
+
const v = process.env[key];
|
|
38
|
+
if (typeof v === "string")
|
|
39
|
+
base[key] = v;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const [k, v] of Object.entries(extras)) {
|
|
43
|
+
if (typeof v === "string")
|
|
44
|
+
base[k] = v;
|
|
45
|
+
}
|
|
46
|
+
return base;
|
|
47
|
+
}
|
package/dist/process-manager.js
CHANGED
|
@@ -9,6 +9,7 @@ import { SessionLogger } from "./session-logger.js";
|
|
|
9
9
|
import { ClaudePtyBridge } from "./claude-pty-bridge.js";
|
|
10
10
|
import { truncateMessagesForTransport } from "./message-truncator.js";
|
|
11
11
|
import { appendWindow, hasExplicitConfirmSyntax, hasPermissionActionContext, normalizePromptText, PTY_OUTPUT_MAX_SIZE } from "./pty-text-utils.js";
|
|
12
|
+
import { buildChildEnv } from "./env-utils.js";
|
|
12
13
|
import { prepareSessionWorktree } from "./git-worktree.js";
|
|
13
14
|
import { getResumeCommandSessionId } from "./resume-policy.js";
|
|
14
15
|
function resolveProviderFromCommand(command) {
|
|
@@ -659,12 +660,11 @@ export class ProcessManager extends EventEmitter {
|
|
|
659
660
|
try {
|
|
660
661
|
child = pty.spawn(this.config.shell, shellArgs, {
|
|
661
662
|
cwd: resolvedCwd,
|
|
662
|
-
env: {
|
|
663
|
-
...process.env,
|
|
663
|
+
env: buildChildEnv(this.config.inheritEnv !== false, {
|
|
664
664
|
WAND_MODE: effectiveMode,
|
|
665
665
|
WAND_AUTO_CONFIRM: effectiveMode === "full-access" ? "1" : "0",
|
|
666
666
|
WAND_AUTO_EDIT: effectiveMode === "auto-edit" ? "1" : "0"
|
|
667
|
-
},
|
|
667
|
+
}),
|
|
668
668
|
name: "xterm-color",
|
|
669
669
|
// 使用 record 上由前端协商好的真实尺寸,避免"先 120 列、几百毫秒后再 resize"
|
|
670
670
|
// 期间 Claude/Codex 用错列宽渲染出 \x1b[120G 这类绝对列定位序列。
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
3
5
|
import { prepareSessionWorktree } from "./git-worktree.js";
|
|
4
6
|
import { truncateMessagesForTransport } from "./message-truncator.js";
|
|
7
|
+
import { buildChildEnv } from "./env-utils.js";
|
|
5
8
|
function defaultStructuredRunner(provider) {
|
|
6
9
|
return provider === "codex" ? "codex-cli-exec" : "claude-cli-print";
|
|
7
10
|
}
|
|
@@ -24,6 +27,47 @@ const ARCHIVE_AFTER_MS = 1000 * 60 * 60 * 24;
|
|
|
24
27
|
function isRunningAsRoot() {
|
|
25
28
|
return process.getuid?.() === 0 || process.geteuid?.() === 0;
|
|
26
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* 检测当前系统是否使用 musl libc(Alpine Linux 等)。
|
|
32
|
+
* Node.js 进程报告中 glibcVersionRuntime 仅在 glibc 系统存在;musl 系统为 undefined。
|
|
33
|
+
*/
|
|
34
|
+
function isMuslSystem() {
|
|
35
|
+
try {
|
|
36
|
+
const header = process.report?.getReport()?.header;
|
|
37
|
+
return !header?.glibcVersionRuntime;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 解析 claude-agent-sdk 应使用的 native binary 路径。
|
|
45
|
+
* SDK 默认在 Linux 上优先选 musl 包,但 glibc 系统(Debian/Ubuntu 等)跑不动 musl binary,
|
|
46
|
+
* 会抛 "Claude Code native binary not found" 错误。这里手动按 libc 类型选正确的包,
|
|
47
|
+
* 找不到时回退到系统 PATH 上的 `claude`。
|
|
48
|
+
*/
|
|
49
|
+
function resolveSdkClaudeBinary() {
|
|
50
|
+
if (process.platform !== "linux")
|
|
51
|
+
return undefined;
|
|
52
|
+
const musl = isMuslSystem();
|
|
53
|
+
const arch = process.arch;
|
|
54
|
+
const require = createRequire(import.meta.url);
|
|
55
|
+
// 按当前 libc 类型决定优先顺序
|
|
56
|
+
const candidates = musl
|
|
57
|
+
? [`@anthropic-ai/claude-agent-sdk-linux-${arch}-musl/claude`, `@anthropic-ai/claude-agent-sdk-linux-${arch}/claude`]
|
|
58
|
+
: [`@anthropic-ai/claude-agent-sdk-linux-${arch}/claude`, `@anthropic-ai/claude-agent-sdk-linux-${arch}-musl/claude`];
|
|
59
|
+
for (const pkg of candidates) {
|
|
60
|
+
try {
|
|
61
|
+
const resolved = require.resolve(pkg);
|
|
62
|
+
if (existsSync(resolved))
|
|
63
|
+
return resolved;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// 包不存在,继续
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
27
71
|
/**
|
|
28
72
|
* 找出最后一条 assistant turn 中尚未配对 tool_result 的 AskUserQuestion tool_use。
|
|
29
73
|
* 用来识别"刚被 SIGTERM 中断、正在等用户提交答案"的状态。
|
|
@@ -713,7 +757,7 @@ export class StructuredSessionManager {
|
|
|
713
757
|
const spawnedAt = new Date().toISOString();
|
|
714
758
|
const child = spawn("codex", args, {
|
|
715
759
|
cwd: session.cwd,
|
|
716
|
-
env:
|
|
760
|
+
env: buildChildEnv(this.config.inheritEnv !== false),
|
|
717
761
|
stdio: ["pipe", "pipe", "pipe"],
|
|
718
762
|
});
|
|
719
763
|
this.logger?.appendStructuredSpawn(sessionId, {
|
|
@@ -1042,7 +1086,7 @@ export class StructuredSessionManager {
|
|
|
1042
1086
|
const spawnedAt = new Date().toISOString();
|
|
1043
1087
|
const child = spawn("claude", args, {
|
|
1044
1088
|
cwd: session.cwd,
|
|
1045
|
-
env:
|
|
1089
|
+
env: buildChildEnv(this.config.inheritEnv !== false),
|
|
1046
1090
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1047
1091
|
});
|
|
1048
1092
|
this.logger?.appendStructuredSpawn(sessionId, {
|
|
@@ -1491,6 +1535,7 @@ export class StructuredSessionManager {
|
|
|
1491
1535
|
? "请使用中文回复。所有解释、注释和对话文本都使用中文。"
|
|
1492
1536
|
: `Please respond in ${language}. Use ${language} for all your explanations, comments, and conversational text.`);
|
|
1493
1537
|
}
|
|
1538
|
+
const sdkClaudeBinary = resolveSdkClaudeBinary();
|
|
1494
1539
|
const sdkOptions = {
|
|
1495
1540
|
cwd: session.cwd,
|
|
1496
1541
|
abortController,
|
|
@@ -1500,6 +1545,7 @@ export class StructuredSessionManager {
|
|
|
1500
1545
|
...(isManaged ? { disallowedTools: ["AskUserQuestion"] } : {}),
|
|
1501
1546
|
includePartialMessages: true,
|
|
1502
1547
|
...(systemPromptParts.length > 0 ? { appendSystemPrompt: systemPromptParts.join("\n\n") } : {}),
|
|
1548
|
+
...(sdkClaudeBinary ? { pathToClaudeCodeExecutable: sdkClaudeBinary } : {}),
|
|
1503
1549
|
};
|
|
1504
1550
|
if (session.claudeSessionId)
|
|
1505
1551
|
sdkOptions.resume = session.claudeSessionId;
|
package/dist/types.d.ts
CHANGED
|
@@ -93,6 +93,12 @@ export interface WandConfig {
|
|
|
93
93
|
defaultModel?: string;
|
|
94
94
|
/** 结构化会话使用的 runner: "cli"(默认,spawn claude -p)或 "sdk"(@anthropic-ai/claude-agent-sdk)。 */
|
|
95
95
|
structuredRunner?: "cli" | "sdk";
|
|
96
|
+
/**
|
|
97
|
+
* 启动 PTY / 结构化子进程时是否继承父进程的环境变量(process.env)。默认 true。
|
|
98
|
+
* 关闭后子进程仅获得最小可用环境(PATH/HOME/SHELL/LANG/LC_ALL/TERM 等)外加 WAND_* 控制变量,
|
|
99
|
+
* 用于隔离敏感凭据或避免 API key 泄漏到子命令。
|
|
100
|
+
*/
|
|
101
|
+
inheritEnv?: boolean;
|
|
96
102
|
}
|
|
97
103
|
export interface ClaudeModelInfo {
|
|
98
104
|
/** 传给 --model 的值(别名或完整模型 ID) */
|
|
@@ -2142,6 +2142,16 @@
|
|
|
2142
2142
|
'</select>' +
|
|
2143
2143
|
'<p class="field-hint" style="margin-top:4px;">SDK 模式使用官方 Agent SDK 替代 CLI subprocess,接口更整洁,功能等价。保存后对新建会话立即生效。</p>' +
|
|
2144
2144
|
'</div>' +
|
|
2145
|
+
'<div class="settings-toggle-row">' +
|
|
2146
|
+
'<div class="settings-toggle-text">' +
|
|
2147
|
+
'<label class="settings-toggle-title" for="cfg-inherit-env">继承环境变量</label>' +
|
|
2148
|
+
'<span class="settings-toggle-desc">启动 PTY / 结构化子进程时,把当前服务进程的环境变量传给 claude / codex。关闭后子进程仅获得最小可用环境(PATH/HOME/SHELL/LANG/TERM 等),可用于隔离 API key 等敏感凭据。</span>' +
|
|
2149
|
+
'</div>' +
|
|
2150
|
+
'<label class="settings-switch">' +
|
|
2151
|
+
'<input id="cfg-inherit-env" type="checkbox" class="switch-toggle" />' +
|
|
2152
|
+
'<span class="switch-slider"></span>' +
|
|
2153
|
+
'</label>' +
|
|
2154
|
+
'</div>' +
|
|
2145
2155
|
'<div class="field">' +
|
|
2146
2156
|
'<label class="field-label" for="cfg-default-model">默认模型</label>' +
|
|
2147
2157
|
'<div class="settings-row-with-action">' +
|
|
@@ -7539,6 +7549,9 @@
|
|
|
7539
7549
|
var srEl = document.getElementById("cfg-structured-runner");
|
|
7540
7550
|
if (srEl) srEl.value = cfg.structuredRunner || "cli";
|
|
7541
7551
|
|
|
7552
|
+
var inheritEnvEl = document.getElementById("cfg-inherit-env");
|
|
7553
|
+
if (inheritEnvEl) inheritEnvEl.checked = cfg.inheritEnv !== false;
|
|
7554
|
+
|
|
7542
7555
|
// Default model
|
|
7543
7556
|
state.configDefaultModel = cfg.defaultModel || "";
|
|
7544
7557
|
updateSettingsDefaultModelSelect();
|
|
@@ -7598,6 +7611,7 @@
|
|
|
7598
7611
|
language: (document.getElementById("cfg-language") || {}).value || "",
|
|
7599
7612
|
defaultModel: (document.getElementById("cfg-default-model") || {}).value || "",
|
|
7600
7613
|
structuredRunner: (document.getElementById("cfg-structured-runner") || {}).value || "cli",
|
|
7614
|
+
inheritEnv: (document.getElementById("cfg-inherit-env") || {}).checked !== false,
|
|
7601
7615
|
};
|
|
7602
7616
|
|
|
7603
7617
|
var previousDefaultModel = (state.config && state.config.defaultModel) || "";
|
|
@@ -15597,7 +15611,7 @@
|
|
|
15597
15611
|
// ── Native Live Progress Sync ──
|
|
15598
15612
|
|
|
15599
15613
|
var _progressSyncTimers = {};
|
|
15600
|
-
var _PROGRESS_SYNC_DEBOUNCE_MS =
|
|
15614
|
+
var _PROGRESS_SYNC_DEBOUNCE_MS = 100;
|
|
15601
15615
|
|
|
15602
15616
|
// Strip markdown formatting and clamp to a single short line so the
|
|
15603
15617
|
// native Live Activity / lock-screen card stays readable. 100 chars
|
|
@@ -15647,6 +15661,7 @@
|
|
|
15647
15661
|
var todos = null;
|
|
15648
15662
|
var latestUserText = "";
|
|
15649
15663
|
var latestAssistantText = "";
|
|
15664
|
+
var recentUserTexts = [];
|
|
15650
15665
|
var messages = session.messages || [];
|
|
15651
15666
|
for (var i = messages.length - 1; i >= 0; i--) {
|
|
15652
15667
|
var msg = messages[i];
|
|
@@ -15662,7 +15677,7 @@
|
|
|
15662
15677
|
}
|
|
15663
15678
|
}
|
|
15664
15679
|
|
|
15665
|
-
if (
|
|
15680
|
+
if (msg.role === "user") {
|
|
15666
15681
|
// Skip queued / synthetic placeholder turns — they don't represent
|
|
15667
15682
|
// user-visible "I just asked this" prompts.
|
|
15668
15683
|
var isPlaceholder = msg.content.some(function(b) { return b && b.__queued; });
|
|
@@ -15670,7 +15685,9 @@
|
|
|
15670
15685
|
for (var ui = 0; ui < msg.content.length; ui++) {
|
|
15671
15686
|
var ublock = msg.content[ui];
|
|
15672
15687
|
if (ublock && ublock.type === "text" && ublock.text && ublock.text.trim()) {
|
|
15673
|
-
|
|
15688
|
+
var utext = _compactNotificationText(ublock.text);
|
|
15689
|
+
if (!latestUserText) latestUserText = utext;
|
|
15690
|
+
if (recentUserTexts.length < 4) recentUserTexts.push(utext);
|
|
15674
15691
|
break;
|
|
15675
15692
|
}
|
|
15676
15693
|
}
|
|
@@ -15688,8 +15705,9 @@
|
|
|
15688
15705
|
}
|
|
15689
15706
|
}
|
|
15690
15707
|
|
|
15691
|
-
if (todos &&
|
|
15708
|
+
if (todos && recentUserTexts.length >= 4 && latestAssistantText) break;
|
|
15692
15709
|
}
|
|
15710
|
+
recentUserTexts.reverse();
|
|
15693
15711
|
|
|
15694
15712
|
// Get current task
|
|
15695
15713
|
var currentTask = "";
|
|
@@ -15703,7 +15721,8 @@
|
|
|
15703
15721
|
currentTask: currentTask,
|
|
15704
15722
|
latestUserText: latestUserText,
|
|
15705
15723
|
latestAssistantText: latestAssistantText,
|
|
15706
|
-
todos: todos || []
|
|
15724
|
+
todos: todos || [],
|
|
15725
|
+
recentUserTexts: recentUserTexts
|
|
15707
15726
|
};
|
|
15708
15727
|
|
|
15709
15728
|
try {
|