@wemind/cli 1.2.0 → 1.3.0
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/README.md +9 -7
- package/dist/cli.js +409 -62
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -973,6 +973,8 @@ function normAgentFields(j) {
|
|
|
973
973
|
if (typeof j.bin === "string" && j.bin.trim()) cfg.bin = j.bin.trim();
|
|
974
974
|
if (Array.isArray(j.args)) cfg.args = j.args.filter((x) => typeof x === "string");
|
|
975
975
|
else if (typeof j.args === "string" && j.args.trim()) cfg.args = j.args.trim().split(/\s+/);
|
|
976
|
+
if (Array.isArray(j.extraArgs)) cfg.extraArgs = j.extraArgs.filter((x) => typeof x === "string");
|
|
977
|
+
else if (typeof j.extraArgs === "string" && j.extraArgs.trim()) cfg.extraArgs = j.extraArgs.trim().split(/\s+/);
|
|
976
978
|
if (typeof j.shell === "boolean") cfg.shell = j.shell;
|
|
977
979
|
if (typeof j.homeEnvVar === "string" && j.homeEnvVar.trim()) cfg.homeEnvVar = j.homeEnvVar.trim();
|
|
978
980
|
if (typeof j.model === "string" && j.model.trim()) cfg.model = j.model.trim();
|
|
@@ -984,6 +986,8 @@ function normPresetOverride(j) {
|
|
|
984
986
|
if (typeof j?.bin === "string" && j.bin.trim()) ov.bin = j.bin.trim();
|
|
985
987
|
if (Array.isArray(j?.args)) ov.args = j.args.filter((x) => typeof x === "string");
|
|
986
988
|
else if (typeof j?.args === "string" && j.args.trim()) ov.args = j.args.trim().split(/\s+/);
|
|
989
|
+
if (Array.isArray(j?.extraArgs)) ov.extraArgs = j.extraArgs.filter((x) => typeof x === "string");
|
|
990
|
+
else if (typeof j?.extraArgs === "string" && j.extraArgs.trim()) ov.extraArgs = j.extraArgs.trim().split(/\s+/);
|
|
987
991
|
if (typeof j?.shell === "boolean") ov.shell = j.shell;
|
|
988
992
|
if (typeof j?.homeEnvVar === "string" && j.homeEnvVar.trim()) ov.homeEnvVar = j.homeEnvVar.trim();
|
|
989
993
|
if (typeof j?.model === "string" && j.model.trim()) ov.model = j.model.trim();
|
|
@@ -1053,6 +1057,7 @@ function mergeBrainConfig(existing, patch) {
|
|
|
1053
1057
|
const legacy = {};
|
|
1054
1058
|
if (existing?.bin) legacy.bin = existing.bin;
|
|
1055
1059
|
if (existing?.args?.length) legacy.args = existing.args;
|
|
1060
|
+
if (existing?.extraArgs?.length) legacy.extraArgs = existing.extraArgs;
|
|
1056
1061
|
if (existing?.shell !== void 0) legacy.shell = existing.shell;
|
|
1057
1062
|
if (existing?.homeEnvVar) legacy.homeEnvVar = existing.homeEnvVar;
|
|
1058
1063
|
if (existing?.model) legacy.model = existing.model;
|
|
@@ -1064,20 +1069,23 @@ function mergeBrainConfig(existing, patch) {
|
|
|
1064
1069
|
const next = { agent: patch.agent };
|
|
1065
1070
|
if (patch.bin) next.bin = patch.bin;
|
|
1066
1071
|
if (patch.args) next.args = patch.args;
|
|
1072
|
+
if (patch.extraArgs) next.extraArgs = patch.extraArgs;
|
|
1067
1073
|
if (patch.shell !== void 0) next.shell = patch.shell;
|
|
1068
1074
|
if (patch.homeEnvVar) next.homeEnvVar = patch.homeEnvVar;
|
|
1069
1075
|
if (patch.model) next.model = patch.model;
|
|
1070
1076
|
if (prev && prev === nextAgent) {
|
|
1071
1077
|
if (!patch.bin && existing?.bin) next.bin = existing.bin;
|
|
1072
1078
|
if (!patch.args && existing?.args?.length) next.args = existing.args;
|
|
1079
|
+
if (!patch.extraArgs && existing?.extraArgs?.length) next.extraArgs = existing.extraArgs;
|
|
1073
1080
|
if (patch.shell === void 0 && existing?.shell !== void 0) next.shell = existing.shell;
|
|
1074
1081
|
if (!patch.homeEnvVar && existing?.homeEnvVar) next.homeEnvVar = existing.homeEnvVar;
|
|
1075
1082
|
if (!patch.model && existing?.model) next.model = existing.model;
|
|
1076
1083
|
}
|
|
1077
|
-
if (next.bin || next.args?.length || next.shell !== void 0 || next.homeEnvVar || next.model) {
|
|
1084
|
+
if (next.bin || next.args?.length || next.extraArgs?.length || next.shell !== void 0 || next.homeEnvVar || next.model) {
|
|
1078
1085
|
const ov = { ...presets[nextAgent] };
|
|
1079
1086
|
if (next.bin) ov.bin = next.bin;
|
|
1080
1087
|
if (next.args?.length) ov.args = next.args;
|
|
1088
|
+
if (next.extraArgs?.length) ov.extraArgs = next.extraArgs;
|
|
1081
1089
|
if (next.shell !== void 0) ov.shell = next.shell;
|
|
1082
1090
|
if (next.homeEnvVar) ov.homeEnvVar = next.homeEnvVar;
|
|
1083
1091
|
if (next.model) ov.model = next.model;
|
|
@@ -1104,6 +1112,53 @@ function clearBrainConfig(dir = DATA_DIR) {
|
|
|
1104
1112
|
}
|
|
1105
1113
|
}
|
|
1106
1114
|
//#endregion
|
|
1115
|
+
//#region src/cline-credentials.ts
|
|
1116
|
+
/**
|
|
1117
|
+
* Cline ACP 的认证桥接。
|
|
1118
|
+
*
|
|
1119
|
+
* Cline 的 `isSessionReady()` 只自动恢复它自己的 3 种登录(`cline` / `cline-pass` / `openai-codex`),
|
|
1120
|
+
* 自定义/API-key provider(如 `openai-compatible`)不在 `tryRestoreAuth()` 的列表里——即使
|
|
1121
|
+
* `data/settings/providers.json` 里已经存在 `lastUsedProvider` 和 `apiKey`,`session/new` 也会报
|
|
1122
|
+
* `-32000 Authentication required`。Cline 官方给 ACP 的逃生口是 `CLINE_PROVIDER` + `CLINE_API_KEY`。
|
|
1123
|
+
*
|
|
1124
|
+
* 这里做一层**只读、按需**的桥接:父进程没有显式提供这两个变量时,把 `lastUsedProvider` 对应的
|
|
1125
|
+
* `settings.apiKey` 转成这两个环境变量,从而复用用户已经用 `cline auth` / 手写配置好的登录态。
|
|
1126
|
+
*
|
|
1127
|
+
* 边界(刻意收窄):
|
|
1128
|
+
* - **显式环境变量永远优先**:`CLINE_PROVIDER` 或 `CLINE_API_KEY` 只要有一个已存在,就完全不接管
|
|
1129
|
+
* (避免把父进程的 provider 和配置文件里的 key 错配);
|
|
1130
|
+
* - 只认 `lastUsedProvider`,且该 provider 必须有非空字符串 `apiKey`;OAuth 条目(只有 `auth`、
|
|
1131
|
+
* 没有 `apiKey`)不处理,继续走 Cline 自己的 `tryRestoreAuth()`;
|
|
1132
|
+
* - 文件不存在 / JSON 坏了 / 结构不对 → 返回空表,不抛异常(登录态问题仍由原来的认证提示暴露);
|
|
1133
|
+
* - 只读不写,不打印 key。
|
|
1134
|
+
*/
|
|
1135
|
+
function clineCredentialEnv(realHome, parentEnv) {
|
|
1136
|
+
if (parentEnv.CLINE_PROVIDER?.trim() || parentEnv.CLINE_API_KEY?.trim()) return {};
|
|
1137
|
+
if (!realHome) return {};
|
|
1138
|
+
const file = path.join(realHome, "data", "settings", "providers.json");
|
|
1139
|
+
let parsed;
|
|
1140
|
+
try {
|
|
1141
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
1142
|
+
} catch {
|
|
1143
|
+
return {};
|
|
1144
|
+
}
|
|
1145
|
+
if (!isObj$1(parsed)) return {};
|
|
1146
|
+
const providerId = typeof parsed.lastUsedProvider === "string" ? parsed.lastUsedProvider.trim() : "";
|
|
1147
|
+
if (!providerId) return {};
|
|
1148
|
+
const providers = isObj$1(parsed.providers) ? parsed.providers : null;
|
|
1149
|
+
const entry = providers && isObj$1(providers[providerId]) ? providers[providerId] : null;
|
|
1150
|
+
const settings = entry && isObj$1(entry.settings) ? entry.settings : null;
|
|
1151
|
+
const apiKey = settings && typeof settings.apiKey === "string" ? settings.apiKey.trim() : "";
|
|
1152
|
+
if (!apiKey) return {};
|
|
1153
|
+
return {
|
|
1154
|
+
CLINE_PROVIDER: providerId,
|
|
1155
|
+
CLINE_API_KEY: apiKey
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function isObj$1(v) {
|
|
1159
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
1160
|
+
}
|
|
1161
|
+
//#endregion
|
|
1107
1162
|
//#region src/profile-model.ts
|
|
1108
1163
|
/**
|
|
1109
1164
|
* 真实 profile 用户层(`<realHome>/profiles/<profile>/cordis.patch.yml`)的三件事:
|
|
@@ -1265,7 +1320,12 @@ const ACP_AGENT_PRESETS = {
|
|
|
1265
1320
|
name: "gemini",
|
|
1266
1321
|
bin: "gemini",
|
|
1267
1322
|
binWin: "gemini.cmd",
|
|
1268
|
-
args: [
|
|
1323
|
+
args: [
|
|
1324
|
+
"--acp",
|
|
1325
|
+
"--approval-mode",
|
|
1326
|
+
"yolo",
|
|
1327
|
+
"--skip-trust"
|
|
1328
|
+
],
|
|
1269
1329
|
homeEnvVar: "GEMINI_CLI_HOME",
|
|
1270
1330
|
shell: true
|
|
1271
1331
|
},
|
|
@@ -1306,7 +1366,8 @@ const ACP_AGENT_PRESETS = {
|
|
|
1306
1366
|
images: false,
|
|
1307
1367
|
realProfile: "acp",
|
|
1308
1368
|
model: "[\"deepseek-account\",\"deepseek-flash\"]",
|
|
1309
|
-
mcpViaAcp: true
|
|
1369
|
+
mcpViaAcp: true,
|
|
1370
|
+
envDefaults: { DSH_PERMISSION_MODE: "danger-full-access" }
|
|
1310
1371
|
},
|
|
1311
1372
|
qoder: {
|
|
1312
1373
|
name: "qoder",
|
|
@@ -1331,18 +1392,63 @@ const ACP_AGENT_PRESETS = {
|
|
|
1331
1392
|
credentialFiles: [".auth/user"],
|
|
1332
1393
|
identityFiles: [".auth/machine_id"]
|
|
1333
1394
|
},
|
|
1395
|
+
cline: {
|
|
1396
|
+
name: "cline",
|
|
1397
|
+
bin: "cline",
|
|
1398
|
+
binWin: "cline.cmd",
|
|
1399
|
+
args: [
|
|
1400
|
+
"--acp",
|
|
1401
|
+
"--auto-approve",
|
|
1402
|
+
"true"
|
|
1403
|
+
],
|
|
1404
|
+
shell: true,
|
|
1405
|
+
images: false,
|
|
1406
|
+
env: {
|
|
1407
|
+
CLINE_DIR: `%HOME%${path.sep}cline`,
|
|
1408
|
+
CLINE_DATA_DIR: `%HOME%${path.sep}cline${path.sep}data`,
|
|
1409
|
+
CLINE_MCP_SETTINGS_PATH: `%HOME%${path.sep}cline${path.sep}mcp.json`
|
|
1410
|
+
},
|
|
1411
|
+
realHome: path.join(os.homedir(), ".cline"),
|
|
1412
|
+
inheritDirs: [
|
|
1413
|
+
"rules",
|
|
1414
|
+
"skills",
|
|
1415
|
+
"agents"
|
|
1416
|
+
],
|
|
1417
|
+
configSubdir: "cline",
|
|
1418
|
+
mcpFormat: "kiro",
|
|
1419
|
+
mcpConfigFile: "mcp.json",
|
|
1420
|
+
catalogFile: "data/settings/cline_mcp_settings.json",
|
|
1421
|
+
credentialFiles: ["data/settings/providers.json", "data/settings/models.json"],
|
|
1422
|
+
credentialEnv: clineCredentialEnv
|
|
1423
|
+
},
|
|
1334
1424
|
"claude-code": {
|
|
1335
1425
|
name: "claude-code",
|
|
1336
|
-
bin: "
|
|
1337
|
-
binWin: "
|
|
1338
|
-
args: [
|
|
1339
|
-
shell: true
|
|
1426
|
+
bin: "claude-agent-acp",
|
|
1427
|
+
binWin: "claude-agent-acp.cmd",
|
|
1428
|
+
args: [],
|
|
1429
|
+
shell: true,
|
|
1430
|
+
env: { CLAUDE_CONFIG_DIR: `%HOME%${path.sep}claude` },
|
|
1431
|
+
realHome: path.join(os.homedir(), ".claude"),
|
|
1432
|
+
inheritDirs: [
|
|
1433
|
+
"skills",
|
|
1434
|
+
"agents",
|
|
1435
|
+
"commands"
|
|
1436
|
+
],
|
|
1437
|
+
inheritFiles: ["CLAUDE.md"],
|
|
1438
|
+
configSubdir: "claude",
|
|
1439
|
+
catalogFile: path.join("..", ".claude.json"),
|
|
1440
|
+
credentialFiles: [".credentials.json"],
|
|
1441
|
+
mcpViaAcp: true,
|
|
1442
|
+
sessionMode: "bypassPermissions"
|
|
1340
1443
|
},
|
|
1341
1444
|
codex: {
|
|
1342
1445
|
name: "codex",
|
|
1343
1446
|
bin: "codex-acp",
|
|
1344
|
-
binWin: "codex-acp.
|
|
1345
|
-
args: []
|
|
1447
|
+
binWin: "codex-acp.cmd",
|
|
1448
|
+
args: [],
|
|
1449
|
+
shell: true,
|
|
1450
|
+
envDefaults: { INITIAL_AGENT_MODE: "agent-full-access" },
|
|
1451
|
+
sessionMode: "agent-full-access"
|
|
1346
1452
|
}
|
|
1347
1453
|
};
|
|
1348
1454
|
function onPath(exe) {
|
|
@@ -1351,15 +1457,45 @@ function onPath(exe) {
|
|
|
1351
1457
|
}
|
|
1352
1458
|
/**
|
|
1353
1459
|
* `auto` 的探测顺序:按序取第一个「二进制能在 PATH 上解析到」的 agent。
|
|
1354
|
-
* 只收纳**原生 ACP 且二进制可直接判定**的 agent——适配器类(`claude-code` 走 `
|
|
1460
|
+
* 只收纳**原生 ACP 且二进制可直接判定**的 agent——适配器类(`claude-code` 走 `claude-agent-acp`、`codex` 走 `codex-acp`)不参与自动探测:
|
|
1461
|
+
* 它们是独立安装的桥接进程、各有自己的认证,误选中会让「装了但没登录」的机器静默换脑;显式选用(`wemind brain set|switch <名>`)更安全。
|
|
1462
|
+
* 新 agent 一律追加在末尾:老机器上已装的前几个仍按原顺序被选中,不改变既有用户的默认大脑。
|
|
1355
1463
|
*/
|
|
1356
1464
|
const AUTO_PROBE_ORDER = [
|
|
1357
1465
|
"kiro",
|
|
1358
1466
|
"opencode",
|
|
1359
1467
|
"gemini",
|
|
1360
1468
|
"deepseek",
|
|
1361
|
-
"qoder"
|
|
1469
|
+
"qoder",
|
|
1470
|
+
"cline"
|
|
1362
1471
|
];
|
|
1472
|
+
/**
|
|
1473
|
+
* 该 agent 预设里「进入 ACP 模式」的标记 token:从默认 args 中挑出含 `acp` 的项。
|
|
1474
|
+
* 只用于整段替换 args 时的静态提醒;适配器(claude-code / codex)的 args 为空 → 返回空数组(不检查——适配器本体就是 ACP server,没有模式参数)。
|
|
1475
|
+
* 不能只查裸 `acp`:qoder 是 `--acp`、gemini 是 `--acp`、deepseek 是 `--profile acp`。
|
|
1476
|
+
*/
|
|
1477
|
+
function acpModeTokens(name) {
|
|
1478
|
+
const preset = ACP_AGENT_PRESETS[name];
|
|
1479
|
+
return preset ? preset.args.filter((a) => /acp/i.test(a)) : [];
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* 整段替换 args 时的静态提醒(**只提醒、不拒绝**):新 args 里找不到该 agent 的 ACP 模式标记时返回文案。
|
|
1483
|
+
* `binOverridden`(env / brain.json 指定了自定义 bin)时不检查——自定义入口/包装脚本可能自带 ACP 模式,静态判断不可靠。
|
|
1484
|
+
*/
|
|
1485
|
+
function acpArgsWarning(name, args, binOverridden) {
|
|
1486
|
+
if (binOverridden) return null;
|
|
1487
|
+
const markers = acpModeTokens(name);
|
|
1488
|
+
if (!markers.length) return null;
|
|
1489
|
+
if (args.some((a) => /acp/i.test(a))) return null;
|
|
1490
|
+
return `args 里没有 ${name} 的 ACP 模式参数(预设是 ${markers.map((m) => `\`${m}\``).join(" / ")})——进程可能起得来但不会说 ACP,首条消息会握手超时。请把它一起写回去,或改用 --extra-args 只追加。`;
|
|
1491
|
+
}
|
|
1492
|
+
/** 运行时诊断:握手失败 / 进程在握手前退出时追加的提示;args 看起来正常、或 bin 是自定义入口时返回空串。 */
|
|
1493
|
+
function acpArgsHint(ag) {
|
|
1494
|
+
if (ag.binOverridden) return "";
|
|
1495
|
+
const markers = acpModeTokens(ag.name);
|
|
1496
|
+
if (!markers.length || ag.args.some((a) => /acp/i.test(a))) return "";
|
|
1497
|
+
return `(当前 args=[${ag.args.join(" ")}] 里没有 ${markers.map((m) => `\`${m}\``).join(" / ")}——若覆盖过 --args,确认没漏 ACP 模式参数;也可改用 --extra-args 只追加)`;
|
|
1498
|
+
}
|
|
1363
1499
|
/** agent 在该平台的「可执行文件名」(用于 PATH 探测)。 */
|
|
1364
1500
|
function presetExe(preset) {
|
|
1365
1501
|
return process.platform === "win32" ? preset.binWin ?? `${preset.bin}.exe` : preset.bin;
|
|
@@ -1390,6 +1526,21 @@ function locateBin(bin) {
|
|
|
1390
1526
|
}
|
|
1391
1527
|
return null;
|
|
1392
1528
|
}
|
|
1529
|
+
/** 组装 spawn 用的环境变量:父进程 env → `homeEnvVar` 指向沙箱 → 从凭据文件派生的 env(父进程未提供时)→ 预设强制 `env` → 预设 `envDefaults`(父进程已设同名变量时保留父进程的,`skipped` 收集这些键供调用方记日志)。 */
|
|
1530
|
+
function agentEnv(ag, home, skipped = []) {
|
|
1531
|
+
const env = { ...process.env };
|
|
1532
|
+
if (ag.homeEnvVar) env[ag.homeEnvVar] = home;
|
|
1533
|
+
for (const [k, v] of Object.entries(ag.credentialEnv?.(ag.realHome, process.env) ?? {})) if (env[k] === void 0) env[k] = v;
|
|
1534
|
+
for (const [k, v] of Object.entries(ag.env ?? {})) env[k] = v.replaceAll("%HOME%", home);
|
|
1535
|
+
for (const [k, v] of Object.entries(ag.envDefaults ?? {})) {
|
|
1536
|
+
if (process.env[k] !== void 0) {
|
|
1537
|
+
skipped.push(k);
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
env[k] = v.replaceAll("%HOME%", home);
|
|
1541
|
+
}
|
|
1542
|
+
return env;
|
|
1543
|
+
}
|
|
1393
1544
|
/** 该角色「选 agent」的环境变量:worker 先看 `WEMIND_WORKER_BRAIN`,未设则回落 `WEMIND_BRAIN`。 */
|
|
1394
1545
|
function envKeyFor(role) {
|
|
1395
1546
|
return (role === "worker" ? process.env.WEMIND_WORKER_BRAIN?.trim() || process.env.WEMIND_BRAIN?.trim() : process.env.WEMIND_BRAIN?.trim()) || void 0;
|
|
@@ -1430,7 +1581,10 @@ function resolveAgentCore(role, cfg, opts = {}) {
|
|
|
1430
1581
|
const bin = explicitBin || (shell ? process.platform === "win32" ? exe : preset.bin : binPath || preset.bin);
|
|
1431
1582
|
const available = known && binPath !== null;
|
|
1432
1583
|
const argsOverride = opts.ignoreEnv ? void 0 : process.env.WEMIND_BRAIN_ARGS?.trim();
|
|
1433
|
-
const
|
|
1584
|
+
const baseArgs = argsOverride ? argsOverride.split(/\s+/) : overrideCfg?.args && overrideCfg.args.length ? overrideCfg.args : presetOv?.args && presetOv.args.length ? presetOv.args : preset.args;
|
|
1585
|
+
const extraEnv = opts.ignoreEnv ? void 0 : process.env.WEMIND_BRAIN_EXTRA_ARGS?.trim();
|
|
1586
|
+
const extraArgs = extraEnv ? extraEnv.split(/\s+/) : overrideCfg?.extraArgs && overrideCfg.extraArgs.length ? overrideCfg.extraArgs : presetOv?.extraArgs && presetOv.extraArgs.length ? presetOv.extraArgs : [];
|
|
1587
|
+
const args = [...baseArgs, ...extraArgs];
|
|
1434
1588
|
const homeEnvVar = (opts.ignoreEnv ? void 0 : process.env.WEMIND_BRAIN_HOME_ENV?.trim()) || overrideCfg?.homeEnvVar?.trim() || presetOv?.homeEnvVar?.trim() || preset.homeEnvVar;
|
|
1435
1589
|
const inherit = (process.env.WEMIND_BRAIN_INHERIT ?? process.env.WECHAT_BRAIN_INHERIT ?? "1") !== "0";
|
|
1436
1590
|
const realHome = process.env.WEMIND_BRAIN_REAL_HOME?.trim() || process.env.WECHAT_REAL_KIRO_HOME?.trim() || preset.realHome;
|
|
@@ -1440,6 +1594,13 @@ function resolveAgentCore(role, cfg, opts = {}) {
|
|
|
1440
1594
|
const model = explicitModel || derivedModel || (isDefaultModel(rawModel) ? void 0 : preset.model);
|
|
1441
1595
|
const dirsEnv = process.env.WEMIND_BRAIN_INHERIT_DIRS?.trim();
|
|
1442
1596
|
const inheritDirs = dirsEnv ? dirsEnv.split(/[,\s]+/).filter(Boolean) : preset.inheritDirs ?? [];
|
|
1597
|
+
const modeEnv = process.env.WEMIND_ACP_SESSION_MODE?.trim();
|
|
1598
|
+
const sessionMode = !!modeEnv && [
|
|
1599
|
+
"0",
|
|
1600
|
+
"false",
|
|
1601
|
+
"off",
|
|
1602
|
+
"no"
|
|
1603
|
+
].includes(modeEnv.toLowerCase()) ? void 0 : modeEnv || preset.sessionMode;
|
|
1443
1604
|
return {
|
|
1444
1605
|
name: preset.name,
|
|
1445
1606
|
bin,
|
|
@@ -1455,6 +1616,8 @@ function resolveAgentCore(role, cfg, opts = {}) {
|
|
|
1455
1616
|
mcpConfigFile: preset.mcpConfigFile ?? "settings/mcp.json",
|
|
1456
1617
|
catalogFile: preset.catalogFile ?? "settings/mcp.json",
|
|
1457
1618
|
env: preset.env ?? {},
|
|
1619
|
+
envDefaults: preset.envDefaults ?? {},
|
|
1620
|
+
credentialEnv: preset.credentialEnv,
|
|
1458
1621
|
restore: preset.restore,
|
|
1459
1622
|
firstFrameMs: preset.firstFrameMs,
|
|
1460
1623
|
heartbeatMs: preset.heartbeatMs,
|
|
@@ -1465,11 +1628,13 @@ function resolveAgentCore(role, cfg, opts = {}) {
|
|
|
1465
1628
|
model,
|
|
1466
1629
|
realProfile: preset.realProfile,
|
|
1467
1630
|
mcpViaAcp: preset.mcpViaAcp ?? false,
|
|
1631
|
+
sessionMode,
|
|
1468
1632
|
requestedKey,
|
|
1469
1633
|
source,
|
|
1470
1634
|
known,
|
|
1471
1635
|
available,
|
|
1472
|
-
binPath
|
|
1636
|
+
binPath,
|
|
1637
|
+
binOverridden: !!explicitBin
|
|
1473
1638
|
};
|
|
1474
1639
|
}
|
|
1475
1640
|
/** 解析当前要用的**对话大脑**:环境变量 > 账号 brain.json 顶层 > auto。 */
|
|
@@ -1506,6 +1671,7 @@ function resolvePresetByName(name, cfg) {
|
|
|
1506
1671
|
if (cfg?.agent?.trim().toLowerCase() === key) {
|
|
1507
1672
|
if (cfg.bin) base.bin = cfg.bin;
|
|
1508
1673
|
if (cfg.args?.length) base.args = cfg.args;
|
|
1674
|
+
if (cfg.extraArgs?.length) base.extraArgs = cfg.extraArgs;
|
|
1509
1675
|
if (cfg.shell !== void 0) base.shell = cfg.shell;
|
|
1510
1676
|
if (cfg.homeEnvVar) base.homeEnvVar = cfg.homeEnvVar;
|
|
1511
1677
|
if (cfg.model) base.model = cfg.model;
|
|
@@ -1521,15 +1687,35 @@ function resolvePresetByName(name, cfg) {
|
|
|
1521
1687
|
};
|
|
1522
1688
|
}
|
|
1523
1689
|
/**
|
|
1690
|
+
* 适配器类 agent 的安装指引:找不到适配器二进制时**直接给出可粘贴的命令**。
|
|
1691
|
+
* 口径:**适配器一律全局安装**(daemon 常驻、worker 每条任务起进程;npx 每次冷启动要联网现下、版本不可复现)。
|
|
1692
|
+
*/
|
|
1693
|
+
const ADAPTER_INSTALL_HINT = { "claude-code": "`npm i -g @agentclientprotocol/claude-agent-acp`(装完得到 claude-agent-acp;Windows 上是 claude-agent-acp.cmd;**需 Node ≥22**)" };
|
|
1694
|
+
/**
|
|
1524
1695
|
* 校验解析后的大脑是否可用:可用返回 null,否则返回一句面向用户的说明。
|
|
1525
1696
|
* 供 daemon 启动校验(早失败、说人话)与 `wemind brain` / `wemind brain check` 复用。
|
|
1526
1697
|
*/
|
|
1527
1698
|
function agentProblem(ag) {
|
|
1528
1699
|
if (!ag.known) return `未知 agent「${ag.requestedKey}」。可选:${Object.keys(ACP_AGENT_PRESETS).join("、")}(自定义二进制可用 WEMIND_BRAIN_BIN 或 brain set --bin 指定)`;
|
|
1529
1700
|
if (ag.requestedKey === "auto" && !ag.available) return `自动探测未找到可用的大脑(已试:${AUTO_PROBE_ORDER.join("、")})。请安装其中之一,或运行 \`wemind brain set <agent 名>\``;
|
|
1530
|
-
if (!ag.available)
|
|
1701
|
+
if (!ag.available) {
|
|
1702
|
+
const hint = ADAPTER_INSTALL_HINT[ag.name];
|
|
1703
|
+
if (hint) return `找不到大脑「${ag.name}」的适配器「${ag.bin}」。先装适配器:${hint};装完仍找不到就 \`wemind brain set ${ag.name} --bin "<绝对路径>"\` 指定它。`;
|
|
1704
|
+
return ag.name === "deepseek" ? `找不到 DeepSeek 的「${ag.bin}」。桌面端通常有 dsh.cmd;把它的绝对路径传给 \`wemind brain set deepseek --bin "<...>\\dsh.cmd"\`` : ag.name === "qoder" ? `找不到 Qoder CLI 的「${ag.bin}」。官方安装会放在 ~/.qoder/bin/qodercli/ 并加入 PATH;确认后重开终端,或 \`wemind brain set qoder --bin "<...>\\qodercli.exe"\`` : ag.name === "cline" ? `找不到 Cline CLI 的「${ag.bin}」。安装:\`npm i -g cline\`(Node 20+),装完后重开终端;或用 \`wemind brain set cline --bin "<...>\\cline.cmd"\`` : `找不到大脑「${ag.name}」的二进制「${ag.bin}」。安装它,或运行 \`wemind brain set <agent 名>\` 切换`;
|
|
1705
|
+
}
|
|
1531
1706
|
return null;
|
|
1532
1707
|
}
|
|
1708
|
+
/**
|
|
1709
|
+
* 认证失败时按 agent 补一句可执行的建议(`wemind brain check` 与 daemon 日志复用)。
|
|
1710
|
+
* Cline 的 ACP 只自动恢复它自己的 3 种登录;自定义 provider 必须靠环境变量——
|
|
1711
|
+
* 「重启 daemon」对它没用,所以不能只套用通用的「密钥/鉴权」提示。
|
|
1712
|
+
* 其它 agent 返回空串(不改变既有文案)。
|
|
1713
|
+
*/
|
|
1714
|
+
function authHint(agentName, message) {
|
|
1715
|
+
if (agentName !== "cline") return "";
|
|
1716
|
+
if (!/authentication required|authenticate|unauthorized|not authenticated/i.test(message)) return "";
|
|
1717
|
+
return "Cline 的 ACP 只自动恢复 cline / cline-pass / openai-codex 三种登录;用自定义 provider(如 openai-compatible)时,请在启动 wemind 的环境里设置 CLINE_PROVIDER 与 CLINE_API_KEY(设置后重启一次 daemon)。";
|
|
1718
|
+
}
|
|
1533
1719
|
//#endregion
|
|
1534
1720
|
//#region src/agent-slot.ts
|
|
1535
1721
|
/**
|
|
@@ -1814,6 +2000,7 @@ function applySandbox(ag) {
|
|
|
1814
2000
|
for (const n of active) servers[n] = cat[n];
|
|
1815
2001
|
const file = brainMcpJson(ag);
|
|
1816
2002
|
const next = withMcpServers(readJson$1(file) ?? {}, servers, ag.mcpFormat);
|
|
2003
|
+
if (ag.mcpFormat === "opencode" && next.permission === void 0) next.permission = "allow";
|
|
1817
2004
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1818
2005
|
fs.writeFileSync(file, JSON.stringify(next, null, 2), "utf-8");
|
|
1819
2006
|
}
|
|
@@ -3043,9 +3230,9 @@ function promptRequest(ag, params) {
|
|
|
3043
3230
|
async function ensureProcess(ag) {
|
|
3044
3231
|
if (isAlive() && initialized) return;
|
|
3045
3232
|
reset();
|
|
3046
|
-
const
|
|
3047
|
-
|
|
3048
|
-
|
|
3233
|
+
const kept = [];
|
|
3234
|
+
const env = agentEnv(ag, BRAIN_HOME, kept);
|
|
3235
|
+
if (kept.length) dlog$1(`env defaults kept from parent: ${kept.join(",")}`);
|
|
3049
3236
|
const proc = spawnAgent(ag, {
|
|
3050
3237
|
cwd: BRAIN_HOME,
|
|
3051
3238
|
env
|
|
@@ -3056,17 +3243,22 @@ async function ensureProcess(ag) {
|
|
|
3056
3243
|
proc.stdout.on("data", onStdout);
|
|
3057
3244
|
proc.stderr.on("data", () => {});
|
|
3058
3245
|
proc.on("exit", (code) => {
|
|
3059
|
-
dlog$1(`acp exited code=${code}`);
|
|
3246
|
+
dlog$1(`acp exited code=${code}${child === proc && !initialized ? acpArgsHint(ag) : ""}`);
|
|
3060
3247
|
if (child === proc) reset();
|
|
3061
3248
|
});
|
|
3062
3249
|
proc.on("error", (e) => {
|
|
3063
3250
|
dlog$1(`acp error ${String(e)}`);
|
|
3064
3251
|
if (child === proc) reset();
|
|
3065
3252
|
});
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3253
|
+
try {
|
|
3254
|
+
await request("initialize", {
|
|
3255
|
+
protocolVersion: 1,
|
|
3256
|
+
clientCapabilities: {}
|
|
3257
|
+
}, REQ_TIMEOUT$1);
|
|
3258
|
+
} catch (e) {
|
|
3259
|
+
dlog$1(`initialize failed: ${String(e)}${acpArgsHint(ag)}`);
|
|
3260
|
+
throw e;
|
|
3261
|
+
}
|
|
3070
3262
|
initialized = true;
|
|
3071
3263
|
dlog$1("initialized");
|
|
3072
3264
|
}
|
|
@@ -3085,6 +3277,32 @@ async function applyModel(ag, sid) {
|
|
|
3085
3277
|
throw e;
|
|
3086
3278
|
}
|
|
3087
3279
|
}
|
|
3280
|
+
/** 从 `session/new|load|resume` 的回包里取可用模式 id;agent 不报 `modes` 时返回 null(=不校验,照样尝试)。 */
|
|
3281
|
+
function availableModeIds(res) {
|
|
3282
|
+
const list = res?.modes?.availableModes;
|
|
3283
|
+
if (!Array.isArray(list)) return null;
|
|
3284
|
+
const ids = list.map((m) => String(m?.id ?? m)).filter(Boolean);
|
|
3285
|
+
return ids.length ? ids : null;
|
|
3286
|
+
}
|
|
3287
|
+
/** 会话建立/恢复后切到预设声明的模式(免审批:claude-code 的 bypassPermissions、codex 的 agent-full-access)。
|
|
3288
|
+
* **失败只记日志、不抛**:模式不可用(如 claude-code 以 root 跑却没有 IS_SANDBOX)时进程仍可用,
|
|
3289
|
+
* 审批请求继续由应答器处理(默认拒绝,WEMIND_ACP_AUTO_ALLOW=1 放行);抛错反而会把整轮打成失败。 */
|
|
3290
|
+
async function applySessionMode(ag, sid, modes = null) {
|
|
3291
|
+
if (!ag.sessionMode) return;
|
|
3292
|
+
if (modes && !modes.includes(ag.sessionMode)) {
|
|
3293
|
+
dlog$1(`session mode ${ag.sessionMode} 不在 agent 报告的可用模式 [${modes.join(",")}] 里——跳过;审批请求仍由应答器处理`);
|
|
3294
|
+
return;
|
|
3295
|
+
}
|
|
3296
|
+
try {
|
|
3297
|
+
await request("session/set_mode", {
|
|
3298
|
+
sessionId: sid,
|
|
3299
|
+
modeId: ag.sessionMode
|
|
3300
|
+
}, REQ_TIMEOUT$1);
|
|
3301
|
+
dlog$1(`session mode set ${ag.sessionMode}`);
|
|
3302
|
+
} catch (e) {
|
|
3303
|
+
dlog$1(`session mode set failed (${String(e)})——继续;审批请求仍由应答器处理`);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3088
3306
|
async function ensureSession(ag, userId) {
|
|
3089
3307
|
const existing = userSessions.get(userId);
|
|
3090
3308
|
if (existing) return existing;
|
|
@@ -3094,12 +3312,13 @@ async function ensureSession(ag, userId) {
|
|
|
3094
3312
|
if (saved) {
|
|
3095
3313
|
const methods = ag.restore === "resume" ? ["resume"] : ["load", "resume"];
|
|
3096
3314
|
for (const method of methods) try {
|
|
3097
|
-
await request(`session/${method}`, {
|
|
3315
|
+
const restored = await request(`session/${method}`, {
|
|
3098
3316
|
sessionId: saved,
|
|
3099
3317
|
cwd: BRAIN_HOME,
|
|
3100
3318
|
mcpServers
|
|
3101
3319
|
}, REQ_TIMEOUT$1);
|
|
3102
3320
|
await applyModel(ag, saved);
|
|
3321
|
+
await applySessionMode(ag, saved, availableModeIds(restored));
|
|
3103
3322
|
userSessions.set(userId, saved);
|
|
3104
3323
|
markUsed(ag.name, userId, saved);
|
|
3105
3324
|
dlog$1(`session/${method} ok ${saved} mcp=[${mcpServers.map((s) => s.name).join(",")}]`);
|
|
@@ -3114,6 +3333,7 @@ async function ensureSession(ag, userId) {
|
|
|
3114
3333
|
}, REQ_TIMEOUT$1);
|
|
3115
3334
|
if (!res?.sessionId) throw new Error("session/new returned no sessionId");
|
|
3116
3335
|
await applyModel(ag, res.sessionId);
|
|
3336
|
+
await applySessionMode(ag, res.sessionId, availableModeIds(res));
|
|
3117
3337
|
userSessions.set(userId, res.sessionId);
|
|
3118
3338
|
map[keyOf(ag, userId)] = res.sessionId;
|
|
3119
3339
|
saveSessionMap$1(map);
|
|
@@ -3216,12 +3436,13 @@ async function resumeSession(userId, sessionId) {
|
|
|
3216
3436
|
const methods = ag.restore === "resume" ? ["resume"] : ["load", "resume"];
|
|
3217
3437
|
let lastErr = "";
|
|
3218
3438
|
for (const method of methods) try {
|
|
3219
|
-
await request(`session/${method}`, {
|
|
3439
|
+
const restored = await request(`session/${method}`, {
|
|
3220
3440
|
sessionId,
|
|
3221
3441
|
cwd: BRAIN_HOME,
|
|
3222
3442
|
mcpServers
|
|
3223
3443
|
}, REQ_TIMEOUT$1);
|
|
3224
3444
|
await applyModel(ag, sessionId);
|
|
3445
|
+
await applySessionMode(ag, sessionId, availableModeIds(restored));
|
|
3225
3446
|
userSessions.set(userId, sessionId);
|
|
3226
3447
|
const map = loadSessionMap$1(ag);
|
|
3227
3448
|
map[keyOf(ag, userId)] = sessionId;
|
|
@@ -3263,6 +3484,55 @@ function heartbeatMs() {
|
|
|
3263
3484
|
return getAgent("brain").heartbeatMs;
|
|
3264
3485
|
}
|
|
3265
3486
|
//#endregion
|
|
3487
|
+
//#region src/brain-state.ts
|
|
3488
|
+
/**
|
|
3489
|
+
* daemon 启动/热切换成功后,把「这一刻真正生效的对话大脑」写进 `<账号目录>/brain-runtime.json`。
|
|
3490
|
+
*
|
|
3491
|
+
* 为什么还需要这个文件:`brain.pid` 只在 **ACP 子进程真的 spawn 之后**才有内容,而大脑是懒启动的——
|
|
3492
|
+
* 刚启动、还没收到消息时没有 pid 记录。此前 `wemind list` 的 `agent=` 只能显示 `brain.json`(有意「不看
|
|
3493
|
+
* 当前 shell 的环境变量」),于是用 `WEMIND_BRAIN=cline` 启动的 daemon 会一直显示成配置里的 deepseek,
|
|
3494
|
+
* 让人以为切换没生效。
|
|
3495
|
+
*
|
|
3496
|
+
* 这个文件记录的是 **daemon 进程自己的解析结果**(含来源 env/file/auto),与 CLI 端环境变量无关;
|
|
3497
|
+
* daemon 没运行时 `manage.ts` 不会采信它(`stop` 也会清掉),所以不会把旧状态当成现在时。
|
|
3498
|
+
*/
|
|
3499
|
+
const BRAIN_RUNTIME_FILE = "brain-runtime.json";
|
|
3500
|
+
/** 写入运行时大脑(原子替换;失败静默——显示用,不该影响收发)。 */
|
|
3501
|
+
function writeBrainState(dir, ag) {
|
|
3502
|
+
const body = {
|
|
3503
|
+
agent: ag.name,
|
|
3504
|
+
source: ag.source,
|
|
3505
|
+
bin: ag.bin,
|
|
3506
|
+
args: ag.args,
|
|
3507
|
+
startedAt: Date.now()
|
|
3508
|
+
};
|
|
3509
|
+
try {
|
|
3510
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3511
|
+
const file = path.join(dir, BRAIN_RUNTIME_FILE);
|
|
3512
|
+
const tmp = `${file}.tmp`;
|
|
3513
|
+
fs.writeFileSync(tmp, JSON.stringify(body, null, 2) + "\n", "utf-8");
|
|
3514
|
+
fs.renameSync(tmp, file);
|
|
3515
|
+
} catch {}
|
|
3516
|
+
}
|
|
3517
|
+
/** 读运行时大脑;没有/损坏/字段不对 → null(调用方按「没有运行时信息」处理)。 */
|
|
3518
|
+
function readBrainState(dir) {
|
|
3519
|
+
try {
|
|
3520
|
+
const j = JSON.parse(fs.readFileSync(path.join(dir, BRAIN_RUNTIME_FILE), "utf-8"));
|
|
3521
|
+
if (typeof j?.agent !== "string" || !j.agent) return null;
|
|
3522
|
+
const src = j.source;
|
|
3523
|
+
const source = src === "env" || src === "auto" ? src : src === "file" ? "file" : "file";
|
|
3524
|
+
return {
|
|
3525
|
+
agent: j.agent,
|
|
3526
|
+
source,
|
|
3527
|
+
bin: typeof j.bin === "string" ? j.bin : "",
|
|
3528
|
+
args: Array.isArray(j.args) ? j.args.filter((x) => typeof x === "string") : [],
|
|
3529
|
+
startedAt: Number(j.startedAt) || 0
|
|
3530
|
+
};
|
|
3531
|
+
} catch {
|
|
3532
|
+
return null;
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
//#endregion
|
|
3266
3536
|
//#region src/brain-switch.ts
|
|
3267
3537
|
/**
|
|
3268
3538
|
* 运行时切换对话大脑。
|
|
@@ -3375,6 +3645,7 @@ async function runSwitch(key, opts) {
|
|
|
3375
3645
|
const note = envNote(key);
|
|
3376
3646
|
if (pre.already) {
|
|
3377
3647
|
if (opts.persist !== false) updateBrainConfig({ agent: key }, dir);
|
|
3648
|
+
writeBrainState(dir, target);
|
|
3378
3649
|
return {
|
|
3379
3650
|
ok: true,
|
|
3380
3651
|
agent: target,
|
|
@@ -3396,6 +3667,7 @@ async function runSwitch(key, opts) {
|
|
|
3396
3667
|
await withTimeout(warmup(target, opts.userId ?? ""), TIMEOUT_MS, warmupTimeoutMsg());
|
|
3397
3668
|
setAgent("brain", target);
|
|
3398
3669
|
if (opts.persist !== false) updateBrainConfig({ agent: key }, dir);
|
|
3670
|
+
writeBrainState(dir, target);
|
|
3399
3671
|
const ms = Date.now() - t0;
|
|
3400
3672
|
return {
|
|
3401
3673
|
ok: true,
|
|
@@ -4231,6 +4503,7 @@ function applyWorkerSandbox(ag) {
|
|
|
4231
4503
|
}
|
|
4232
4504
|
const file = workerMcpJson(ag);
|
|
4233
4505
|
const next = withMcpServers(readJson(file) ?? {}, servers, ag.mcpFormat);
|
|
4506
|
+
if (ag.mcpFormat === "opencode" && next.permission === void 0) next.permission = "allow";
|
|
4234
4507
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
4235
4508
|
fs.writeFileSync(file, JSON.stringify(next, null, 2), "utf-8");
|
|
4236
4509
|
}
|
|
@@ -4435,7 +4708,7 @@ function saveSessionMap(m) {
|
|
|
4435
4708
|
fs.renameSync(tmp, SESSION_MAP_FILE);
|
|
4436
4709
|
}
|
|
4437
4710
|
/** 一条连接:一个 ACP agent 进程 + 它自己的握手/会话/进度状态。 */
|
|
4438
|
-
var WorkerConn = class {
|
|
4711
|
+
var WorkerConn = class WorkerConn {
|
|
4439
4712
|
id;
|
|
4440
4713
|
agent;
|
|
4441
4714
|
/** 池用来标注「这条连接现在跑的是哪个任务」(空闲时为 undefined)。 */
|
|
@@ -4724,9 +4997,9 @@ var WorkerConn = class {
|
|
|
4724
4997
|
async ensureProcess() {
|
|
4725
4998
|
if (this.isAlive() && this.initialized) return;
|
|
4726
4999
|
this.kill();
|
|
4727
|
-
const
|
|
4728
|
-
|
|
4729
|
-
|
|
5000
|
+
const kept = [];
|
|
5001
|
+
const env = agentEnv(this.agent, WORKER_HOME, kept);
|
|
5002
|
+
if (kept.length) dlog(`${this.id} env defaults kept from parent: ${kept.join(",")}`);
|
|
4730
5003
|
const cwd = this.effectiveCwd();
|
|
4731
5004
|
const proc = spawnAgent(this.agent, {
|
|
4732
5005
|
cwd,
|
|
@@ -4738,17 +5011,23 @@ var WorkerConn = class {
|
|
|
4738
5011
|
proc.stdout.on("data", (d) => this.onStdout(d));
|
|
4739
5012
|
proc.stderr.on("data", () => {});
|
|
4740
5013
|
proc.on("exit", (code) => {
|
|
4741
|
-
|
|
5014
|
+
const hint = this.child === proc && !this.initialized ? acpArgsHint(this.agent) : "";
|
|
5015
|
+
dlog(`${this.id}(${this.agent.name}) exited code=${code}${hint}`);
|
|
4742
5016
|
if (this.child === proc) this.kill();
|
|
4743
5017
|
});
|
|
4744
5018
|
proc.on("error", (e) => {
|
|
4745
5019
|
dlog(`${this.id}(${this.agent.name}) error ${String(e)}`);
|
|
4746
5020
|
if (this.child === proc) this.kill();
|
|
4747
5021
|
});
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
5022
|
+
try {
|
|
5023
|
+
await this.request("initialize", {
|
|
5024
|
+
protocolVersion: 1,
|
|
5025
|
+
clientCapabilities: {}
|
|
5026
|
+
}, REQ_TIMEOUT);
|
|
5027
|
+
} catch (e) {
|
|
5028
|
+
dlog(`${this.id}(${this.agent.name}) initialize failed: ${String(e)}${acpArgsHint(this.agent)}`);
|
|
5029
|
+
throw e;
|
|
5030
|
+
}
|
|
4752
5031
|
this.initialized = true;
|
|
4753
5032
|
dlog(`${this.id} initialized`);
|
|
4754
5033
|
}
|
|
@@ -4766,6 +5045,30 @@ var WorkerConn = class {
|
|
|
4766
5045
|
throw e;
|
|
4767
5046
|
}
|
|
4768
5047
|
}
|
|
5048
|
+
/** 从 `session/new|load|resume` 的回包里取可用模式 id;agent 不报 `modes` 时返回 null(=不校验,照样尝试)。 */
|
|
5049
|
+
static availableModeIds(res) {
|
|
5050
|
+
const list = res?.modes?.availableModes;
|
|
5051
|
+
if (!Array.isArray(list)) return null;
|
|
5052
|
+
const ids = list.map((m) => String(m?.id ?? m)).filter(Boolean);
|
|
5053
|
+
return ids.length ? ids : null;
|
|
5054
|
+
}
|
|
5055
|
+
/** 会话建立/恢复后切到预设声明的模式(免审批);失败只记日志、不抛——审批请求仍由应答器兜底。 */
|
|
5056
|
+
async applySessionMode(sid, modes = null) {
|
|
5057
|
+
if (!this.agent.sessionMode) return;
|
|
5058
|
+
if (modes && !modes.includes(this.agent.sessionMode)) {
|
|
5059
|
+
dlog(`${this.id} session mode ${this.agent.sessionMode} 不在可用模式 [${modes.join(",")}] 里——跳过;审批请求仍由应答器处理`);
|
|
5060
|
+
return;
|
|
5061
|
+
}
|
|
5062
|
+
try {
|
|
5063
|
+
await this.request("session/set_mode", {
|
|
5064
|
+
sessionId: sid,
|
|
5065
|
+
modeId: this.agent.sessionMode
|
|
5066
|
+
}, REQ_TIMEOUT);
|
|
5067
|
+
dlog(`${this.id} session mode set ${this.agent.sessionMode}`);
|
|
5068
|
+
} catch (e) {
|
|
5069
|
+
dlog(`${this.id} session mode set failed (${String(e)})——继续;审批请求仍由应答器处理`);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
4769
5072
|
/** 取会话:内存 → 磁盘(load/resume 恢复) → 新建。key 由调用方给(每任务一个)。
|
|
4770
5073
|
* `strictResume`:有旧会话但恢复失败 → 抛「接不上」,绝不静默新建空会话。 */
|
|
4771
5074
|
async ensureSession(key, strictResume = false) {
|
|
@@ -4777,12 +5080,13 @@ var WorkerConn = class {
|
|
|
4777
5080
|
if (saved) {
|
|
4778
5081
|
const methods = this.agent.restore === "resume" ? ["resume"] : ["load", "resume"];
|
|
4779
5082
|
for (const method of methods) try {
|
|
4780
|
-
await this.request(`session/${method}`, {
|
|
5083
|
+
const restored = await this.request(`session/${method}`, {
|
|
4781
5084
|
sessionId: saved,
|
|
4782
5085
|
cwd: this.effectiveCwd(),
|
|
4783
5086
|
mcpServers
|
|
4784
5087
|
}, REQ_TIMEOUT);
|
|
4785
5088
|
await this.applyModel(saved);
|
|
5089
|
+
await this.applySessionMode(saved, WorkerConn.availableModeIds(restored));
|
|
4786
5090
|
this.sessions.set(key, saved);
|
|
4787
5091
|
dlog(`${this.id} session/${method} ok ${saved} mcp=[${mcpServers.map((s) => s.name).join(",")}]`);
|
|
4788
5092
|
return saved;
|
|
@@ -4797,6 +5101,7 @@ var WorkerConn = class {
|
|
|
4797
5101
|
}, REQ_TIMEOUT);
|
|
4798
5102
|
if (!res?.sessionId) throw new Error("session/new returned no sessionId");
|
|
4799
5103
|
await this.applyModel(res.sessionId);
|
|
5104
|
+
await this.applySessionMode(res.sessionId, WorkerConn.availableModeIds(res));
|
|
4800
5105
|
this.sessions.set(key, res.sessionId);
|
|
4801
5106
|
const fresh = loadSessionMap(this.agent);
|
|
4802
5107
|
fresh[mapKey] = res.sessionId;
|
|
@@ -5730,10 +6035,13 @@ function brainFailurePlan(e) {
|
|
|
5730
6035
|
category: cat
|
|
5731
6036
|
};
|
|
5732
6037
|
}
|
|
5733
|
-
/**
|
|
5734
|
-
function brainErrorMessage(e) {
|
|
6038
|
+
/** 按错误类型给用户的失败提示。`agentName` 用于给特定 agent 补专属建议(如 Cline 的自定义 provider 要设环境变量)。 */
|
|
6039
|
+
function brainErrorMessage(e, agentName) {
|
|
5735
6040
|
const prov = providerIssue(e);
|
|
5736
|
-
if (prov)
|
|
6041
|
+
if (prov) {
|
|
6042
|
+
const auth = agentName ? authHint(agentName, String(e instanceof Error ? e.message : e)) : "";
|
|
6043
|
+
return `(这次没答上来:${prov}。这类问题重试不会好——把账号/模型处理好后把消息再发一次就行。${auth ? ` ${auth}` : prov === "密钥/鉴权有问题" || prov === "模型不可用" ? "若刚在本机改过登录态或模型配置,还要重启一次 daemon(wemind stop && wemind start)才会读到。" : ""})`;
|
|
6044
|
+
}
|
|
5737
6045
|
switch (classifyBrainError(e)) {
|
|
5738
6046
|
case "backend": return "(模型后端临时出错了,自动重试多次仍失败,请稍后把消息再发一次)";
|
|
5739
6047
|
case "timeout": return "(这次处理超时/卡住被中断了,重试也没成功——可把任务拆小一点再发)";
|
|
@@ -6221,7 +6529,7 @@ async function handleMessage(account, ctx, to, text, imageItems, createTimeMs, l
|
|
|
6221
6529
|
answer = await runWithRetry(text, images);
|
|
6222
6530
|
} catch (e2) {
|
|
6223
6531
|
ok = false;
|
|
6224
|
-
answer = brainErrorMessage(e2);
|
|
6532
|
+
answer = brainErrorMessage(e2, getAgent("brain").name);
|
|
6225
6533
|
} finally {
|
|
6226
6534
|
clearInterval(keepAlive);
|
|
6227
6535
|
clearInterval(heartbeat);
|
|
@@ -6624,6 +6932,7 @@ async function runDaemon() {
|
|
|
6624
6932
|
process.exit(78);
|
|
6625
6933
|
}
|
|
6626
6934
|
log(`brain=${agent.name} bin=${agent.bin} args=[${agent.args.join(" ")}] shell=${agent.shell} (source: ${agent.source})`);
|
|
6935
|
+
writeBrainState(DATA_DIR, agent);
|
|
6627
6936
|
const wagent = getAgent("worker");
|
|
6628
6937
|
if (wagent.name !== agent.name) log(`worker=${wagent.name} bin=${wagent.bin} args=[${wagent.args.join(" ")}] (source: ${wagent.source})`);
|
|
6629
6938
|
const workerProblem = agentProblem(wagent);
|
|
@@ -6914,9 +7223,7 @@ function watchdogStart(key, cliPath) {
|
|
|
6914
7223
|
async function checkAgent(ag, timeoutMs = 6e4) {
|
|
6915
7224
|
const started = Date.now();
|
|
6916
7225
|
const home = fs.mkdtempSync(path.join(os.tmpdir(), "wemind-brain-check-"));
|
|
6917
|
-
const env =
|
|
6918
|
-
if (ag.homeEnvVar) env[ag.homeEnvVar] = home;
|
|
6919
|
-
for (const [k, v] of Object.entries(ag.env ?? {})) env[k] = v.replaceAll("%HOME%", home);
|
|
7226
|
+
const env = agentEnv(ag, home);
|
|
6920
7227
|
const cfgDir = path.join(home, ag.configSubdir);
|
|
6921
7228
|
for (const f of [...ag.credentialFiles, ...ag.identityFiles]) try {
|
|
6922
7229
|
const src = path.join(ag.realHome ?? "", f);
|
|
@@ -6994,22 +7301,35 @@ async function checkAgent(ag, timeoutMs = 6e4) {
|
|
|
6994
7301
|
protocolVersion: 1,
|
|
6995
7302
|
clientCapabilities: {}
|
|
6996
7303
|
});
|
|
7304
|
+
const s = await req("session/new", {
|
|
7305
|
+
cwd: home,
|
|
7306
|
+
mcpServers: []
|
|
7307
|
+
});
|
|
7308
|
+
let modeNote = "";
|
|
7309
|
+
if (ag.sessionMode && s?.sessionId) try {
|
|
7310
|
+
await req("session/set_mode", {
|
|
7311
|
+
sessionId: s.sessionId,
|
|
7312
|
+
modeId: ag.sessionMode
|
|
7313
|
+
});
|
|
7314
|
+
modeNote = `,mode=${ag.sessionMode}`;
|
|
7315
|
+
} catch (e) {
|
|
7316
|
+
modeNote = `,但 session/set_mode ${ag.sessionMode} 失败(${e instanceof Error ? e.message : String(e)})——免审批模式可能不可用,回合仍可跑但会按 agent 自己的审批流程走`;
|
|
7317
|
+
}
|
|
6997
7318
|
return {
|
|
6998
7319
|
ok: true,
|
|
6999
|
-
detail: `ACP 握手成功(initialize + session/new,sessionId=${
|
|
7000
|
-
cwd: home,
|
|
7001
|
-
mcpServers: []
|
|
7002
|
-
}))?.sessionId ?? "?"})`,
|
|
7320
|
+
detail: `ACP 握手成功(initialize + session/new,sessionId=${s?.sessionId ?? "?"}${modeNote})`,
|
|
7003
7321
|
ms: Date.now() - started
|
|
7004
7322
|
};
|
|
7005
7323
|
} catch (e) {
|
|
7006
7324
|
const all = stderrTail.join("").trim();
|
|
7007
7325
|
const lines = all.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
7008
7326
|
const head = lines.find((l) => /^(Error|YAMLException)\b/.test(l)) ?? lines.find((l) => /failed to parse|YAMLException/i.test(l)) ?? lines[0];
|
|
7327
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7328
|
+
const hint = authHint(ag.name, msg);
|
|
7009
7329
|
const tail = all.slice(-400);
|
|
7010
7330
|
return {
|
|
7011
7331
|
ok: false,
|
|
7012
|
-
detail: `${
|
|
7332
|
+
detail: `${msg}${head ? `\n stderr: ${head}` : ""}${tail && !head?.includes(tail.trim()) ? `\n …${tail}` : ""}${hint ? `\n 提示:${hint}` : ""}`,
|
|
7013
7333
|
ms: Date.now() - started
|
|
7014
7334
|
};
|
|
7015
7335
|
} finally {
|
|
@@ -7284,7 +7604,8 @@ function stopDaemon(key) {
|
|
|
7284
7604
|
"daemon.pid",
|
|
7285
7605
|
"brain.pid",
|
|
7286
7606
|
"worker.pid",
|
|
7287
|
-
"workers.pid.json"
|
|
7607
|
+
"workers.pid.json",
|
|
7608
|
+
BRAIN_RUNTIME_FILE
|
|
7288
7609
|
]) try {
|
|
7289
7610
|
fs.unlinkSync(path.join(dir, f));
|
|
7290
7611
|
} catch {}
|
|
@@ -7442,6 +7763,7 @@ function cmdList() {
|
|
|
7442
7763
|
bpid: brec.pid ?? readPid(dir, "brain.pid"),
|
|
7443
7764
|
brainMarkers: brec.markers ?? brainPidMarkers(ag.bin, ag.args),
|
|
7444
7765
|
brainAgent: brec.agent,
|
|
7766
|
+
brainRuntime: readBrainState(dir),
|
|
7445
7767
|
workers: readPidRecords(path.join(dir, "workers.pid.json")).map((w) => ({
|
|
7446
7768
|
pid: w.pid,
|
|
7447
7769
|
agent: w.agent,
|
|
@@ -7458,11 +7780,12 @@ function cmdList() {
|
|
|
7458
7780
|
]));
|
|
7459
7781
|
const aliveOursIn = (pid, markers) => !!pid && matchesMarkers(lines.get(pid), markers);
|
|
7460
7782
|
return entries.map((e) => {
|
|
7461
|
-
const
|
|
7783
|
+
const runningNow = aliveOursIn(e.dpid, CLI_PID_MARKERS);
|
|
7784
|
+
const running = runningNow ? `运行中(pid ${e.dpid})` : "已停止";
|
|
7462
7785
|
const brainPid = aliveOursIn(e.bpid, e.brainMarkers) ? ` brain(pid ${e.bpid}${e.brainAgent ? ` ${e.brainAgent}` : ""})` : "";
|
|
7463
7786
|
const live = e.workers.filter((w) => aliveOursIn(w.pid, w.markers));
|
|
7464
7787
|
const pool = live.length ? ` pool[${live.map((w) => `${w.agent}${w.taskId ? `#${w.taskId}` : ""}:${w.state}`).join(", ")}]` : "";
|
|
7465
|
-
return `- ${e.a.key}${e.a.key === def ? " [默认]" : ""} label=${e.a.label} botId=${e.a.botId} agent=${agentLabel(e.dir)} ${running}${brainPid}${pool}`;
|
|
7788
|
+
return `- ${e.a.key}${e.a.key === def ? " [默认]" : ""} label=${e.a.label} botId=${e.a.botId} agent=${agentLabel(e.dir, runningNow ? e.brainRuntime : null)} ${running}${brainPid}${pool}`;
|
|
7466
7789
|
}).join("\n");
|
|
7467
7790
|
}
|
|
7468
7791
|
const BRAIN_SRC_LABEL = {
|
|
@@ -7471,32 +7794,46 @@ const BRAIN_SRC_LABEL = {
|
|
|
7471
7794
|
auto: "自动探测(未配置)"
|
|
7472
7795
|
};
|
|
7473
7796
|
/** list 用:账号「配置 / 自动探测」得到的大脑标签(不看当前 shell 的 env,避免误导)。 */
|
|
7474
|
-
function
|
|
7797
|
+
function configAgentLabel(dir) {
|
|
7475
7798
|
const cfg = loadBrainConfig(dir);
|
|
7476
7799
|
if (cfg?.agent && cfg.agent.toLowerCase() !== "auto") return cfg.agent;
|
|
7477
7800
|
const picked = AUTO_PROBE_ORDER.find((k) => presetAvailable(k));
|
|
7478
7801
|
return picked ? `${picked}(自动)` : "(无可用)";
|
|
7479
7802
|
}
|
|
7803
|
+
/**
|
|
7804
|
+
* list 用的 agent 列:daemon **在跑**时优先显示它实际生效的大脑(`brain-runtime.json`,
|
|
7805
|
+
* 来自 daemon 自己的解析,含 `WEMIND_BRAIN` 覆盖/热切换结果);daemon 没跑时仍只显示配置/自动探测。
|
|
7806
|
+
* 运行时与配置不一致时把两者都写出来,避免「看着是 deepseek、实际在跑 cline」。
|
|
7807
|
+
*/
|
|
7808
|
+
function agentLabel(dir, runtime = null) {
|
|
7809
|
+
const cfg = configAgentLabel(dir);
|
|
7810
|
+
if (!runtime?.agent?.trim()) return cfg;
|
|
7811
|
+
const name = runtime.agent.trim();
|
|
7812
|
+
if (name === cfg.replace(/([^)]*)$/, "")) return cfg;
|
|
7813
|
+
return `${name}(运行,来源 ${runtime.source};配置 ${cfg})`;
|
|
7814
|
+
}
|
|
7480
7815
|
/** `wemind brain`(show):打印某账号大脑的「配置」与「生效」情况。 */
|
|
7481
7816
|
function describeBrain(key) {
|
|
7482
7817
|
const cfg = loadBrainConfig(accountDir(key));
|
|
7483
7818
|
const ag = resolveAgent(cfg);
|
|
7484
7819
|
const problem = agentProblem(ag);
|
|
7485
|
-
const cfgLine = cfg ? `brain.json → agent=${cfg.agent}${cfg.bin ? ` bin=${cfg.bin}` : ""}${cfg.args ? ` args=[${cfg.args.join(" ")}]` : ""}${cfg.shell !== void 0 ? ` shell=${cfg.shell}` : ""}${cfg.model ? ` model=${cfg.model}` : ""}` : "(未设置 brain.json)";
|
|
7820
|
+
const cfgLine = cfg ? `brain.json → agent=${cfg.agent}${cfg.bin ? ` bin=${cfg.bin}` : ""}${cfg.args ? ` args=[${cfg.args.join(" ")}]` : ""}${cfg.extraArgs ? ` extra_args=[${cfg.extraArgs.join(" ")}]` : ""}${cfg.shell !== void 0 ? ` shell=${cfg.shell}` : ""}${cfg.model ? ` model=${cfg.model}` : ""}` : "(未设置 brain.json)";
|
|
7486
7821
|
const lines = [
|
|
7487
7822
|
`账号 ${key}`,
|
|
7488
7823
|
` 配置:${cfgLine}`,
|
|
7489
7824
|
` 请求:${ag.requestedKey} 来源:${BRAIN_SRC_LABEL[ag.source]}`,
|
|
7490
|
-
` 生效:name=${ag.name} bin=${ag.bin} args=[${ag.args.join(" ")}] shell=${ag.shell} home_env=${ag.homeEnvVar ?? "(无)"} model=${ag.model ?? "(agent 默认)"}`,
|
|
7825
|
+
` 生效:name=${ag.name} bin=${ag.bin} args=[${ag.args.join(" ")}] shell=${ag.shell} home_env=${ag.homeEnvVar ?? "(无)"} model=${ag.model ?? "(agent 默认)"} session_mode=${ag.sessionMode ?? "(无)"}`,
|
|
7491
7826
|
` 可用:${problem ? `❌ ${problem}` : `✅ ${ag.binPath ?? ag.bin}`}`,
|
|
7492
7827
|
` 可选 agent:${Object.keys(ACP_AGENT_PRESETS).join("、")}`,
|
|
7493
7828
|
` 自动探测顺序:${AUTO_PROBE_ORDER.join(" → ")}`
|
|
7494
7829
|
];
|
|
7830
|
+
const argsWarn = acpArgsWarning(ag.name, ag.args, ag.binOverridden);
|
|
7831
|
+
if (argsWarn) lines.push(` ⚠️ ${argsWarn}`);
|
|
7495
7832
|
const hint = ag.realProfile && ag.realHome && ag.inherit ? acpOverrideHint(ag.realHome, ag.realProfile) : null;
|
|
7496
7833
|
if (hint) lines.push("", hint);
|
|
7497
7834
|
return lines.join("\n");
|
|
7498
7835
|
}
|
|
7499
|
-
/** `wemind brain set <agent 名|auto>`:把选择按账号写入磁盘(可选 --bin/--args/--shell/--model;--restart 立即重启 daemon)。 */
|
|
7836
|
+
/** `wemind brain set <agent 名|auto>`:把选择按账号写入磁盘(可选 --bin/--args/--extra-args/--shell/--model;--restart 立即重启 daemon)。 */
|
|
7500
7837
|
async function cmdBrainSet(agentName, key, opts) {
|
|
7501
7838
|
const agent = agentName.trim().toLowerCase();
|
|
7502
7839
|
if (agent !== "auto" && !ACP_AGENT_PRESETS[agent]) throw new Error(`未知 agent「${agentName}」。可选:auto、${Object.keys(ACP_AGENT_PRESETS).join("、")}(自定义二进制用 --bin 搭配任一 agent)`);
|
|
@@ -7505,7 +7842,7 @@ async function cmdBrainSet(agentName, key, opts) {
|
|
|
7505
7842
|
if (agent === "auto") throw new Error("`auto` 是「自动探测」,不绑定具体 agent,不能配 --model;请指定具体 agent(如 brain set deepseek --model …)");
|
|
7506
7843
|
}
|
|
7507
7844
|
const dir = accountDir(key);
|
|
7508
|
-
const customized = opts.bin !== void 0 || opts.args !== void 0 || opts.shell !== void 0 || opts.model !== void 0;
|
|
7845
|
+
const customized = opts.bin !== void 0 || opts.args !== void 0 || opts.extraArgs !== void 0 || opts.shell !== void 0 || opts.model !== void 0;
|
|
7509
7846
|
if (!opts.restart && !customized && bridgeReady(key)) {
|
|
7510
7847
|
const r = await cmdBrainSwitch(agent, key, false);
|
|
7511
7848
|
return r.startsWith("❌") ? `${r}\n(brain.json **没有被改写**:换大脑失败就不写入磁盘——这是「失败就退回原来那个」的代价。想强制写入再重启:wemind brain set ${agent} --restart)` : r;
|
|
@@ -7513,17 +7850,24 @@ async function cmdBrainSet(agentName, key, opts) {
|
|
|
7513
7850
|
const patch = { agent };
|
|
7514
7851
|
if (opts.bin) patch.bin = opts.bin;
|
|
7515
7852
|
if (opts.args !== void 0) patch.args = opts.args.trim() ? opts.args.trim().split(/\s+/) : [];
|
|
7853
|
+
if (opts.extraArgs !== void 0) patch.extraArgs = opts.extraArgs.trim() ? opts.extraArgs.trim().split(/\s+/) : [];
|
|
7516
7854
|
if (opts.shell !== void 0) patch.shell = opts.shell;
|
|
7517
7855
|
if (opts.model !== void 0) patch.model = opts.model.trim();
|
|
7856
|
+
const before = loadBrainConfig(dir);
|
|
7518
7857
|
const cfg = updateBrainConfig(patch, dir);
|
|
7519
|
-
const lines = [`已设置 ${key} 的大脑:agent=${agent}${cfg.bin ? ` bin=${cfg.bin}` : ""}${cfg.args ? ` args=[${cfg.args.join(" ")}]` : ""}${cfg.model ? ` model=${cfg.model}` : ""}`, `配置文件:${brainConfigPath(dir)}`];
|
|
7858
|
+
const lines = [`已设置 ${key} 的大脑:agent=${agent}${cfg.bin ? ` bin=${cfg.bin}` : ""}${cfg.args ? ` args=[${cfg.args.join(" ")}]` : ""}${cfg.extraArgs ? ` extra_args=[${cfg.extraArgs.join(" ")}]` : ""}${cfg.model ? ` model=${cfg.model}` : ""}`, `配置文件:${brainConfigPath(dir)}`];
|
|
7859
|
+
if (opts.args !== void 0) {
|
|
7860
|
+
const binOverridden = opts.bin !== void 0 || !!before?.presets?.[agent]?.bin || before?.agent?.trim().toLowerCase() === agent && !!before?.bin;
|
|
7861
|
+
const warn = acpArgsWarning(agent, cfg.args ?? [], binOverridden);
|
|
7862
|
+
if (warn) lines.push(`⚠️ ${warn}`);
|
|
7863
|
+
}
|
|
7520
7864
|
if (opts.restart) if (aliveOurs(readPid(dir, "daemon.pid"))) {
|
|
7521
7865
|
startDaemon(key);
|
|
7522
7866
|
lines.push(`已重启 daemon(账号 ${key})。`);
|
|
7523
7867
|
} else lines.push(`daemon 未在运行,未重启(用 wemind start ${key} 启动)。`);
|
|
7524
7868
|
else if (customized && bridgeReady(key)) {
|
|
7525
|
-
const viaSwitch = opts.bin !== void 0 || opts.args !== void 0 || opts.model !== void 0;
|
|
7526
|
-
lines.push(`(带 --bin/--args/--shell/--model 的改动不能由 brain set 当场应用:控制桥只认 agent 名。重启后生效——wemind stop ${key} && wemind start ${key}${viaSwitch ? `;不想重启的话,接着执行 wemind brain switch ${agent} 也能当场应用(它按 name/bin/args/model 判断是不是同一个,改过就会真切换)` : ""})`);
|
|
7869
|
+
const viaSwitch = opts.bin !== void 0 || opts.args !== void 0 || opts.extraArgs !== void 0 || opts.model !== void 0;
|
|
7870
|
+
lines.push(`(带 --bin/--args/--extra-args/--shell/--model 的改动不能由 brain set 当场应用:控制桥只认 agent 名。重启后生效——wemind stop ${key} && wemind start ${key}${viaSwitch ? `;不想重启的话,接着执行 wemind brain switch ${agent} 也能当场应用(它按 name/bin/args/model 判断是不是同一个,改过就会真切换)` : ""})`);
|
|
7527
7871
|
} else if (daemonAlive(key)) lines.push(`(daemon 在跑但不是当前版本(不认控制桥):盘已写好,重启一次才生效——wemind stop ${key} && wemind start ${key})`);
|
|
7528
7872
|
else lines.push(`(daemon 需重启才生效:wemind start ${key})`);
|
|
7529
7873
|
return lines.join("\n");
|
|
@@ -7546,6 +7890,7 @@ async function cmdBrain(rest) {
|
|
|
7546
7890
|
const FLAGS = /* @__PURE__ */ new Set([
|
|
7547
7891
|
"--bin",
|
|
7548
7892
|
"--args",
|
|
7893
|
+
"--extra-args",
|
|
7549
7894
|
"--shell",
|
|
7550
7895
|
"--no-shell",
|
|
7551
7896
|
"--model",
|
|
@@ -7568,6 +7913,7 @@ async function cmdBrain(rest) {
|
|
|
7568
7913
|
};
|
|
7569
7914
|
const bin = takeFlag("--bin");
|
|
7570
7915
|
const argsOverride = takeFlag("--args");
|
|
7916
|
+
const extraArgsOverride = takeFlag("--extra-args");
|
|
7571
7917
|
const model = takeFlag("--model");
|
|
7572
7918
|
const restart = argv.includes("--restart");
|
|
7573
7919
|
if (restart) argv.splice(argv.indexOf("--restart"), 1);
|
|
@@ -7578,7 +7924,7 @@ async function cmdBrain(rest) {
|
|
|
7578
7924
|
}
|
|
7579
7925
|
while (argv.includes("--worker")) argv.splice(argv.indexOf("--worker"), 1);
|
|
7580
7926
|
const unknown = argv.filter((x) => x.startsWith("--"));
|
|
7581
|
-
if (unknown.length) throw new Error(`未知参数:${unknown.join(" ")}(可用:--bin --args --shell --no-shell --model --restart)`);
|
|
7927
|
+
if (unknown.length) throw new Error(`未知参数:${unknown.join(" ")}(可用:--bin --args --extra-args --shell --no-shell --model --restart)`);
|
|
7582
7928
|
const pos = argv.filter((x) => x && !x.startsWith("--"));
|
|
7583
7929
|
const first = (pos[0] || "").toLowerCase();
|
|
7584
7930
|
if (first && !(/* @__PURE__ */ new Set([
|
|
@@ -7593,16 +7939,17 @@ async function cmdBrain(rest) {
|
|
|
7593
7939
|
switch (sub) {
|
|
7594
7940
|
case "show": return describeBrain(pickKey(pos[1]));
|
|
7595
7941
|
case "set":
|
|
7596
|
-
if (!pos[1]) throw new Error("用法: brain set <agent 名|auto> [--bin X] [--args \"…\"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]");
|
|
7942
|
+
if (!pos[1]) throw new Error("用法: brain set <agent 名|auto> [--bin X] [--args \"…\"] [--extra-args \"…\"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]");
|
|
7597
7943
|
return await cmdBrainSet(pos[1], pickKey(pos[2]), {
|
|
7598
7944
|
bin,
|
|
7599
7945
|
args: argsOverride,
|
|
7946
|
+
extraArgs: extraArgsOverride,
|
|
7600
7947
|
shell,
|
|
7601
7948
|
model,
|
|
7602
7949
|
restart
|
|
7603
7950
|
});
|
|
7604
7951
|
case "switch": {
|
|
7605
|
-
if (bin !== void 0 || argsOverride !== void 0 || shell !== void 0 || model !== void 0) throw new Error("`brain switch` 只接受 agent 名(换大脑是「换一个已装好的 agent」,不能带 --bin/--args/--model)。\n需要自定义二进制用 `brain set <agent 名> --bin …`(那是配置变更,只写入磁盘——之后 `wemind brain switch <同名 agent>` 可当场应用,或重启 daemon
|
|
7952
|
+
if (bin !== void 0 || argsOverride !== void 0 || extraArgsOverride !== void 0 || shell !== void 0 || model !== void 0) throw new Error("`brain switch` 只接受 agent 名(换大脑是「换一个已装好的 agent」,不能带 --bin/--args/--extra-args/--model)。\n需要自定义二进制用 `brain set <agent 名> --bin …`(那是配置变更,只写入磁盘——之后 `wemind brain switch <同名 agent>` 可当场应用,或重启 daemon);追加参数用 `brain set <agent 名> --extra-args …`;换模型用 `brain set <agent 名> --model …`。");
|
|
7606
7953
|
if (!pos[1]) throw new Error("用法: brain switch <agent 名|auto> [--worker] [<key|label>]");
|
|
7607
7954
|
const worker = rest.includes("--worker");
|
|
7608
7955
|
return await cmdBrainSwitch(pos[1], pickKey(pos[2]), worker);
|
|
@@ -8110,8 +8457,8 @@ function helpText() {
|
|
|
8110
8457
|
list 列出账号、别名、运行状态(含 brain 进程与 worker 池)
|
|
8111
8458
|
start [key|label] 启动某账号的 daemon(省略 = 默认账号)
|
|
8112
8459
|
stop [key|label] 停止某账号的 daemon(连带 brain 与全部 worker 进程树;省略 = 默认账号)
|
|
8113
|
-
brain set <agent 名|auto> [--bin X] [--args "…"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]
|
|
8114
|
-
改账号大脑**配置**(写 brain.json;只给 agent 名且 daemon 认桥时当场应用;带 --bin/--args/--shell/--model 只写盘——除 --shell 外可用同名 brain switch
|
|
8460
|
+
brain set <agent 名|auto> [--bin X] [--args "…"] [--extra-args "…"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]
|
|
8461
|
+
改账号大脑**配置**(写 brain.json;只给 agent 名且 daemon 认桥时当场应用;带 --bin/--args/--extra-args/--shell/--model 只写盘——除 --shell 外可用同名 brain switch 当场应用;--extra-args 追加在预设参数后,不会漏掉 ACP 子命令)
|
|
8115
8462
|
brain unset|check|show [<key|label>] 清除配置 / 真机 ACP 自检 / 查看配置与生效情况
|
|
8116
8463
|
logout <key|label> 停 daemon + 通知下线 + 清凭证(保留目录,可重登)
|
|
8117
8464
|
delete <key|label> 停 daemon + 删除目录 + 移除注册
|