@wemind/cli 1.3.0 → 1.4.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 +27 -26
- package/dist/cli.js +4452 -2892
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":["loadRegistry","saveRegistry","readJson","resolveKey","isObj","catalogPath","mirrorRealKiro","readJson","syncCredentialFile","syncCredentials","loadCatalog","syncKiroSettings","syncOpencodeBase","settingsBase","syncSettingsBase","syncProfilePatch","syncSettings","isAlive","labelOf","dlog","REQ_TIMEOUT","PROMPT_IDLE_MS","PROMPT_CAP_MS","firstFrameMs","loadSessionMap","saveSessionMap","labelOf","log","writeTaskCmd","sleep","sweepWorkerPool","sleep","isAlive","readAccount","writeTaskCommand"],"sources":["../../wechat-ilink-core/dist/config.js","../../wechat-ilink-core/dist/pkg-version.js","../../wechat-ilink-core/dist/api/types.js","../../wechat-ilink-core/dist/api/error-codes.js","../../wechat-ilink-core/dist/api/client.js","../../wechat-ilink-core/dist/api/transport-errors.js","../../wechat-ilink-core/dist/api/session-guard.js","../../wechat-ilink-core/dist/store/accounts.js","../../wechat-ilink-core/dist/store/account.js","../../wechat-ilink-core/dist/store/allow-from.js","../../wechat-ilink-core/dist/cdn/image.js","../src/brain-config.ts","../src/cline-credentials.ts","../src/profile-model.ts","../src/brain-agents.ts","../src/agent-slot.ts","../src/agent-mcp.ts","../src/mcp-sandbox.ts","../src/proc-identity.ts","../src/pid-record.ts","../src/spawn-agent.ts","../src/session-map.ts","../src/session-registry.ts","../src/handoff.ts","../src/acp-brain.ts","../src/brain-state.ts","../src/brain-switch.ts","../src/chat-commands.ts","../src/session-manager.ts","../src/control-bridge.ts","../src/image-reply.ts","../src/md-reply.ts","../src/pending-push.ts","../src/worker-sandbox.ts","../src/worker-acp.ts","../src/worker-pool.ts","../src/task-store.ts","../src/task-manager.ts","../src/task-inbox.ts","../src/index.ts","../src/pm2-runner.ts","../src/debug-env.ts","../src/watchdog-runner.ts","../src/brain-check.ts","../src/version.ts","../src/manage.ts","../src/cli.ts"],"sourcesContent":["/**\n * iLink 应用标识。\n * 原 fork 从 package.json 的 ilink_appid 字段读取(向上查找);拆为 workspace 多包后,该耦合不再可靠(mcp-server 打包后会找到错误的 package.json),固化为常量,并允许 env 覆盖。\n */\nexport const ILINK_APP_ID = process.env.ILINK_APP_ID?.trim() || \"bot\";\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n/**\n * 读取「调用方所在包」自己的版本号(从它的 package.json)。\n *\n * ## 为什么放在 core\n * `@wemind/wechat-mcp` 与 `@wemind/cli` 都需要上报自己的版本,两边的「向上查找 package.json」逻辑完全一样。放 core 里只维护一份;两个消费方各留一个 3 行的薄适配层。\n *\n * ## 为什么必须由调用方传入位置(`callerUrl`)\n * `import.meta.url` 指向**当前文件**,而当前文件在 core 里——用它去向上找,命中的会是 core 自己的 `package.json`(或更上层的仓库根),**永远拿不到调用方的版本**。\n * 实测过:在 daemon 语境里直接复用 mcp 的同类实现,得到的是 mcp 的版本(1.0.4)而不是 daemon 的(1.1.0),且不会报错——静默给错值是最坏的一类 bug。\n * 所以这里强制由调用方传 `import.meta.url`,函数从**那个位置**向上找。\n *\n * ## 为什么还要 expectedName\n * 只按「最近的 package.json」查找并不可靠:在 monorepo 里向上找,可能先撞到 core 的、也可能撞到仓库根的。用 `name` 精确匹配,命中不了就返回 `undefined`(安全失败),由调用方决定兜底值,绝不静默返回别人的版本。\n *\n * ## 打包后仍然有效\n * 两个消费方都用 tsdown 把 core **内联**进单文件产物。由于查找起点是调用方的 `import.meta.url`(打包后是 `dist/cli.js` / `dist/index.js`),向上第一个带 `name` 匹配的 `package.json` 正是该消费方自己的——dev(`src/`)与产物(`dist/`)两种布局都成立。\n *\n * @param callerUrl 调用方模块的 `import.meta.url`\n * @param expectedName 期望的包名(即调用方自己 package.json 的 `name`)\n * @returns 匹配到的 `version`;找不到返回 `undefined`\n */\nexport function readOwnVersion(callerUrl, expectedName) {\n if (!callerUrl || !expectedName)\n return undefined;\n let dir;\n try {\n dir = path.dirname(fileURLToPath(callerUrl));\n }\n catch {\n // callerUrl 不是合法 file: URL(如打包器改写了它):安全失败\n return undefined;\n }\n const { root } = path.parse(dir);\n while (dir && dir !== root) {\n const candidate = path.join(dir, \"package.json\");\n try {\n const p = JSON.parse(fs.readFileSync(candidate, \"utf-8\"));\n if (p.name === expectedName && p.version)\n return p.version;\n }\n catch {\n // 文件不存在 / 非法 JSON:都不是目标,继续向上\n }\n dir = path.dirname(dir);\n }\n return undefined;\n}\n","/**\n * iLink Bot API 协议类型(照抄服务端 proto:GetUpdatesReq/Resp、WeixinMessage、SendMessageReq 等)。\n *\n * 来源与依据:\n * - 字段与 proto 含义对照官方 Tencent/openclaw-weixin `src/api/types.ts` 整理(社区/官方最权威的公开实现)。\n * - 错误码(ret / errcode)语义见 `error-codes.ts`。\n * - 标 `[实测]` 的注释为 WeMind 在真机使用中验证/纠正的结论(详见笔记「迭代记录与待办」)。\n *\n * 约定:\n * - 全部走 JSON over HTTP;proto 里的 bytes 字段在 JSON 中是 **base64 字符串**。\n * - 协议字段绝大多数可选(服务端按场景下发),故统一用 `?`;本地自有类型(AccountData/ServerState/QrLoginState)按实际必填。\n */\n/** 消息方向(proto: MessageType)。USER=入站(用户发来),BOT=出站(机器人发出)。 */\nexport const MessageType = {\n NONE: 0,\n USER: 1,\n BOT: 2,\n};\n/** 消息项类型(proto: MessageItemType)。一条消息的 item_list 里每项是其中一种。 */\nexport const MessageItemType = {\n NONE: 0,\n TEXT: 1,\n IMAGE: 2,\n VOICE: 3,\n FILE: 4,\n VIDEO: 5,\n};\n/**\n * 消息状态(proto: MessageState)。\n * - NEW=0:新消息。\n * - GENERATING=1:生成中(流式/进行中)。\n * - FINISH=2:本回合结束。\n * [实测] 关键:发送进度/中间消息用 **GENERATING**,仅最终答案用 **FINISH**——FINISH 会结束当前回合(关闭该 context 的发送窗口),若进度也用 FINISH 会提前关掉会话。\n * 详见笔记里程碑 21/23 与 README「进度提示与连续发送上限」。\n */\nexport const MessageState = {\n NEW: 0,\n GENERATING: 1,\n FINISH: 2,\n};\n/** 打字状态(proto: TypingStatus)。1=显示「正在输入」,2=取消。配合 sendTyping 使用。 */\nexport const TypingStatus = {\n TYPING: 1,\n CANCEL: 2,\n};\n/** 上传媒体类型(proto: UploadMediaType)。getUploadUrl 的 media_type 取值。 */\nexport const UploadMediaType = {\n IMAGE: 1,\n VIDEO: 2,\n FILE: 3,\n VOICE: 4,\n};\n","/**\n * iLink Bot API 错误码定义与分类。\n *\n * 权威来源:逆向自官方 Tencent/openclaw-weixin 的「iLink Bot API 协议参考」(corespeed-io/wechatbot `docs/protocol.md` 的 Error Codes 表,社区逆向、目前最权威的公开文档)。\n *\n * 两个字段,语义不同:\n * - `ret` :发送/上传等 RPC 的返回码(sendmessage / getuploadurl 等响应体)。\n * - `errcode`:长轮询/会话类接口的错误码(getupdates 等)。\n *\n * 已知错误码(截至文档):\n * | 码 | 字段 | 含义 | 处理 |\n * |---------------|----------|------------------------------|----------------------------------------|\n * | 0 | ret | 成功 | — |\n * | -2 | ret | 参数错误 (Parameter error) | 检查请求参数 |\n * | -14 | errcode | 会话过期 (Session expired) | 重新登录(本项目对该账号冷却 1 小时) |\n *\n * ⚠️ 重要澄清(避免重蹈覆辙):\n * - `ret = -2` 官方定义是**参数错误**,**不是**\"context_token 按时间过期\"。\n * 常见诱因:context_token 失效/与当前会话不匹配、必填字段缺失/格式不对。\n * - context_token 官方说明:按 `(accountId, userId)` **缓存、持久化、重用**,**仅在会话过期(-14)/重新登录时清除**——**没有**文档化的时间 TTL。\n * - 真正\"按时间/会话过期\"的是 `errcode = -14`(会话级),与 -2 无关。\n */\n/** 发送/上传 RPC 的 `ret` 码。 */\nexport const ILINK_RET = {\n OK: 0,\n /** 参数错误:检查请求(最常见为 context_token 失效/不匹配;也可能被复用为发送限流——短时连续发送过多,见 README「连续发送上限」)。 */\n PARAM_ERROR: -2,\n};\n/** 长轮询/会话类接口的 `errcode` 码。 */\nexport const ILINK_ERRCODE = {\n /** 会话过期,需重新登录。 */\n SESSION_EXPIRED: -14,\n};\n/** 把一个 ret/errcode 数值分类。 */\nexport function classifyILinkCode(code) {\n if (code === undefined || code === null || code === 0)\n return \"ok\";\n if (code === ILINK_ERRCODE.SESSION_EXPIRED)\n return \"session_expired\";\n if (code === ILINK_RET.PARAM_ERROR)\n return \"param_error\";\n return \"unknown\";\n}\n/** 返回可读标签 + 处理建议,用于日志与提示。 */\nexport function describeILinkCode(code) {\n switch (classifyILinkCode(code)) {\n case \"ok\":\n return { kind: \"ok\", label: `ok(${code ?? 0})`, hint: \"\" };\n case \"session_expired\":\n return { kind: \"session_expired\", label: \"会话过期(-14)\", hint: \"需重新登录\" };\n case \"param_error\":\n return {\n kind: \"param_error\",\n label: \"参数错误(-2)\",\n hint: \"检查请求参数(最常见:context_token 失效/不匹配;或发送限流——短时连续发送过多)\",\n };\n default:\n return { kind: \"unknown\", label: `未知错误码(${code})`, hint: \"对照 iLink 协议文档排查\" };\n }\n}\n/** iLink 接口返回非 0 ret/errcode 时抛出的结构化错误,携带原始码与分类,便于上层按类型处理。 */\nexport class ILinkError extends Error {\n code;\n kind;\n constructor(op, code, errmsg) {\n const { kind, label, hint } = describeILinkCode(code);\n super(`${op} 调用失败:${label}${hint ? ` — ${hint}` : \"\"}${errmsg ? `(${errmsg})` : \"\"}`);\n this.name = \"ILinkError\";\n this.code = code;\n this.kind = kind;\n }\n}\n","import crypto from \"node:crypto\";\nimport { ILINK_APP_ID } from \"../config.js\";\nimport { ILinkError } from \"./error-codes.js\";\n/** 普通 RPC 的超时(send/typing/config/upload/notify)。 */\nconst DEFAULT_API_TIMEOUT_MS = 15_000;\n/** 消息长轮询(getupdates)超时。 */\nconst DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;\n/** 扫码状态长轮询(get_qrcode_status)超时。与消息长轮询语义不同,故分开命名。 */\nconst QR_LONG_POLL_TIMEOUT_MS = 35_000;\n// 对外声明的 iLink 客户端版本:模仿官方 openclaw-weixin 的版本,避免后端因客户端版本过低而拒绝/快速过期二维码。需随官方更新——故支持 env 覆盖,免改代码。\nconst ILINK_CLIENT_VERSION = process.env.ILINK_CLIENT_VERSION?.trim() || \"2.4.3\";\nconst CHANNEL_VERSION = ILINK_CLIENT_VERSION;\n/**\n * iLink-App-ClientVersion: uint32 编码 0x00MMNNPP(major<<16 | minor<<8 | patch)。\n * 编码方式借鉴自官方 Tencent/openclaw-weixin(MIT)。\n */\nexport function buildClientVersion(version) {\n const [major = 0, minor = 0, patch = 0] = version.split(\".\").map((p) => parseInt(p, 10) || 0);\n return ((major & 0xff) << 16) | ((minor & 0xff) << 8) | (patch & 0xff);\n}\nconst ILINK_APP_CLIENT_VERSION = String(buildClientVersion(ILINK_CLIENT_VERSION));\n/** iLink 公共请求头:所有 GET/POST 都需携带,缺失会导致服务端拒绝连接。 */\nfunction buildCommonHeaders() {\n return {\n \"iLink-App-Id\": ILINK_APP_ID,\n \"iLink-App-ClientVersion\": ILINK_APP_CLIENT_VERSION,\n };\n}\nfunction ensureTrailingSlash(url) {\n return url.endsWith(\"/\") ? url : `${url}/`;\n}\nfunction randomWechatUin() {\n const uint32 = crypto.randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\nfunction buildHeaders(opts) {\n const headers = {\n \"Content-Type\": \"application/json\",\n AuthorizationType: \"ilink_bot_token\",\n \"Content-Length\": String(Buffer.byteLength(opts.body, \"utf-8\")),\n \"X-WECHAT-UIN\": randomWechatUin(),\n ...buildCommonHeaders(),\n };\n if (opts.token?.trim()) {\n headers.Authorization = `Bearer ${opts.token.trim()}`;\n }\n return headers;\n}\n/** POST 一个 iLink CGI,返回原始响应体。HTTP 非 2xx 抛错;响应体是否是 JSON 由调用方决定。 */\nasync function apiFetch(params) {\n const base = ensureTrailingSlash(params.baseUrl);\n const url = new URL(params.endpoint, base);\n const hdrs = buildHeaders({ token: params.token, body: params.body });\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), params.timeoutMs);\n try {\n const res = await fetch(url.toString(), {\n method: \"POST\",\n headers: hdrs,\n body: params.body,\n signal: controller.signal,\n });\n // 读完响应体后才算这次请求结束:只清定时器会漏掉「头已到、体卡住」的慢响应,长轮询尤甚——所以清理放在 finally,成功/失败/超时都只写一次。\n const rawText = await res.text();\n if (!res.ok) {\n throw new Error(`${params.label} ${res.status}: ${rawText.slice(0, 500)}`);\n }\n return rawText;\n }\n finally {\n clearTimeout(timer);\n }\n}\n/** 宽松解析:空响应体或非 JSON 返回 null(部分接口成功时无响应体)。 */\nfunction tryParseJson(raw) {\n if (!raw)\n return null;\n try {\n return JSON.parse(raw);\n }\n catch {\n return null;\n }\n}\n/** 防御性解析 API JSON 响应:失败抛带 label 与响应片段的清晰错误,而非裸 SyntaxError(畸形/代理 HTML/截断响应不致以晦涩错误打断调用方)。 */\nfunction parseJsonResp(raw, label) {\n try {\n return JSON.parse(raw);\n }\n catch {\n throw new Error(`${label}:响应不是合法 JSON(${raw.slice(0, 120)})`);\n }\n}\n/**\n * iLink 即使 HTTP 200 也可能在响应体返回非 0 ret/errcode 表示失败。语义见 error-codes.ts:\n * ret=-2 参数错误(最常见 context_token 失效/不匹配);errcode=-14 会话过期。\n * 不校验会导致「假成功」——消息其实没送达。仅在明确拿到非 0 错误码时抛 ILinkError(携带码+分类,供上层按类型处理)。\n */\nfunction throwIfErrorCode(op, resp) {\n const code = resp?.ret ?? resp?.errcode;\n if (code !== undefined && code !== 0) {\n throw new ILinkError(op, code, resp?.errmsg);\n }\n}\nexport function buildBaseInfo() {\n return { channel_version: CHANNEL_VERSION };\n}\nexport async function getUpdates(params) {\n const timeout = params.timeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;\n try {\n const rawText = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getupdates\",\n body: JSON.stringify({\n get_updates_buf: params.updatesBuf ?? \"\",\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: timeout,\n label: \"getUpdates\",\n });\n return parseJsonResp(rawText, \"getUpdates\");\n }\n catch (err) {\n // 长轮询超时是常态:返回空结果(保留游标),让调用方直接进入下一轮。\n if (err instanceof Error && err.name === \"AbortError\") {\n return { ret: 0, msgs: [], get_updates_buf: params.updatesBuf };\n }\n throw err;\n }\n}\nexport async function sendMessage(params) {\n const raw = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendmessage\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"sendMessage\",\n });\n // 空/非 JSON 响应视为成功(历史上 iLink 成功时可能无响应体);只有明确拿到非 0 错误码才抛。\n throwIfErrorCode(\"sendMessage\", tryParseJson(raw));\n}\n/** 取 CDN 上传地址(发图/发文件用)。返回 upload_full_url 或 upload_param。 */\nexport async function getUploadUrl(params) {\n const raw = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getuploadurl\",\n body: JSON.stringify({\n filekey: params.filekey,\n media_type: params.media_type,\n to_user_id: params.to_user_id,\n rawsize: params.rawsize,\n rawfilemd5: params.rawfilemd5,\n filesize: params.filesize,\n no_need_thumb: params.no_need_thumb,\n aeskey: params.aeskey,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"getUploadUrl\",\n });\n const resp = parseJsonResp(raw, \"getUploadUrl\");\n throwIfErrorCode(\"getUploadUrl\", resp);\n return resp;\n}\nexport async function fetchQRCode(apiBaseUrl) {\n // 对齐官方:get_bot_qrcode 为 POST,body 带 local_token_list,并走全套请求头。\n const rawText = await apiFetch({\n baseUrl: apiBaseUrl,\n endpoint: \"ilink/bot/get_bot_qrcode?bot_type=3\",\n body: JSON.stringify({ local_token_list: [] }),\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: \"fetchQRCode\",\n });\n return parseJsonResp(rawText, \"fetchQRCode\");\n}\nexport async function pollQRStatus(apiBaseUrl, qrcode) {\n const base = ensureTrailingSlash(apiBaseUrl);\n const url = new URL(`ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`, base);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), QR_LONG_POLL_TIMEOUT_MS);\n try {\n const response = await fetch(url.toString(), {\n headers: buildCommonHeaders(),\n signal: controller.signal,\n });\n const text = await response.text();\n if (!response.ok) {\n throw new Error(`pollQRStatus ${response.status} ${response.statusText} ${text.slice(0, 500)}`);\n }\n return parseJsonResp(text, \"pollQRStatus\");\n }\n catch (err) {\n // 服务端会把长轮询 hold 到超时:AbortError 表示「这段时间没人扫码」,不是失败。\n if (err instanceof Error && err.name === \"AbortError\") {\n return { status: \"wait\" };\n }\n throw err;\n }\n finally {\n clearTimeout(timer);\n }\n}\nexport function generateId(prefix) {\n return `${prefix}:${Date.now()}-${crypto.randomBytes(4).toString(\"hex\")}`;\n}\nexport async function getConfig(params) {\n const rawText = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getconfig\",\n body: JSON.stringify({\n ilink_user_id: params.ilinkUserId,\n context_token: params.contextToken,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: \"getConfig\",\n });\n const resp = parseJsonResp(rawText, \"getConfig\");\n throwIfErrorCode(\"getConfig\", resp);\n return resp;\n}\nexport async function sendTyping(params) {\n const raw = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendtyping\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: \"sendTyping\",\n });\n // 与 sendMessage 同理:HTTP 200 也可能带非 0 ret,不校验会让「打字中」显示成假成功。\n throwIfErrorCode(\"sendTyping\", tryParseJson(raw));\n}\n/**\n * 上线/下线通知的共同实现。\n * best-effort:失败只记日志、绝不抛出——通知失败不应阻断登录/登出主流程。\n * 也正因为要吞错,这里刻意**不校验**响应体的 ret/errcode。\n */\nasync function notifyLifecycle(op, endpoint, params) {\n try {\n await apiFetch({\n baseUrl: params.baseUrl,\n endpoint,\n body: JSON.stringify({ base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: op,\n });\n }\n catch (err) {\n console.error(`[wemind/wechat-ilink-core/ilink] ${op} failed (ignored, best-effort): ${String(err)}`);\n }\n}\n/** 通知服务端该 bot 客户端上线(best-effort,失败不阻断)。 */\nexport async function notifyStart(params) {\n await notifyLifecycle(\"notifyStart\", \"ilink/bot/msg/notifystart\", params);\n}\n/** 通知服务端该 bot 客户端下线(best-effort,失败不阻断)。 */\nexport async function notifyStop(params) {\n await notifyLifecycle(\"notifyStop\", \"ilink/bot/msg/notifystop\", params);\n}\n","/**\n * 传输层错误分类:把 fetch/网络层抛出的错误归类,判定是否可重试。\n * 与 error-codes.ts(iLink 业务 ret/errcode)正交——这里只管「网络传输」层面的失败。\n * 借鉴 wxclawbot-cli(MIT)client.ts 的 classifyTransportError 思路。\n */\n/** 归类一个传输层错误。timeout(AbortError)一律可重试。 */\nexport function classifyTransportError(err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n return { kind: \"timeout\", retryable: true, message: err.message || \"request timeout\" };\n }\n const message = err instanceof Error ? err.message : String(err);\n const code = err &&\n typeof err === \"object\" &&\n \"code\" in err &&\n typeof err.code === \"string\"\n ? err.code\n : undefined;\n const cause = err instanceof Error && err.cause != null ? String(err.cause) : undefined;\n const s = `${message} ${code ?? \"\"} ${cause ?? \"\"}`.toLowerCase();\n if (s.includes(\"enotfound\") || s.includes(\"dns\"))\n return { kind: \"dns\", retryable: true, message, code };\n if (s.includes(\"econnreset\") || s.includes(\"socket hang up\"))\n return { kind: \"connection\", retryable: true, message, code };\n // TLS/证书错误判定必须排在 \"fetch failed\" 兜底之前:Node fetch 的网络失败顶层恒为 \"TypeError: fetch failed\",真正的 cert/tls 原因在 err.cause 里(已并入 s)。若放在 network 分支之后,会被 \"fetch failed\" 先匹配成可重试 → 此分支成死代码。\n if (s.includes(\"cert\") || s.includes(\"tls\") || s.includes(\"ssl\"))\n return { kind: \"tls\", retryable: false, message, code };\n if (s.includes(\"econnrefused\") ||\n s.includes(\"ehostunreach\") ||\n s.includes(\"enetunreach\") ||\n s.includes(\"etimedout\") ||\n s.includes(\"network\") ||\n s.includes(\"fetch failed\")) {\n return { kind: \"network\", retryable: true, message, code };\n }\n return { kind: \"unknown\", retryable: false, message, code };\n}\n","/**\n * 会话守卫:处理服务端 errcode -14(bot 会话超时)。\n * 命中后对该账号进入 1 小时冷却,期间快速返回而非反复长轮询空转。\n * 逻辑借鉴自官方 Tencent/openclaw-weixin(MIT),按 MCP 请求驱动场景精简。\n */\n/** 服务端返回的会话超时错误码。 */\nexport const SESSION_EXPIRED_ERRCODE = -14;\nconst PAUSE_MS = 60 * 60 * 1000;\nconst pauseUntil = new Map();\n/** 命中 -14 后暂停该账号一小时。 */\nexport function pauseSession(accountId) {\n pauseUntil.set(accountId, Date.now() + PAUSE_MS);\n}\n/** 距离冷却结束的剩余毫秒数(未暂停时为 0)。 */\nexport function getRemainingPauseMs(accountId) {\n const until = pauseUntil.get(accountId);\n if (until === undefined)\n return 0;\n const remaining = until - Date.now();\n if (remaining <= 0) {\n pauseUntil.delete(accountId);\n return 0;\n }\n return remaining;\n}\nexport function isSessionPaused(accountId) {\n return getRemainingPauseMs(accountId) > 0;\n}\n/** 重新登录成功后清除冷却。 */\nexport function clearSessionPause(accountId) {\n pauseUntil.delete(accountId);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n/**\n * 账号注册表:管理 ~/.wemind 下的多账号清单。\n * - 每个账号一个独立子目录(key = 由 botId 派生的唯一标识,见 deriveAccountKey)。\n * - accounts.json 记录 key → 元数据(botId/userId/baseUrl/label/addedAt)与默认账号。\n * label 为人类可读别名(默认等于 key,可改),folder/key 始终唯一且由微信 botId 派生。\n */\n/** WeMind 根目录(所有账号子目录与注册表的父目录)。可用 WEMIND_HOME 覆盖。 */\nexport const WEMIND_ROOT = process.env.WEMIND_HOME?.trim() || path.join(os.homedir(), \".wemind\");\nconst REGISTRY_FILE = path.join(WEMIND_ROOT, \"accounts.json\");\n/** 把 botId(如 feead781eb44@im.bot)净化成可作目录名的唯一 key(feead781eb44)。 */\nexport function deriveAccountKey(botId) {\n const head = (botId || \"\").split(\"@\")[0] || botId || \"\";\n const cleaned = head.replace(/[^a-zA-Z0-9_-]/g, \"\").trim();\n return cleaned || \"account\";\n}\n/** 某账号的状态目录:~/.wemind/<key>。 */\nexport function accountDir(key) {\n return path.join(WEMIND_ROOT, key);\n}\nexport function loadRegistry() {\n let raw;\n try {\n raw = fs.readFileSync(REGISTRY_FILE, \"utf-8\");\n }\n catch {\n return { accounts: {} }; // 不存在:正常(首个账号登记前)\n }\n try {\n const j = JSON.parse(raw);\n if (j && typeof j === \"object\" && j.accounts)\n return j;\n throw new Error(\"registry shape invalid\");\n }\n catch (e) {\n // 存在但损坏:不要当空注册表直接覆盖(紧接的 save 会抹掉所有已登记账号);备份坏文件 + 记日志\n console.error(`[wemind/wechat-ilink-core/accounts] ${REGISTRY_FILE} parse failed, backing up: ${String(e)}`);\n try {\n fs.renameSync(REGISTRY_FILE, `${REGISTRY_FILE}.corrupt-${Date.now()}`);\n }\n catch {\n /* best-effort */\n }\n return { accounts: {} };\n }\n}\n/** 原子写注册表:临时文件 + rename,避免写途崩溃/并发损坏。 */\nexport function saveRegistry(reg) {\n fs.mkdirSync(WEMIND_ROOT, { recursive: true });\n const tmp = `${REGISTRY_FILE}.tmp-${process.pid}-${Date.now()}`;\n fs.writeFileSync(tmp, JSON.stringify(reg, null, 2), \"utf-8\");\n fs.renameSync(tmp, REGISTRY_FILE);\n}\nexport function listAccounts() {\n const reg = loadRegistry();\n return Object.values(reg.accounts).sort((a, b) => a.addedAt - b.addedAt);\n}\nexport function getDefaultKey() {\n const reg = loadRegistry();\n if (reg.default && reg.accounts[reg.default])\n return reg.default;\n const keys = Object.keys(reg.accounts);\n return keys.length ? keys[0] : undefined;\n}\nexport function setDefaultKey(key) {\n const reg = loadRegistry();\n if (!reg.accounts[key])\n throw new Error(`account not found: ${key}`);\n reg.default = key;\n saveRegistry(reg);\n}\n/** 登记/更新一个账号;首个账号自动设为默认。 */\nexport function registerAccount(meta) {\n const reg = loadRegistry();\n const existing = reg.accounts[meta.key];\n const full = {\n key: meta.key,\n botId: meta.botId,\n userId: meta.userId,\n baseUrl: meta.baseUrl,\n label: meta.label || existing?.label || meta.key,\n addedAt: existing?.addedAt ?? meta.addedAt ?? Date.now(),\n };\n reg.accounts[meta.key] = full;\n if (!reg.default)\n reg.default = meta.key;\n saveRegistry(reg);\n return full;\n}\n/** 移除注册表中的账号条目(不删除磁盘目录,由调用方决定)。 */\nexport function unregisterAccount(key) {\n const reg = loadRegistry();\n delete reg.accounts[key];\n if (reg.default === key) {\n const rest = Object.keys(reg.accounts);\n reg.default = rest.length ? rest[0] : undefined;\n }\n saveRegistry(reg);\n}\n/** 设置/修改某账号的人类可读别名。 */\nexport function setLabel(key, label) {\n const reg = loadRegistry();\n if (!reg.accounts[key])\n throw new Error(`account not found: ${key}`);\n reg.accounts[key].label = label;\n saveRegistry(reg);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { WEMIND_ROOT } from \"./accounts.js\";\n/**\n * 数据目录解析(按优先级):\n * 1. WECHAT_DATA_DIR:显式指定完整路径(最高优先级,便于任意自定义/多账号)。\n * 2. 否则 ~/.wemind/<account>,其中 <account> 取自 WECHAT_ACCOUNT,默认 \"default\"。\n * account.json / state.json / brain-sessions.json / brain-mcps.json / brain-home 等都落在此目录下。\n * 多账号即「不同 WECHAT_ACCOUNT(或 WECHAT_DATA_DIR)→ 不同目录 → 互相隔离」。\n */\nexport const ACCOUNT_KEY = process.env.WECHAT_ACCOUNT?.trim() || \"default\";\nexport const DATA_DIR = process.env.WECHAT_DATA_DIR?.trim() || path.join(WEMIND_ROOT, ACCOUNT_KEY);\n/** 持久化文件名:集中在此,避免各处拼字符串写错(load/save/clear 必须指向同一份)。 */\nconst FILES = {\n account: \"account.json\",\n state: \"state.json\",\n qrState: \"qr_login.json\",\n};\nfunction ensureDir(dir) {\n fs.mkdirSync(dir, { recursive: true });\n}\nfunction readJson(filePath) {\n let raw;\n try {\n raw = fs.readFileSync(filePath, \"utf-8\");\n }\n catch (e) {\n // 只有「不存在」才算首次运行;权限/fs 错误必须冒出去,不能静默当成空数据(否则下一步 save 会覆盖掉原文件)\n if (isNotFound(e))\n return null;\n throw e;\n }\n try {\n return JSON.parse(raw);\n }\n catch (e) {\n // 文件存在但解析失败(写途崩溃/外部破坏):不静默清空,备份坏文件 + 记日志,便于排查与人工恢复\n console.error(`[wemind/wechat-ilink-core/store] ${filePath} parse failed, backing up and treating as empty: ${String(e)}`);\n try {\n fs.renameSync(filePath, `${filePath}.corrupt-${Date.now()}`);\n }\n catch {\n /* best-effort */\n }\n return null;\n }\n}\nfunction isNotFound(err) {\n return err?.code === \"ENOENT\";\n}\n/** 原子写:先写临时文件再 rename 覆盖(同盘 rename 原子),避免写途崩溃/并发产生半截损坏文件。 */\nfunction writeJson(filePath, data) {\n ensureDir(path.dirname(filePath));\n const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n try {\n fs.writeFileSync(tmp, JSON.stringify(data, null, 2), \"utf-8\");\n fs.renameSync(tmp, filePath);\n }\n catch (e) {\n // 写/rename 失败时清掉临时文件,避免数据目录里堆积 *.tmp-* 垃圾\n try {\n fs.rmSync(tmp, { force: true });\n }\n catch {\n /* best-effort */\n }\n throw e;\n }\n}\nexport function loadAccount(dir = DATA_DIR) {\n return readJson(path.join(dir, FILES.account));\n}\nlet warnedWinPerms = false;\n/**\n * 把登录凭证写进**指定**目录的 `account.json`(原子写 + POSIX 600 + Windows 一次性明文告警)。\n *\n * 为什么要有 dir 参数:`wemind login` 是先写到暂存目录 `.staging/`、扫码确认后才 rename 成账号目录。\n * 那条路径以前直接 `fs.writeFileSync`,于是「POSIX 600 / Windows 告警」这两条安全承诺在最推荐的登录入口上不成立(chmod 也漏了)。改成复用本函数,两条路径行为一致。\n * rename 同盘保留权限位,所以先写好再归档不会丢 600。\n */\nexport function saveAccountTo(dir, account) {\n const file = path.join(dir, FILES.account);\n writeJson(file, account);\n if (process.platform === \"win32\") {\n // Windows 上 fs.chmod 实际是 no-op,无法用 0o600 限制访问。\n // 不做无效操作,改为一次性提醒(详见 README 的安全说明)。\n if (!warnedWinPerms) {\n warnedWinPerms = true;\n console.error(`[wemind/wechat-ilink-core] warning: ${file} stores the bot token in plaintext; ` +\n `Windows cannot restrict it via chmod. Protect ${dir} with folder permissions or disk encryption.`);\n }\n }\n else {\n try {\n fs.chmodSync(file, 0o600);\n }\n catch {\n /* best-effort */\n }\n }\n}\nexport function saveAccount(account) {\n saveAccountTo(DATA_DIR, account);\n}\n/** 删除凭证。返回是否真的删掉了(登录流程据此判断「本来就没登录」还是「删失败」)。 */\nexport function clearAccount(dir = DATA_DIR) {\n return removeFile(path.join(dir, FILES.account));\n}\n/** 空状态:唯一来源,避免 loadState/resetState 各写一份、将来加字段时漏改。 */\nfunction emptyState() {\n return { updatesBuf: \"\", contextTokens: {}, lastMessageId: 0 };\n}\nexport function loadState(dir = DATA_DIR) {\n return readJson(path.join(dir, FILES.state)) ?? emptyState();\n}\nexport function saveState(state, dir = DATA_DIR) {\n writeJson(path.join(dir, FILES.state), state);\n}\nexport function resetState(dir = DATA_DIR) {\n saveState(emptyState(), dir);\n}\nexport function loadQrState(dir = DATA_DIR) {\n return readJson(path.join(dir, FILES.qrState));\n}\nexport function saveQrState(state, dir = DATA_DIR) {\n writeJson(path.join(dir, FILES.qrState), state);\n}\nexport function clearQrState(dir = DATA_DIR) {\n return removeFile(path.join(dir, FILES.qrState));\n}\nfunction removeFile(filePath) {\n try {\n fs.unlinkSync(filePath);\n return true;\n }\n catch (e) {\n if (isNotFound(e))\n return false; // 本来就没有:正常\n console.error(`[wemind/wechat-ilink-core/store] failed to remove ${filePath}: ${String(e)}`);\n return false;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DATA_DIR } from \"./account.js\";\nfunction filePath(dir = DATA_DIR) {\n return path.join(dir, \"allowFrom.json\");\n}\nexport function readAllowFrom(dir = DATA_DIR) {\n let raw;\n try {\n raw = fs.readFileSync(filePath(dir), \"utf-8\");\n }\n catch {\n return []; // 不存在:正常(daemon 启动会种入本人)\n }\n try {\n const j = JSON.parse(raw);\n return Array.isArray(j.allowFrom)\n ? j.allowFrom.filter((x) => typeof x === \"string\" && !!x)\n : [];\n }\n catch (e) {\n // 存在但损坏:本清单是安全闸,损坏返回 [] 是 fail-closed(全员锁死),必须可见 → 记日志 + 备份坏文件\n console.error(`[wemind/wechat-ilink-core/allow-from] ${filePath(dir)} parse failed, backing up: ${String(e)}`);\n try {\n fs.renameSync(filePath(dir), `${filePath(dir)}.corrupt-${Date.now()}`);\n }\n catch {\n /* best-effort */\n }\n return [];\n }\n}\n/** 原子写:临时文件 + rename,避免写途崩溃/并发损坏这个安全闸文件。 */\nfunction writeAllowFrom(list, dir = DATA_DIR) {\n fs.mkdirSync(dir, { recursive: true });\n const uniq = [...new Set(list.filter((x) => typeof x === \"string\" && !!x))];\n const fp = filePath(dir);\n const tmp = `${fp}.tmp-${process.pid}-${Date.now()}`;\n fs.writeFileSync(tmp, JSON.stringify({ version: 1, allowFrom: uniq }, null, 2), \"utf-8\");\n fs.renameSync(tmp, fp);\n}\n/** 把某个 userId 加入清单(去重)。返回更新后的清单。 */\nexport function addAllowFrom(userId, dir = DATA_DIR) {\n const list = readAllowFrom(dir);\n if (userId && !list.includes(userId))\n list.push(userId);\n writeAllowFrom(list, dir);\n return list;\n}\n/** 从清单里移除某个 userId。返回更新后的清单。 */\nexport function removeAllowFrom(userId, dir = DATA_DIR) {\n const list = readAllowFrom(dir).filter((x) => x !== userId);\n writeAllowFrom(list, dir);\n return list;\n}\n/** 该 userId 是否在清单里。 */\nexport function isAllowed(userId, dir = DATA_DIR) {\n return !!userId && readAllowFrom(dir).includes(userId);\n}\n","import { createCipheriv, createDecipheriv, createHash, randomBytes } from \"node:crypto\";\nimport { getUploadUrl, sendMessage, generateId } from \"../api/client.js\";\nimport { MessageType, MessageState, MessageItemType, UploadMediaType, } from \"../api/types.js\";\n/**\n * 收图:从 iLink CDN 下载并 AES-128-ECB 解密图片。\n * 机制借鉴官方 Tencent/openclaw-weixin(MIT)的 cdn/pic-decrypt + cdn-url。\n */\nconst CDN_BASE_URL = process.env.WECHAT_CDN_BASE_URL?.trim() || \"https://novac2c.cdn.weixin.qq.com/c2c\";\nexport function decryptAesEcb(ciphertext, key) {\n const d = createDecipheriv(\"aes-128-ecb\", key, null); // PKCS7 默认\n return Buffer.concat([d.update(ciphertext), d.final()]);\n}\n/** 解析图片 AES key → 16 字节。aeskey 为 hex;media.aes_key 为 base64(可能是 base64(hex 字符串))。 */\nexport function resolveKey(img) {\n if (img.aeskey && /^[0-9a-fA-F]{32}$/.test(img.aeskey))\n return Buffer.from(img.aeskey, \"hex\");\n const b64 = img.media?.aes_key;\n if (b64) {\n const decoded = Buffer.from(b64, \"base64\");\n if (decoded.length === 16)\n return decoded;\n if (decoded.length === 32 && /^[0-9a-fA-F]{32}$/.test(decoded.toString(\"ascii\"))) {\n return Buffer.from(decoded.toString(\"ascii\"), \"hex\");\n }\n }\n return null;\n}\n/** 下载并解密一张图片,返回明文 Buffer(失败抛错)。 */\nexport async function downloadImage(img) {\n const url = img.media?.full_url ||\n (img.media?.encrypt_query_param\n ? `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(img.media.encrypt_query_param)}`\n : \"\");\n if (!url)\n throw new Error(\"image: 无 full_url/encrypt_query_param\");\n const key = resolveKey(img);\n if (!key)\n throw new Error(\"image: 无法解析 aes key\");\n const res = await fetch(url, { signal: AbortSignal.timeout(20000) });\n if (!res.ok)\n throw new Error(`image: CDN 下载失败 ${res.status}`);\n const ciphertext = Buffer.from(await res.arrayBuffer());\n return decryptAesEcb(ciphertext, key);\n}\n// ===== 发图:AES-128-ECB 加密 + CDN 上传 + 拼 image_item 发送 =====\n// 机制借鉴官方 Tencent/openclaw-weixin(MIT)的 cdn/aes-ecb + cdn/upload + cdn/cdn-upload + messaging/send。\n/** AES-128-ECB 加密(PKCS7 默认)。 */\nexport function encryptAesEcb(plaintext, key) {\n const c = createCipheriv(\"aes-128-ecb\", key, null);\n return Buffer.concat([c.update(plaintext), c.final()]);\n}\n/** AES-128-ECB 密文大小(PKCS7 补齐到 16 字节边界)。 */\nexport function aesEcbPaddedSize(plaintextSize) {\n return Math.ceil((plaintextSize + 1) / 16) * 16;\n}\n/** 加密并上传一段 buffer 到 CDN,返回下载用的 encrypt_query_param(响应头 x-encrypted-param)。 */\nasync function uploadBufferToCdn(params) {\n const { buf, aeskey, filekey, uploadFullUrl, uploadParam } = params;\n const ciphertext = encryptAesEcb(buf, aeskey);\n const url = uploadFullUrl?.trim() ||\n (uploadParam\n ? `${CDN_BASE_URL}/upload?encrypted_query_param=${encodeURIComponent(uploadParam)}&filekey=${encodeURIComponent(filekey)}`\n : \"\");\n if (!url)\n throw new Error(\"upload: 无 upload_full_url/upload_param\");\n let lastErr;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/octet-stream\" },\n body: new Uint8Array(ciphertext),\n signal: AbortSignal.timeout(30000),\n });\n if (res.status >= 400 && res.status < 500)\n throw new Error(`CDN 上传客户端错误 ${res.status}`); // 不重试\n if (res.status !== 200)\n throw new Error(`CDN 上传服务端错误 ${res.status}`);\n const dp = res.headers.get(\"x-encrypted-param\");\n if (!dp)\n throw new Error(\"CDN 上传响应缺 x-encrypted-param\");\n return dp;\n }\n catch (e) {\n lastErr = e;\n if (e instanceof Error && e.message.includes(\"客户端错误\"))\n throw e;\n if (attempt < 3)\n continue;\n }\n }\n throw lastErr instanceof Error ? lastErr : new Error(\"CDN 上传失败\");\n}\n/** getUploadUrl + 加密上传,返回媒体引用所需信息。media_type 见 UploadMediaType。 */\nasync function uploadMediaBytes(params) {\n const { baseUrl, token, to, data, mediaType } = params;\n const rawsize = data.length;\n const rawfilemd5 = createHash(\"md5\").update(data).digest(\"hex\");\n const filesize = aesEcbPaddedSize(rawsize);\n const filekey = randomBytes(16).toString(\"hex\");\n const aeskey = randomBytes(16);\n // 非 0 ret/errcode 已由 getUploadUrl 内部抛 ILinkError,这里直接用\n const up = await getUploadUrl({\n baseUrl,\n token,\n filekey,\n media_type: mediaType,\n to_user_id: to,\n rawsize,\n rawfilemd5,\n filesize,\n no_need_thumb: true,\n aeskey: aeskey.toString(\"hex\"),\n });\n const downloadParam = await uploadBufferToCdn({\n buf: data,\n aeskey,\n filekey,\n uploadFullUrl: up.upload_full_url,\n uploadParam: up.upload_param,\n });\n return { downloadParam, aeskeyHex: aeskey.toString(\"hex\"), filesize, rawsize };\n}\n/** 拼 CDN media 引用(消息里给接收方下载+解密用)。 */\nfunction buildMediaRef(downloadParam, aeskeyHex) {\n return {\n encrypt_query_param: downloadParam,\n aes_key: Buffer.from(aeskeyHex).toString(\"base64\"),\n encrypt_type: 1,\n };\n}\nconst IMAGE_EXTS = new Set([\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".bmp\"]);\nconst VIDEO_EXTS = new Set([\".mp4\", \".mov\", \".webm\", \".mkv\", \".avi\"]);\n/** 按文件名扩展名判定媒体类别(借鉴 wxclawbot media.ts classifyMedia)。其余一律按文件发送。 */\nexport function classifyMediaByName(name) {\n const i = name.lastIndexOf(\".\");\n const ext = i >= 0 ? name.slice(i).toLowerCase() : \"\";\n if (IMAGE_EXTS.has(ext))\n return \"image\";\n if (VIDEO_EXTS.has(ext))\n return \"video\";\n return \"file\";\n}\nconst UPLOAD_TYPE_BY_KIND = {\n image: UploadMediaType.IMAGE,\n video: UploadMediaType.VIDEO,\n file: UploadMediaType.FILE,\n};\n/** 主动发一张图片给用户(需 context_token,与发文本相同)。image 为图片明文 buffer。 */\nexport async function sendImage(params) {\n const { baseUrl, token, to, image, contextToken } = params;\n const { downloadParam, aeskeyHex, filesize } = await uploadMediaBytes({\n baseUrl,\n token,\n to,\n data: image,\n mediaType: UploadMediaType.IMAGE,\n });\n const req = {\n msg: {\n from_user_id: \"\",\n to_user_id: to,\n client_id: generateId(\"img\"),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n item_list: [\n {\n type: MessageItemType.IMAGE,\n image_item: { media: buildMediaRef(downloadParam, aeskeyHex), mid_size: filesize },\n },\n ],\n context_token: contextToken,\n },\n };\n await sendMessage({ baseUrl, token, body: req });\n}\n/**\n * 主动发一个文件/视频/图片给用户(需 context_token)。\n * 类别按 fileName 扩展名自动判定(image/video/file),也可用 kind 显式指定。\n * 借鉴 wxclawbot media.ts 的类型识别与 item 组装;上传链路复用 WeMind 自有实现。\n */\nexport async function sendFile(params) {\n const { baseUrl, token, to, data, fileName, contextToken } = params;\n const kind = params.kind ?? classifyMediaByName(fileName);\n const { downloadParam, aeskeyHex, filesize, rawsize } = await uploadMediaBytes({\n baseUrl,\n token,\n to,\n data,\n mediaType: UPLOAD_TYPE_BY_KIND[kind],\n });\n const media = buildMediaRef(downloadParam, aeskeyHex);\n let item;\n if (kind === \"image\") {\n item = { type: MessageItemType.IMAGE, image_item: { media, mid_size: filesize } };\n }\n else if (kind === \"video\") {\n item = { type: MessageItemType.VIDEO, video_item: { media, video_size: filesize } };\n }\n else {\n item = {\n type: MessageItemType.FILE,\n file_item: { media, file_name: fileName, len: String(rawsize) },\n };\n }\n const req = {\n msg: {\n from_user_id: \"\",\n to_user_id: to,\n client_id: generateId(\"media\"),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n item_list: [item],\n context_token: contextToken,\n },\n };\n await sendMessage({ baseUrl, token, body: req });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\n/**\n * 账号级「大脑」持久配置(brain.json)。\n *\n * 背景:此前「用哪个大脑」只存在于进程环境变量 `WEMIND_BRAIN`(见 brain-agents.ts),是个**临时态**——换个入口重启(登录自启、pm2、后台守护进程、`cli.js start`、另一个 shell)就丢,静默退回默认 agent;而默认 agent(kiro)的二进制不一定装了 → 直到第一条消息才报错(历史上表现为「⏳ 响应中断了,正在自动重试…」)。\n *\n * 这里把选择**按账号写入磁盘**,成为单一事实来源:\n * - 任何入口重启都能复现(不依赖启动者的 shell);\n * - 切换是一等命令(`wemind brain set <agent 名>`),而不是「改环境变量 + 记得重开终端」;\n * - 环境变量 `WEMIND_BRAIN*` 仍为**一次性覆盖**(优先级更高),临时试别的 agent 不受影响。\n *\n * 文件形如 `~/.wemind/<账号>/brain.json`:\n * ```json\n * { \"agent\": \"opencode\", \"worker\": { \"agent\": \"kiro\" },\n * \"presets\": { \"deepseek\": { \"bin\": \"C:\\\\…\\\\dsh.cmd\" } } }\n * ```\n *\n * - 顶层 `agent`(可带 `bin`/`args`/`shell`/`homeEnvVar`/`model` 覆盖)=**对话大脑**,历史语义不变;\n * - `worker` =任务 worker 的独立选择(**缺省=跟随对话大脑**,与拆分角色之前完全一致);\n * - `presets` =**按 agent 名存**的启动覆盖:`wemind brain set X --bin …` 写在这里,于是「切到 opencode 再切回 deepseek」不会丢掉 deepseek 的绝对路径(DSH 桌面端必须显式给 `dsh.cmd`)。`--model` 同样存这里。\n * 顶层那些字段仍会同步写一份(兼容旧版本 CLI 读取),切换 agent 时旧 agent 的覆盖会被迁移进 `presets[旧 agent]`。\n */\n\nexport type AgentRole = \"brain\" | \"worker\";\n\n/** 一个角色的 agent 选择与其启动覆盖。 */\nexport type AgentConfig = {\n /** agent 名(见 brain-agents.ts 的 ACP_AGENT_PRESETS),或 `auto`(自动探测)。 */\n agent: string;\n /** 覆盖二进制(绝对路径或 PATH 上的名字)。 */\n bin?: string;\n /** 覆盖 ACP 启动参数。 */\n args?: string[];\n /** 追加到启动参数末尾(保留预设里的 ACP 模式参数;只加开关时用它,避免整段替换漏掉 `acp`)。 */\n extraArgs?: string[];\n /** 覆盖是否经 shell 启动。 */\n shell?: boolean;\n /** 覆盖「配置目录环境变量名」(指向沙箱)。 */\n homeEnvVar?: string;\n /** 覆盖模型值(会话建立后经 `session/set_config_option` 下发一次);`auto` / `default` = 跟随默认(DSH 从真实 profile 读,优先 `- id: acp`、否则 `agent-default-model`;读不到就不下发)。 */\n model?: string;\n};\n\n/** 按 agent 名保存的启动覆盖。 */\nexport type PresetOverride = Omit<AgentConfig, \"agent\">;\n\nexport type BrainConfig = AgentConfig & {\n /** 任务 worker 的独立选择;缺省=跟随对话大脑。 */\n worker?: AgentConfig;\n /** agent 名 → 启动覆盖。 */\n presets?: Record<string, PresetOverride>;\n};\n\n/** brain.json 的完整路径(默认当前账号数据目录)。 */\nexport function brainConfigPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"brain.json\");\n}\n\n/** 宽松归一化「一块 agent 字段」(顶层与 worker 段共用);无有效 agent 返回 null。 */\nfunction normAgentFields(j: any): AgentConfig | null {\n const agent = typeof j?.agent === \"string\" ? j.agent.trim() : \"\";\n if (!agent) return null;\n const cfg: AgentConfig = { agent };\n if (typeof j.bin === \"string\" && j.bin.trim()) cfg.bin = j.bin.trim();\n if (Array.isArray(j.args))\n cfg.args = j.args.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j.args === \"string\" && j.args.trim()) cfg.args = j.args.trim().split(/\\s+/);\n if (Array.isArray(j.extraArgs))\n cfg.extraArgs = j.extraArgs.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j.extraArgs === \"string\" && j.extraArgs.trim())\n cfg.extraArgs = j.extraArgs.trim().split(/\\s+/);\n if (typeof j.shell === \"boolean\") cfg.shell = j.shell;\n if (typeof j.homeEnvVar === \"string\" && j.homeEnvVar.trim()) cfg.homeEnvVar = j.homeEnvVar.trim();\n if (typeof j.model === \"string\" && j.model.trim()) cfg.model = j.model.trim();\n return cfg;\n}\n\n/** 宽松归一化一条配置覆盖;全空返回 null。 */\nfunction normPresetOverride(j: any): PresetOverride | null {\n const ov: PresetOverride = {};\n if (typeof j?.bin === \"string\" && j.bin.trim()) ov.bin = j.bin.trim();\n if (Array.isArray(j?.args))\n ov.args = j.args.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j?.args === \"string\" && j.args.trim()) ov.args = j.args.trim().split(/\\s+/);\n if (Array.isArray(j?.extraArgs))\n ov.extraArgs = j.extraArgs.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j?.extraArgs === \"string\" && j.extraArgs.trim())\n ov.extraArgs = j.extraArgs.trim().split(/\\s+/);\n if (typeof j?.shell === \"boolean\") ov.shell = j.shell;\n if (typeof j?.homeEnvVar === \"string\" && j.homeEnvVar.trim()) ov.homeEnvVar = j.homeEnvVar.trim();\n if (typeof j?.model === \"string\" && j.model.trim()) ov.model = j.model.trim();\n return Object.keys(ov).length ? ov : null;\n}\n\n/**\n * 读取账号的大脑配置;缺失/损坏/无有效 agent 一律返回 null(幂等、不抛)。\n * 字段做了宽松归一化:args 允许字符串(空格分隔)或数组;未知字段忽略。\n */\nexport function loadBrainConfig(dir: string = DATA_DIR): BrainConfig | null {\n let j: any;\n try {\n j = JSON.parse(fs.readFileSync(brainConfigPath(dir), \"utf-8\"));\n } catch {\n return null;\n }\n if (!j || typeof j !== \"object\" || Array.isArray(j)) return null;\n const base = normAgentFields(j);\n if (!base) return null;\n const cfg: BrainConfig = { ...base };\n const worker = normAgentFields(j.worker);\n if (worker) cfg.worker = worker;\n if (j.presets && typeof j.presets === \"object\" && !Array.isArray(j.presets)) {\n const presets: Record<string, PresetOverride> = {};\n for (const [k, v] of Object.entries(j.presets as Record<string, unknown>)) {\n const ov = normPresetOverride(v);\n if (ov) presets[k.trim().toLowerCase()] = ov;\n }\n if (Object.keys(presets).length) cfg.presets = presets;\n }\n return cfg;\n}\n\n/** 原子写入账号的大脑配置(临时文件 + rename,避免读到半截)。 */\nexport function saveBrainConfig(cfg: BrainConfig, dir: string = DATA_DIR): void {\n fs.mkdirSync(dir, { recursive: true });\n const fp = brainConfigPath(dir);\n const tmp = `${fp}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, fp);\n}\n\n/**\n * 取某角色对应的配置块:worker 未单独设置时**回落到顶层**(拆分角色之前的行为完全不变),\n * 顶层为空则返回 null(交给 resolveAgent 走 auto/默认)。\n */\nexport function agentConfigFor(cfg: BrainConfig | null, role: AgentRole): BrainConfig | null {\n if (!cfg) return null;\n if (role === \"brain\") return cfg;\n if (!cfg.worker?.agent) return cfg; // 没有独立 worker 段:worker 沿用顶层整段(含 presets)\n // presets 是**账号级**的「按 agent 名存的启动覆盖」,与「用哪一段选 agent」无关,必须跟着走:\n // 否则 worker 段的存在会让 presets 查不到,`--bin` 之类的覆盖对 worker 静默失效。\n return cfg.presets ? { ...cfg.worker, presets: cfg.presets } : cfg.worker;\n}\n\n/**\n * 合并一次「设配置」(`wemind brain set` / 切换命令写入磁盘):\n * - patch 的顶层字段整体替换顶层;\n * - 保留 existing 的 worker 段(除非 patch 自带)——旧实现整体覆盖写会把 worker 段删掉;\n * - agent 变了 → 把 existing 顶层还带着的启动覆盖(它们属于**旧** agent)迁进 `presets[旧 agent]`,\n * 避免「切到 opencode 再切回 deepseek」丢掉 deepseek 的 `--bin`;agent 没变则沿用未给定的覆盖;\n * - patch 自带的启动覆盖同时写进 `presets[新 agent]`(并被顶层同步一份,兼容旧版本 CLI)。\n */\nexport function mergeBrainConfig(existing: BrainConfig | null, patch: BrainConfig): BrainConfig {\n const prev = existing?.agent?.trim().toLowerCase() ?? \"\";\n const nextAgent = patch.agent.trim().toLowerCase();\n const presets: Record<string, PresetOverride> = { ...existing?.presets };\n\n if (prev && prev !== nextAgent) {\n const legacy: PresetOverride = {};\n if (existing?.bin) legacy.bin = existing.bin;\n if (existing?.args?.length) legacy.args = existing.args;\n if (existing?.extraArgs?.length) legacy.extraArgs = existing.extraArgs;\n if (existing?.shell !== undefined) legacy.shell = existing.shell;\n if (existing?.homeEnvVar) legacy.homeEnvVar = existing.homeEnvVar;\n if (existing?.model) legacy.model = existing.model;\n if (Object.keys(legacy).length) presets[prev] = { ...presets[prev], ...legacy };\n }\n\n const next: BrainConfig = { agent: patch.agent };\n if (patch.bin) next.bin = patch.bin;\n if (patch.args) next.args = patch.args;\n if (patch.extraArgs) next.extraArgs = patch.extraArgs;\n if (patch.shell !== undefined) next.shell = patch.shell;\n if (patch.homeEnvVar) next.homeEnvVar = patch.homeEnvVar;\n if (patch.model) next.model = patch.model;\n // 同一个 agent 再设一次(如只改了 args):没给的启动覆盖沿用原值——它们本来就属于这个 agent,\n // 丢掉会让「切走再切回」丢 --bin 的坑在原地重现。\n if (prev && prev === nextAgent) {\n if (!patch.bin && existing?.bin) next.bin = existing.bin;\n if (!patch.args && existing?.args?.length) next.args = existing.args;\n if (!patch.extraArgs && existing?.extraArgs?.length) next.extraArgs = existing.extraArgs;\n if (patch.shell === undefined && existing?.shell !== undefined) next.shell = existing.shell;\n if (!patch.homeEnvVar && existing?.homeEnvVar) next.homeEnvVar = existing.homeEnvVar;\n if (!patch.model && existing?.model) next.model = existing.model;\n }\n\n if (\n next.bin ||\n next.args?.length ||\n next.extraArgs?.length ||\n next.shell !== undefined ||\n next.homeEnvVar ||\n next.model\n ) {\n const ov: PresetOverride = { ...presets[nextAgent] };\n if (next.bin) ov.bin = next.bin;\n if (next.args?.length) ov.args = next.args;\n if (next.extraArgs?.length) ov.extraArgs = next.extraArgs;\n if (next.shell !== undefined) ov.shell = next.shell;\n if (next.homeEnvVar) ov.homeEnvVar = next.homeEnvVar;\n if (next.model) ov.model = next.model;\n presets[nextAgent] = ov;\n }\n if (Object.keys(presets).length) next.presets = presets;\n\n const worker = patch.worker ?? existing?.worker;\n if (worker?.agent) next.worker = worker;\n return next;\n}\n\n/** 读-改-写一步到位(manage.ts 用):返回写入磁盘后的配置。 */\nexport function updateBrainConfig(patch: BrainConfig, dir: string = DATA_DIR): BrainConfig {\n const next = mergeBrainConfig(loadBrainConfig(dir), patch);\n saveBrainConfig(next, dir);\n return next;\n}\n\n/** 清除账号的大脑配置(回到自动探测/默认);返回是否确有文件被删。 */\nexport function clearBrainConfig(dir: string = DATA_DIR): boolean {\n try {\n fs.unlinkSync(brainConfigPath(dir));\n return true;\n } catch {\n return false;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Cline ACP 的认证桥接。\n *\n * Cline 的 `isSessionReady()` 只自动恢复它自己的 3 种登录(`cline` / `cline-pass` / `openai-codex`),\n * 自定义/API-key provider(如 `openai-compatible`)不在 `tryRestoreAuth()` 的列表里——即使\n * `data/settings/providers.json` 里已经存在 `lastUsedProvider` 和 `apiKey`,`session/new` 也会报\n * `-32000 Authentication required`。Cline 官方给 ACP 的逃生口是 `CLINE_PROVIDER` + `CLINE_API_KEY`。\n *\n * 这里做一层**只读、按需**的桥接:父进程没有显式提供这两个变量时,把 `lastUsedProvider` 对应的\n * `settings.apiKey` 转成这两个环境变量,从而复用用户已经用 `cline auth` / 手写配置好的登录态。\n *\n * 边界(刻意收窄):\n * - **显式环境变量永远优先**:`CLINE_PROVIDER` 或 `CLINE_API_KEY` 只要有一个已存在,就完全不接管\n * (避免把父进程的 provider 和配置文件里的 key 错配);\n * - 只认 `lastUsedProvider`,且该 provider 必须有非空字符串 `apiKey`;OAuth 条目(只有 `auth`、\n * 没有 `apiKey`)不处理,继续走 Cline 自己的 `tryRestoreAuth()`;\n * - 文件不存在 / JSON 坏了 / 结构不对 → 返回空表,不抛异常(登录态问题仍由原来的认证提示暴露);\n * - 只读不写,不打印 key。\n */\nexport function clineCredentialEnv(\n realHome: string | undefined,\n parentEnv: NodeJS.ProcessEnv,\n): Record<string, string> {\n if (parentEnv.CLINE_PROVIDER?.trim() || parentEnv.CLINE_API_KEY?.trim()) return {};\n if (!realHome) return {};\n\n const file = path.join(realHome, \"data\", \"settings\", \"providers.json\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(file, \"utf-8\"));\n } catch {\n return {};\n }\n if (!isObj(parsed)) return {};\n\n const providerId =\n typeof parsed.lastUsedProvider === \"string\" ? parsed.lastUsedProvider.trim() : \"\";\n if (!providerId) return {};\n\n const providers = isObj(parsed.providers) ? parsed.providers : null;\n const entry = providers && isObj(providers[providerId]) ? providers[providerId] : null;\n const settings = entry && isObj(entry.settings) ? entry.settings : null;\n const apiKey = settings && typeof settings.apiKey === \"string\" ? settings.apiKey.trim() : \"\";\n if (!apiKey) return {};\n\n return { CLINE_PROVIDER: providerId, CLINE_API_KEY: apiKey };\n}\n\nfunction isObj(v: unknown): v is Record<string, any> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * 真实 profile 用户层(`<realHome>/profiles/<profile>/cordis.patch.yml`)的三件事:\n * ① 读默认模型(`readProfileDefaultModel`);② 缺 `- id: acp` 时给用户提示(`acpOverrideHint`);\n * ③ 复制进沙箱前剔除「微信 MCP」条目(`stripWechatMcpEntries`,防回环)。\n * 文件是桌面端维护的 YAML 用户层,结构是一个 patch 列表。这里只做定点抽取(块式与 flow 式映射都认),不引入 YAML 依赖。\n */\n\n/**\n * 从 DSH 真实 home 的 profile 用户层读「默认模型」。\n *\n * 为什么需要(2026-10-29 实测,DSH 0.2.1-rc.3,scripts/acp-probe.mjs):`dsh --profile acp` 的 ACP 桥插件默认模型写死在它\n * **自己的 bundle patch** 里(`- id: acp` → `deepseek-official/deepseek-v4-flash`,该路由要 DEEPSEEK_API_KEY),\n * 而 profile 用户层只覆盖 `llm-pi-ai` / `agent-default-model` / `web-search-deepseek`——**`- id: acp` 由用户自己补**\n * (插件 README 原话:a later patch can replace that row's complete config)。\n *\n * 读的顺序(都只是「读用户自己写的配置」,不替 agent 合成):\n * ① 用户层若已有 `- id: acp` 且带 provider/model → 用它的值(这才是 ACP 路径真正生效的模型);\n * ② 否则退回 `- id: agent-default-model` 的 provider/model(桌面端的默认,等于「你日常怎么用」)。\n * 读不到返回 null(调用方回退到内置预设值 / 不下发)。\n */\nexport function readProfileDefaultModel(realHome: string, profile: string): string | null {\n if (!realHome || !profile) return null;\n let text: string;\n try {\n text = fs.readFileSync(path.join(realHome, \"profiles\", profile, \"cordis.patch.yml\"), \"utf-8\");\n } catch {\n return null;\n }\n const acp = entryConfig(text, /^\\s*-\\s+id:\\s*acp\\s*(#.*)?$/);\n if (acp) return acp;\n return entryConfig(text, /^\\s*-\\s+id:\\s*agent-default-model\\s*(#.*)?$/);\n}\n\n/**\n * 复制进沙箱前剔除「微信 MCP」条目(真实文件不动)。DSH 自己的 MCP 也写在 profile 里\n * (`- id: mcp-xxx` + `name: '@deepseek-ai/dsh-mcp-client'`);如果用户在自己的 dsh 配置里挂过微信 MCP,\n * 原样复制会让沙箱里的 agent 用同一个 bot token 起第二个消费者(抢消息/回环)。\n * 大脑与 worker 的 MCP 都由 WeMind 的激活集权威决定(`wechat` 永远不在里面),所以只剔这一种条目,其余原样保留。\n * 没有命中时返回**原文**(逐字节复制不受影响)。\n */\nexport function stripWechatMcpEntries(text: string): { text: string; removed: string[] } {\n const lines = text.split(/\\r?\\n/);\n const starts: number[] = [];\n for (let i = 0; i < lines.length; i++) if (/^-\\s/.test(lines[i])) starts.push(i);\n const removed: string[] = [];\n const drop = new Set<number>();\n for (let b = 0; b < starts.length; b++) {\n const start = starts[b];\n const end = b + 1 < starts.length ? starts[b + 1] : lines.length;\n const body = lines\n .slice(start, end)\n .filter((l) => !/^\\s*#/.test(l))\n .join(\"\\n\");\n if (!isWechatMcpEntry(body)) continue;\n removed.push(lines[start].trim().replace(/^-\\s+/, \"\").split(/\\s+#/)[0]);\n for (let i = start; i < end; i++) drop.add(i);\n }\n if (!removed.length) return { text, removed };\n const eol = text.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n let out = lines.filter((_, i) => !drop.has(i)).join(eol);\n if (/(\\r\\n|\\n)$/.test(text) && !out.endsWith(eol)) out += eol; // 剔掉的若是最后一条,别把文件末尾的换行也带走\n return { text: out, removed };\n}\n\n/** 一条 patch 条目是不是「微信 MCP」:跑 wemind-mcp 的,或 serverName 为 wechat 的 mcp-client。 */\nfunction isWechatMcpEntry(body: string): boolean {\n if (/wemind-mcp|@wemind\\/wechat-mcp/i.test(body)) return true;\n return (\n /name:\\s*['\"]?@deepseek-ai\\/dsh-mcp-client/.test(body) &&\n /serverName:\\s*['\"]?wechat['\"]?\\s*(#.*)?$/m.test(body)\n );\n}\n\n/** 把真实 profile 用户层复制进某个沙箱 home(复制前剔除微信 MCP 条目;没有命中时逐字节原样)——三个调用方共用同一段路径与剔除逻辑。\n * `src` 不存在时抛错,调用方按 best-effort 包 try/catch。返回被剔除的条目(供调用方记日志)。 */\nexport function copyProfilePatch(realHome: string, profile: string, destHome: string): string[] {\n const src = path.join(realHome, \"profiles\", profile, \"cordis.patch.yml\");\n const dst = path.join(destHome, \"profiles\", profile, \"cordis.patch.yml\");\n const raw = fs.readFileSync(src);\n const { text, removed } = stripWechatMcpEntries(raw.toString(\"utf-8\"));\n const out = removed.length ? Buffer.from(text, \"utf-8\") : raw;\n fs.mkdirSync(path.dirname(dst), { recursive: true });\n const same = fs.existsSync(dst) && fs.readFileSync(dst).equals(out);\n if (!same) fs.writeFileSync(dst, out);\n return removed;\n}\n\n/** 给用户的提示(缺 `- id: acp` 时用):说清后果 + 给出可直接粘贴的片段(值取自 agent-default-model,读不到就给占位)。 */\nexport function acpOverrideHint(realHome: string, profile: string): string | null {\n if (!realHome || !profile) return null;\n const file = path.join(realHome, \"profiles\", profile, \"cordis.patch.yml\");\n let text: string;\n try {\n text = fs.readFileSync(file, \"utf-8\");\n } catch {\n return null; // 还没有这份 profile(dsh 首次启动才生成):先不提示,「改哪份文件」还说不清\n }\n if (/^[ \\t]*-[ \\t]+id:[ \\t]*acp[ \\t]*(#.*)?$/m.test(text)) return null;\n const derived = entryConfig(text, /^\\s*-\\s+id:\\s*agent-default-model\\s*(#.*)?$/);\n const pair = derived ? (JSON.parse(derived) as [string, string]) : null;\n const provider = pair?.[0] ?? \"<provider>\";\n const model = pair?.[1] ?? \"<model>\";\n // 新 profile 是「模板注释 + `[]`」:`[]` 是已写完的 flow 根节点,后面不能再接块序列项——照「加到末尾」写出来是非法 YAML,dsh 直接起不来\n const flowRoot = /^[ \\t]*\\[[ \\t]*\\][ \\t]*(#.*)?$/m.test(text);\n const lines = [\n `提示:${file} 没有配置 - id: acp(ACP 桥的默认模型),终端 dsh --profile ${profile} 与微信大脑都会落到 deepseek-official(要 API key)。`,\n flowRoot\n ? `把文件里 [] 那一行替换成下面这段([] 是已经写完的空列表,后面不能再接条目;替换后它就是你补的第一条),两边行为就一致:`\n : `把下面这段加到那份文件末尾(插件文档写明的覆盖点),两边行为就一致:`,\n `- id: acp`,\n ` config:`,\n ` provider: ${JSON.stringify(provider)}`,\n ` model: ${JSON.stringify(model)}`,\n ];\n if (!pair)\n lines.push(\n `(provider/model 是占位符:换成你日常 dsh 用的路由,比如桌面端 profile 里 llm-pi-ai / agent-default-model 写的那组)`,\n );\n return lines.join(\"\\n\");\n}\n\n/** 读某个 patch 条目(按起始行正则定位)的 config.provider/model;缺一个就 null。 */\nfunction entryConfig(text: string, head: RegExp): string | null {\n let inside = false;\n let provider = \"\";\n let model = \"\";\n for (const line of text.split(/\\r?\\n/)) {\n if (!inside) {\n if (head.test(line)) inside = true;\n continue;\n }\n // patch 列表的下一个条目(顶层 `- ` 或别的 `- id:`)→ 当前条目结束\n if (/^-\\s+/.test(line) || /^\\s*-\\s+id:/.test(line)) break;\n if (!provider) provider = scalar(line, \"provider\");\n if (!model) model = scalar(line, \"model\");\n }\n if (!provider || !model) return null;\n return JSON.stringify([provider, model]);\n}\n\n/** 取 `key: value` 的标量值(去引号与行尾注释);块式与 flow 式都认(`config: {provider: magpie, model: auto/x}` 也是合法 YAML,DSH 接管)。 */\nfunction scalar(line: string, key: string): string {\n const block = new RegExp(`^\\\\s+${key}:\\\\s+(.+)$`).exec(line);\n const m = block ?? new RegExp(`[{,]\\\\s*${key}:\\\\s*(\"[^\"]*\"|'[^']*'|[^,}]+)`).exec(line);\n if (!m) return \"\";\n return m[1]\n .replace(/\\s+#.*$/, \"\")\n .trim()\n .replace(/^[\"']|[\"']$/g, \"\");\n}\n","import path from \"node:path\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\n\nimport { agentConfigFor, type AgentRole, type BrainConfig } from \"./brain-config.js\";\nimport { clineCredentialEnv } from \"./cline-credentials.js\";\nimport { readProfileDefaultModel } from \"./profile-model.js\";\n\n/**\n * ACP agent 与解析。\n *\n * WeMind 的大脑通过**标准 ACP(Agent Client Protocol,JSON-RPC over stdio)**驱动一个 agent CLI。ACP 是通用协议(kiro-cli、gemini-cli 等都支持),因此接入任意兼容 ACP 的 agent 不需要改协议代码——只需告诉我们三件事:① 二进制名;② 进入 ACP 模式的启动参数;③ 该 agent 读取「配置/数据目录」的环境变量名(我们把它指向 brain 沙箱)。\n *\n * 选择与覆盖(优先级从高到低):\n * ① 环境变量(一次性覆盖;临时试别的 agent 用它,别写进 profile):\n * - `WEMIND_BRAIN` 选「对话大脑」agent(默认 `auto`:自动探测已安装的 agent,见 AUTO_PROBE_ORDER)\n * - `WEMIND_WORKER_BRAIN` 选「任务 worker」agent(未设则回落 `WEMIND_BRAIN`;与文件侧 `worker ?? 顶层` 的回退规则对称)\n * - `WEMIND_BRAIN_BIN` 覆盖二进制(绝对路径或 PATH 上的名字)\n * - `WEMIND_BRAIN_ARGS` 覆盖启动参数(空格分隔;**整段替换**)\n * - `WEMIND_BRAIN_EXTRA_ARGS` 追加启动参数(空格分隔;保留预设在前面,适合只加一个开关)\n * - `WEMIND_BRAIN_HOME_ENV` 覆盖「配置目录环境变量名」\n * - `WEMIND_BRAIN_SHELL` 是否经 shell 启动(1/true)\n * - `WEMIND_BRAIN_INHERIT` 是否继承真实配置目录的记忆/规则(默认 1;0 关闭)\n * - `WEMIND_BRAIN_REAL_HOME` 覆盖被继承的真实配置目录(默认取该 agent 的 `realHome`)\n * - `WEMIND_BRAIN_INHERIT_DIRS` 覆盖继承的子目录清单(逗号/空格分隔)\n * - `WEMIND_ACP_SESSION_MODE` 覆盖预设的会话模式(`session/set_mode`):`0`/`false`/`off` 关掉该调用,其它非空值 = 用该 modeId\n * - `KIRO_CLI_BIN` 旧的 kiro 专用覆盖(仅 kiro agent、且未设 WEMIND_BRAIN_BIN 时仍生效)\n * ② 账号级持久配置(`<数据目录>/brain.json`,见 brain-config.ts):顶层 = 对话大脑,`worker` 段 = 任务 worker(缺省跟随顶层);每段可带 `agent` / `bin` / `args` / `shell` / `homeEnvVar`,另有按 agent 名存启动覆盖的 `presets` 段。写入磁盘后任何入口重启都能复现,切换用 `wemind brain set <agent 名>`。\n * ③ `auto`(未配置时的兜底):按 AUTO_PROBE_ORDER 取第一个二进制可解析的 agent;一个都没有则标记 unavailable,由启动校验明确报错(不再「静默跑起来、第一条消息才炸」)。\n * 另:未知的 `WEMIND_BRAIN` 值不再静默回退 kiro,而是 `known:false`,交由启动校验报错。\n *\n * 角色(role):同一份 agent 表既是「对话大脑」也是「任务 worker」的来源;`resolveAgent()` = 对话大脑,\n * `resolveAgentFor(cfg, \"worker\")` = 任务 worker。命令驱动的切换用 `resolvePresetByName()` —— 它**绕开环境变量**,\n * 否则用户点了切换、env 却压着它,会出现「看着成功、实际没生效」。\n */\n\nexport type AcpAgentPreset = {\n name: string;\n bin: string; // PATH 上的二进制名(非 Windows)\n binWin?: string; // Windows 下可执行名(默认在 bin 后加 .exe)\n fallbackBinPaths?: string[]; // PATH 上找不到时的已知安装位置(如官方安装器的固定目录)\n args: string[]; // 进入 ACP 模式的启动参数\n homeEnvVar?: string; // agent 读取其配置/数据目录的环境变量名;指向 brain 沙箱。留空=不设置\n shell?: boolean; // 是否经 shell 启动(npx/.cmd/.bat 类适配器在 Windows 需要 true)\n // —— 继承「记忆/规则/技能」配置(让微信大脑与日常使用读到同样内容)——\n realHome?: string; // 该 agent 真实配置目录(被继承的来源)。不同 agent 布局不同,故下沉到 agent\n inheritDirs?: string[]; // 从 realHome junction 进沙箱的「知识/记忆」类子目录(不含 MCP/会话目录)\n inheritFiles?: string[]; // 从 realHome 拷进沙箱配置目录的「单文件」记忆(如 AGENTS.md;目录式记忆用 inheritDirs)\n // —— 沙箱约定(不同 agent 的配置目录位置与 MCP 文件格式不同)——\n configSubdir?: string; // agent 配置目录相对沙箱根的子路径(kiro 空=沙箱根;opencode \"opencode\")\n mcpFormat?: \"kiro\" | \"opencode\"; // MCP 配置文件的组织格式(默认 kiro)\n mcpConfigFile?: string; // MCP 配置文件相对「配置目录」的路径(默认 settings/mcp.json)\n catalogFile?: string; // 可选 MCP 目录来源文件相对 realHome 的路径(默认 settings/mcp.json)\n env?: Record<string, string>; // 额外注入的环境变量(强制;值里 %HOME% → 沙箱根目录)\n envDefaults?: Record<string, string>; // 默认值:父进程已设同名变量时不覆盖。审批类开关(DSH_PERMISSION_MODE / INITIAL_AGENT_MODE)用它,部署方可用环境变量收紧权限\n credentialEnv?: (\n realHome: string | undefined,\n parentEnv: NodeJS.ProcessEnv,\n ) => Record<string, string>; // 从 agent 自己的凭据文件派生环境变量(仅父进程未显式提供时;如 Cline 自定义 provider 的 CLINE_PROVIDER/CLINE_API_KEY)\n // —— 协议/能力差异(按 agent 覆盖;默认保持 kiro/opencode 旧行为)——\n restore?: \"load\" | \"resume\"; // 恢复会话的方式:load(默认)或 resume(如 DSH)\n firstFrameMs?: number; // 首帧超时覆盖(默认 25s;慢启动/committed-only agent 可放宽)\n heartbeatMs?: number; // 静默期心跳间隔覆盖(默认 15s;有 thought/tool/usage 帧的 agent 通常不需要)\n images?: boolean; // 是否接受图片输入(默认 true;DSH 官方 0.2.1-rc.2 为 false)\n credentialFiles?: string[]; // 需要从 realHome 同步进沙箱的凭据文件(相对配置目录;新者胜)\n identityFiles?: string[]; // 必须与 realHome 保持一致的标识文件(相对配置目录;内容不同即覆盖)\n settingsBase?: string; // 以 realHome 下该文件为基底生成沙箱设置(剥离 MCP/插件/自定义供应商并关自动更新)\n model?: string; // 默认模型值(如 DSH 的 [\"deepseek-account\",\"deepseek-flash\"]):经 session/set_config_option(agent 自己的 ACP 接口)在会话建立后下发一次;可用 brain.json / `brain set --model` 覆盖,`auto` = 跟随默认;配了 realProfile 时默认值优先从真实 profile 读\n realProfile?: string; // 真实 home 里该 agent 的 profile 名(`profiles/<名>/cordis.patch.yml` 是用户/桌面端维护的用户层:沙箱**原样复制**、不追加条目,默认模型从它读——优先 `- id: acp`,否则 `agent-default-model`);如 DSH 的 \"acp\"\n mcpViaAcp?: boolean; // MCP 走 ACP session/new|resume 的 mcpServers(不写配置文件;如 DSH)\n sessionMode?: string; // 会话建立/恢复后经 `session/set_mode` 切到的模式 id(agent 自己的 ACP 接口);用于「免审批」模式:claude-code 的 bypassPermissions、codex 的 agent-full-access。留空=不调用\n};\n\n/**\n * 已知 ACP agent。\n * - `kiro` 为默认且**已实测**。\n * - 其余均为**示例(未实测)**:参数/包名以各自官方为准,接入前请核对(见 docs/接入其它-ACP-Agent.md)。\n * - 区别两类:① **原生 ACP**(CLI 自带 ACP 模式,直接起,如 kiro、gemini);② **需适配器**(CLI 本身不说 ACP,要起一个适配器进程,如 Claude Code、Codex)。\n */\nexport const ACP_AGENT_PRESETS: Record<string, AcpAgentPreset> = {\n // —— 原生 ACP ——\n kiro: {\n name: \"kiro\",\n bin: \"kiro-cli\",\n binWin: \"kiro-cli.exe\",\n args: [\"acp\", \"--trust-all-tools\"],\n homeEnvVar: \"KIRO_HOME\",\n realHome: path.join(os.homedir(), \".kiro\"),\n inheritDirs: [\"steering\", \"memories\", \"skills\", \"agents\", \"powers\", \"tasks\", \"extensions\"],\n },\n // Gemini CLI(原生 ACP,0.66+)。`--acp` 取代已弃用的 `--experimental-acp`;`--approval-mode yolo` 自动批准所有工具调用(与 `--yolo` 互斥,二选一);`--skip-trust` 信任当前工作区——**未信任的目录会把 yolo 静默降回 default**,所以必须带上(或用 GEMINI_CLI_TRUST_WORKSPACE=true)。注:Gemini 的「记忆」是 ~/.gemini/GEMINI.md 等文件而非目录,与 kiro 的目录式不同,故未预置 inheritDirs;需要时按其布局用 WEMIND_BRAIN_REAL_HOME/_INHERIT_DIRS 配置或填此处。\n gemini: {\n name: \"gemini\",\n bin: \"gemini\",\n binWin: \"gemini.cmd\",\n args: [\"--acp\", \"--approval-mode\", \"yolo\", \"--skip-trust\"],\n homeEnvVar: \"GEMINI_CLI_HOME\",\n shell: true,\n },\n // OpenCode:原生 ACP(`opencode acp`)。\n // - Windows 上是 npm shim(PATH 只有 opencode.cmd),故 shell:true + 裸名 binWin。\n // - 配置目录走 XDG_CONFIG_HOME(指向沙箱 → 配置落在 <沙箱>/opencode/),可真正替换默认 global 目录实现隔离;(OPENCODE_CONFIG_DIR 只「追加」不替换 global,无法隔离,故不用它。)\n // - MCP 配置文件是 opencode.json 的 mcp.servers(格式与 kiro 的 settings/mcp.json 不同)。\n // - 记忆/技能为目录式 skills/agents/commands + 单文件 AGENTS.md。\n // 详见 docs/接入-OpenCode.md(含 M1 实测)。\n opencode: {\n name: \"opencode\",\n bin: \"opencode\",\n binWin: \"opencode\",\n args: [\"acp\"],\n shell: true,\n homeEnvVar: \"XDG_CONFIG_HOME\",\n realHome: path.join(os.homedir(), \".config\", \"opencode\"),\n inheritDirs: [\"skills\", \"agents\", \"commands\"],\n inheritFiles: [\"AGENTS.md\"],\n configSubdir: \"opencode\",\n mcpFormat: \"opencode\",\n mcpConfigFile: \"opencode.json\",\n catalogFile: \"opencode.json\",\n env: { OPENCODE_DISABLE_PROJECT_CONFIG: \"1\" }, // 不从 cwd 祖先目录发现用户 opencode.json\n },\n // DeepSeek Harness(官方 ACP profile)。\n // - 桌面端自带 `dsh.cmd`(只有 .cmd,没有无扩展名 dsh),故 binWin 必须写全;\n // - DSH_HOME 指向 brain 沙箱;`.credentials.yaml` 由沙箱同步(桌面登录态);\n // - 无 session/load,用 session/resume;image:false(收图需上层拦截);\n // - **模型:跟着用户自己 dsh 的配置走(WeMind 只读、只复制,不替 agent 合成配置)。** 桌面端把 provider 目录与默认模型写进 `~/.dsh/profiles/<profile>/cordis.patch.yml`。\n // `dsh --profile acp` 的 ACP 桥插件(`@deepseek-ai/dsh-acp`)默认模型写死在它**自己的 bundle patch**里:`- id: acp` 的\n // `config: { provider: deepseek-official, model: deepseek-v4-flash }`;profile 用户层只覆盖 `llm-pi-ai` / `agent-default-model`,\n // `- id: acp` 要靠**用户自己**补(插件 README:a later patch can replace that row's complete config)——这就是\n // 「agent-default-model 写着 magpie、session/new 仍报 deepseek-official、一 prompt 就 no API key」的原因(2026-10-29 拆开 app.asar 核实)。\n // 所以 `realProfile: \"acp\"`:沙箱**原样复制**真实 profile 用户层(否则沙箱是空壳 `[]`,连 magpie 路由都没有;不追加条目),\n // 缺 `- id: acp` 时由 `wemind brain` 提示用户补到真实 profile(`acpOverrideHint`)。\n // 默认模型从这份配置读(优先 `- id: acp`,否则 `agent-default-model`)→ 用户日常用哪个模型,微信大脑就用哪个。\n // - 读不到时回退到下面的 `model`(deepseek-account/deepseek-flash:同样显示为 DeepSeek-V4.1-Flash,走账号登录);\n // 显式换模型用 `wemind brain set deepseek --model \"…\"`,`--model auto` = 跟随真实 profile 的默认。\n // `set_config_option`(agent 自己的 ACP 接口)在会话建立后按同一个值下发一次:幂等,也是显式覆盖的通道。\n // - magpie 路由走桌面端的本机模型网关(127.0.0.1:3425),**桌面端要在运行**——和用户日常 dsh 用 magpie 时的前提一样。\n // 详见 docs/接入-DeepSeek.md(含 0.2.1-rc.2 / 0.2.1-rc.3 真机实测)。\n deepseek: {\n name: \"deepseek\",\n bin: \"dsh\",\n binWin: \"dsh.cmd\",\n args: [\"--profile\", \"acp\"],\n shell: true,\n homeEnvVar: \"DSH_HOME\",\n realHome: path.join(os.homedir(), \".dsh\"),\n inheritDirs: [\"skills\"],\n inheritFiles: [\"AGENTS.md\"],\n credentialFiles: [\".credentials.yaml\"],\n restore: \"resume\",\n firstFrameMs: 60_000,\n heartbeatMs: 60_000,\n images: false,\n realProfile: \"acp\",\n model: '[\"deepseek-account\",\"deepseek-flash\"]',\n mcpViaAcp: true, // 不写 mcp.json;激活集转成 ACP mcpServers 传给 session/new|resume\n // 免审批:DSH 没有启动 flag,也没有会话级权限配置项;`DSH_PERMISSION_MODE` 是官方 bundle 读的默认值来源\n // (文档正文未收录,升级后若失效见 docs/接入-DeepSeek.md),取值 danger-full-access = 绕过文件沙箱、不产生提权请求。\n // 注意 `approval.policy: never` 是「不问并拒绝」,不能单独拿来放行——这里只用 danger-full-access。\n // 放在 envDefaults:daemon 进程里显式设了同名变量(如 workspace-write)时以用户为准(可收紧)。\n envDefaults: { DSH_PERMISSION_MODE: \"danger-full-access\" },\n },\n\n // Qoder CLI(官方原生 ACP,qodercli 1.1.78 实测)。\n // - 直接起 qodercli.exe:`qoder` 是分发器(cmd → powershell → qodercli),多一层管道且会与 IDE 命令混淆;\n // - QODER_CONFIG_DIR 指向 <沙箱>/qoder:与 Qoder Desktop 共用的 ~/.qoder 绝不写入;\n // - 登录态要 .auth/user + .auth/machine_id 两个文件(只带 user 会 Authentication required);\n // - MCP 在 settings.json 的 mcpServers(与其它设置同文件),改后需重启进程 → 加载/卸载 MCP 的 restart 已覆盖;\n // - --yolo 下工具调用不发反向请求;capabilities 有 load/resume/image;\n // - providers(BYOK,含明文密钥)与 model 不继承,见 docs/接入-Qoder.md §5。\n qoder: {\n name: \"qoder\",\n bin: \"qodercli\",\n binWin: \"qodercli.exe\",\n // 官方安装器的固定位置;pm2/服务里 PATH 可能不含它,故直接兜底\n fallbackBinPaths:\n process.platform === \"win32\"\n ? [path.join(os.homedir(), \".qoder\", \"bin\", \"qodercli\", \"qodercli.exe\")]\n : [],\n args: [\"--acp\", \"--yolo\"],\n env: { QODER_CONFIG_DIR: `%HOME%${path.sep}qoder` },\n realHome: path.join(os.homedir(), \".qoder\"),\n // memory/ 是 Qoder agent 的用户画像/偏好记忆(实测存在),与日常使用共用一份(junction);skills/agents/commands/AGENTS.md 在 Qoder 1.1.78 的本机不存在 → 当前为空操作,保留以便上游新增后自动生效\n inheritDirs: [\"memory\", \"skills\", \"agents\", \"commands\"],\n inheritFiles: [\"AGENTS.md\"],\n configSubdir: \"qoder\",\n mcpFormat: \"kiro\",\n mcpConfigFile: \"settings.json\",\n catalogFile: \"settings.json\",\n settingsBase: \"settings.json\",\n credentialFiles: [\".auth/user\"],\n identityFiles: [\".auth/machine_id\"],\n },\n\n // Cline CLI(官方原生 ACP,cline 3.0.74 实测;`cline-acp` 适配器已归档,官方让直接用 `cline --acp`)。\n // - `CLINE_DIR` 指到 <沙箱>/cline(不用沙箱根:大脑 cwd 就是沙箱根,避免把 agent 自己的配置目录当工作区);\n // - 登录态:ACP 只自动恢复它自己的 3 种登录(cline / cline-pass / openai-codex,读 data/settings/providers.json);\n // 自定义/API-key provider(如 openai-compatible)必须由 daemon 进程环境提供 CLINE_PROVIDER + CLINE_API_KEY,\n // 否则 session/new 直接 `-32000 Authentication required`(Cline 不会去看 lastUsedProvider);\n // - MCP:data/settings/cline_mcp_settings.json({mcpServers},与 kiro 同格式);沙箱用 CLINE_MCP_SETTINGS_PATH 指到\n // <沙箱>/cline/mcp.json,真实文件不动。ACP `session/new.mcpServers` 不被 Cline 使用(它只存不用)→ mcpViaAcp 保持 false;\n // - 免审批:`--auto-approve true`(Cline 自己文档给的 ACP 启动开关);宿主应答器仍默认拒绝权限请求;\n // - 能力:loadSession / images 可用,重启后 session/load 能接回上下文;session/close 未实现(-32601,WeMind 不调用)。\n cline: {\n name: \"cline\",\n bin: \"cline\",\n binWin: \"cline.cmd\",\n args: [\"--acp\", \"--auto-approve\", \"true\"],\n shell: true,\n // 3.0.74 实测:initialize 广告 image:true,但 ACP prompt 的 de() 只保留 text 块(image 被直接丢弃),\n // 模型只会看到「用户发来一张图片」的文字提示。按「实际不支持」处理,让 daemon 友好拒绝而不是让模型猜。\n images: false,\n env: {\n CLINE_DIR: `%HOME%${path.sep}cline`,\n CLINE_DATA_DIR: `%HOME%${path.sep}cline${path.sep}data`,\n CLINE_MCP_SETTINGS_PATH: `%HOME%${path.sep}cline${path.sep}mcp.json`,\n },\n realHome: path.join(os.homedir(), \".cline\"),\n // 用户级规则/技能/agent 定义与日常 Cline 共用一份;hooks/plugins 不继承(插件可自带 MCP,沙箱不允许 wechat 之外的意外入口)\n inheritDirs: [\"rules\", \"skills\", \"agents\"],\n configSubdir: \"cline\",\n mcpFormat: \"kiro\",\n mcpConfigFile: \"mcp.json\",\n catalogFile: \"data/settings/cline_mcp_settings.json\",\n // providers.json:登录态(OAuth / API-key);models.json:provider 的模型目录缓存——\n // 缺它时 Cline 的 session/new 会回退到 gpt-4o(自定义 provider 下 prompt 直接报「magpie knows no model \"gpt-4o\"」)\n credentialFiles: [\"data/settings/providers.json\", \"data/settings/models.json\"],\n // 自定义/API-key provider:Cline ACP 不读 providers.json 的 lastUsedProvider,按官方 ACP 逃生口补 env\n // (父进程显式设了 CLINE_PROVIDER/CLINE_API_KEY 时完全不接管;OAuth 条目没有 apiKey,返回空)\n credentialEnv: clineCredentialEnv,\n },\n\n // —— 需适配器(CLI 不原生支持 ACP,经官方/第三方适配器桥接)——\n // Claude Code:官方 ACP 适配器 @agentclientprotocol/claude-agent-acp(bin: claude-agent-acp;**Node ≥22**)。\n // - **全局安装,不用 npx**(口径同 codex):daemon 常驻、worker 每条任务起进程,npx 每次冷启动都要问 registry/现下包,离线不可用且版本漂移;\n // `npm i -g @agentclientprotocol/claude-agent-acp`。适配器自带 @anthropic-ai/claude-agent-sdk(含平台原生二进制);\n // 若装的时候漏了 optional 依赖(native binary not found),可用 CLAUDE_CODE_EXECUTABLE 指向本机已装的 claude 二进制。\n // - 免审批:适配器**不解析** --dangerously-skip-permissions 之类 CLI flag;会话建立/恢复后走 `session/set_mode {modeId:\"bypassPermissions\"}`(见 sessionMode)。\n // 以 root 运行且未设 IS_SANDBOX=1 时该模式不可用——会话回包的 availableModes 里没有它,applySessionMode 会跳过并记日志。\n // - 沙箱:CLAUDE_CONFIG_DIR 指向 <BRAIN_HOME>/claude(实测会把 ~/.claude.json 一起搬进该目录,用户级 mcpServers 不会漏进大脑);\n // CLAUDE.md/skills/agents/commands 与日常使用共享;OAuth 登录态 .credentials.json 同步进沙箱。\n // **不复制 settings.json**:它常含网关 token(env),按 §4.9 密钥由 daemon 进程环境提供;需要网关时设 ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL。\n // - MCP:走 ACP session/new|load 的 mcpServers(mcpViaAcp),不写配置文件;目录取真实 ~/.claude.json 的 mcpServers(`catalogFile` 用 `..` 回到 home)。\n // 探针实测 stdio MCP 可用(尽管 initialize 的 mcpCapabilities 只声明 http/sse——能力字段不可信的又一例)。\n // - 2026-10-30 探针实测(claude-agent-acp 0.99.1 / Claude Code 2.1.314,Windows):\n // loadSession ✅、sessionCapabilities.resume/close/delete/fork/list ✅、image ✅、stdio MCP ✅、bypassPermissions 下 0 反向请求;\n // 重启后 session/load 上下文保留;session/new ~1.2s、首帧 ~2ms、首段正文 ~8–12s。真机微信单轮 E2E 已过(2026-10-31);详见 docs/接入-ClaudeCode.md(重启续聊/MCP/任务待复测)。\n \"claude-code\": {\n name: \"claude-code\",\n bin: \"claude-agent-acp\",\n binWin: \"claude-agent-acp.cmd\",\n args: [],\n shell: true,\n env: { CLAUDE_CONFIG_DIR: `%HOME%${path.sep}claude` },\n realHome: path.join(os.homedir(), \".claude\"),\n inheritDirs: [\"skills\", \"agents\", \"commands\"],\n inheritFiles: [\"CLAUDE.md\"],\n configSubdir: \"claude\",\n // 真实 ~/.claude.json 与 realHome 同级;path.join 后得到 ~/.claude/../.claude.json\n catalogFile: path.join(\"..\", \".claude.json\"),\n credentialFiles: [\".credentials.json\"],\n mcpViaAcp: true,\n sessionMode: \"bypassPermissions\",\n },\n // Codex:社区适配器 codex-acp(agentclientprotocol/codex-acp,npm 包 `@agentclientprotocol/codex-acp`,需自行安装)。\n // codex-acp **不解析** CLI 参数(codex 的 --yolo/--full-auto 都不适用);免审批走 ACP `session/set_mode {modeId:\"agent-full-access\"}`\n // (= approval never + sandbox danger-full-access),并同时用 README 记录的环境变量 INITIAL_AGENT_MODE 兜底(旧版本无 set_mode 时仍生效)。\n // npm 全局安装在 Windows 产出 codex-acp.cmd(无 .exe),故 binWin 写 .cmd + shell(与 npx 适配器同理);standalone 二进制用 --bin 指定。\n codex: {\n name: \"codex\",\n bin: \"codex-acp\",\n binWin: \"codex-acp.cmd\",\n args: [],\n shell: true,\n envDefaults: { INITIAL_AGENT_MODE: \"agent-full-access\" },\n sessionMode: \"agent-full-access\",\n },\n};\n\nfunction onPath(exe: string): string | null {\n for (const dir of (process.env.PATH ?? \"\").split(path.delimiter)) {\n if (dir && fs.existsSync(path.join(dir, exe))) return path.join(dir, exe);\n }\n return null;\n}\n\n/**\n * `auto` 的探测顺序:按序取第一个「二进制能在 PATH 上解析到」的 agent。\n * 只收纳**原生 ACP 且二进制可直接判定**的 agent——适配器类(`claude-code` 走 `claude-agent-acp`、`codex` 走 `codex-acp`)不参与自动探测:\n * 它们是独立安装的桥接进程、各有自己的认证,误选中会让「装了但没登录」的机器静默换脑;显式选用(`wemind brain set|switch <名>`)更安全。\n * 新 agent 一律追加在末尾:老机器上已装的前几个仍按原顺序被选中,不改变既有用户的默认大脑。\n */\nexport const AUTO_PROBE_ORDER = [\n \"kiro\",\n \"opencode\",\n \"gemini\",\n \"deepseek\",\n \"qoder\",\n \"cline\",\n] as const;\n\n/**\n * 该 agent 预设里「进入 ACP 模式」的标记 token:从默认 args 中挑出含 `acp` 的项。\n * 只用于整段替换 args 时的静态提醒;适配器(claude-code / codex)的 args 为空 → 返回空数组(不检查——适配器本体就是 ACP server,没有模式参数)。\n * 不能只查裸 `acp`:qoder 是 `--acp`、gemini 是 `--acp`、deepseek 是 `--profile acp`。\n */\nexport function acpModeTokens(name: string): string[] {\n const preset = ACP_AGENT_PRESETS[name];\n return preset ? preset.args.filter((a) => /acp/i.test(a)) : [];\n}\n\n/**\n * 整段替换 args 时的静态提醒(**只提醒、不拒绝**):新 args 里找不到该 agent 的 ACP 模式标记时返回文案。\n * `binOverridden`(env / brain.json 指定了自定义 bin)时不检查——自定义入口/包装脚本可能自带 ACP 模式,静态判断不可靠。\n */\nexport function acpArgsWarning(\n name: string,\n args: string[],\n binOverridden: boolean,\n): string | null {\n if (binOverridden) return null;\n const markers = acpModeTokens(name);\n if (!markers.length) return null;\n // 宽匹配:任何含 `acp` 的 token 都算(`--experimental-acp` 是 gemini 旧版仍在用的等价开关,不能被判成漏参数)\n if (args.some((a) => /acp/i.test(a))) return null;\n return `args 里没有 ${name} 的 ACP 模式参数(预设是 ${markers.map((m) => `\\`${m}\\``).join(\" / \")})——进程可能起得来但不会说 ACP,首条消息会握手超时。请把它一起写回去,或改用 --extra-args 只追加。`;\n}\n\n/** 运行时诊断:握手失败 / 进程在握手前退出时追加的提示;args 看起来正常、或 bin 是自定义入口时返回空串。 */\nexport function acpArgsHint(ag: ResolvedAgent): string {\n if (ag.binOverridden) return \"\"; // 自定义入口/包装脚本可能自带 ACP 模式,静态判断不可靠\n const markers = acpModeTokens(ag.name);\n if (!markers.length || ag.args.some((a) => /acp/i.test(a))) return \"\";\n return `(当前 args=[${ag.args.join(\" \")}] 里没有 ${markers.map((m) => `\\`${m}\\``).join(\" / \")}——若覆盖过 --args,确认没漏 ACP 模式参数;也可改用 --extra-args 只追加)`;\n}\n\n/** agent 在该平台的「可执行文件名」(用于 PATH 探测)。 */\nfunction presetExe(preset: AcpAgentPreset): string {\n return process.platform === \"win32\" ? (preset.binWin ?? `${preset.bin}.exe`) : preset.bin;\n}\n\n/**\n * 该 agent 的二进制是否能在 PATH 上解析到(只判定、不启动)。\n * 传入 cfg 时,`presets[名].bin` 里存的绝对路径也算「可用」——否则桌面端 DSH 这种「装了但不在 PATH」的 agent,auto 探测会白白跳过它。\n */\nexport function presetAvailable(key: string, cfg?: BrainConfig | null): boolean {\n const preset = ACP_AGENT_PRESETS[key];\n if (!preset) return false;\n const ovBin = cfg?.presets?.[key.toLowerCase()]?.bin;\n if (ovBin) return locateBin(ovBin) !== null;\n return onPath(presetExe(preset)) !== null;\n}\n\n/**\n * 解析一个 bin 说明(绝对/相对路径,或 PATH 上的裸名)到实际文件;解析不到返回 null。\n * 裸名在 Windows 上会补试 `.exe`/`.cmd`,覆盖 npm shim 的常见布局。\n */\nfunction locateBin(bin: string): string | null {\n if (!bin) return null;\n if (bin.includes(\"/\") || bin.includes(path.sep)) return fs.existsSync(bin) ? bin : null;\n const candidates = [bin];\n if (process.platform === \"win32\" && !/\\.[A-Za-z0-9]+$/.test(bin))\n candidates.push(`${bin}.exe`, `${bin}.cmd`);\n for (const c of candidates) {\n const hit = onPath(c);\n if (hit) return hit;\n }\n return null;\n}\n\n/** daemon 因「大脑配置错误」退出时用的退出码;后台守护进程据此停止自动重启(配置错误需人工修,重拉无益)。 */\nexport const BRAIN_CONFIG_EXIT_CODE = 78;\n\nexport type ResolvedAgent = {\n name: string;\n bin: string;\n args: string[];\n homeEnvVar?: string;\n shell: boolean;\n inherit: boolean;\n realHome?: string;\n inheritDirs: string[];\n inheritFiles: string[];\n configSubdir: string;\n mcpFormat: \"kiro\" | \"opencode\";\n mcpConfigFile: string;\n catalogFile: string;\n env: Record<string, string>;\n /** 预设给的「默认环境变量」:父进程已设同名变量时不覆盖(用于审批类开关,部署方可以收紧)。 */\n envDefaults: Record<string, string>;\n /** 从 agent 自己的凭据文件派生环境变量(父进程未显式提供时;如 Cline 自定义 provider)。 */\n credentialEnv?: (\n realHome: string | undefined,\n parentEnv: NodeJS.ProcessEnv,\n ) => Record<string, string>;\n restore?: \"load\" | \"resume\";\n firstFrameMs?: number;\n heartbeatMs?: number;\n images: boolean;\n credentialFiles: string[];\n identityFiles: string[];\n settingsBase?: string;\n model?: string;\n realProfile?: string;\n mcpViaAcp: boolean;\n /** 会话建立/恢复后切到的模式 id(`session/set_mode`);「免审批」用,留空=不调用。 */\n sessionMode?: string;\n /** 请求的原始值(来自 env / brain.json / 缺省 `auto`)。 */\n requestedKey: string;\n /** `requestedKey` 的来源。 */\n source: \"env\" | \"file\" | \"auto\";\n /** `requestedKey` 是否为已知 agent(未知 → false,不再静默回退 kiro)。 */\n known: boolean;\n /** 二进制是否解析到了实际文件(找不到 → false)。 */\n available: boolean;\n /** 解析到的二进制绝对路径(`available` 时才有)。 */\n binPath: string | null;\n /** bin 是否来自覆盖(env / brain.json),而不是预设——静态 ACP 标记检查据此跳过(自定义入口可能自带 ACP 模式)。 */\n binOverridden: boolean;\n};\n\n/** 组装 spawn 用的环境变量:父进程 env → `homeEnvVar` 指向沙箱 → 从凭据文件派生的 env(父进程未提供时)→ 预设强制 `env` → 预设 `envDefaults`(父进程已设同名变量时保留父进程的,`skipped` 收集这些键供调用方记日志)。 */\nexport function agentEnv(\n ag: ResolvedAgent,\n home: string,\n skipped: string[] = [],\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n if (ag.homeEnvVar) env[ag.homeEnvVar] = home; // 把沙箱目录传给 agent\n // 从 agent 自己的凭据文件派生(如 Cline 的 CLINE_PROVIDER/CLINE_API_KEY);helper 内部已保证父进程显式值优先\n for (const [k, v] of Object.entries(ag.credentialEnv?.(ag.realHome, process.env) ?? {})) {\n if (env[k] === undefined) env[k] = v;\n }\n for (const [k, v] of Object.entries(ag.env ?? {})) env[k] = v.replaceAll(\"%HOME%\", home);\n for (const [k, v] of Object.entries(ag.envDefaults ?? {})) {\n if (process.env[k] !== undefined) {\n skipped.push(k);\n continue;\n }\n env[k] = v.replaceAll(\"%HOME%\", home);\n }\n return env;\n}\n\n/** 该角色「选 agent」的环境变量:worker 先看 `WEMIND_WORKER_BRAIN`,未设则回落 `WEMIND_BRAIN`。 */\nfunction envKeyFor(role: AgentRole): string | undefined {\n const key =\n role === \"worker\"\n ? process.env.WEMIND_WORKER_BRAIN?.trim() || process.env.WEMIND_BRAIN?.trim()\n : process.env.WEMIND_BRAIN?.trim();\n return key || undefined;\n}\n\n/** 模型覆盖里的「跟随 agent 默认」写法(`brain set deepseek --model auto`):不指定任何模型。 */\nfunction isDefaultModel(v: string): boolean {\n const s = v.trim().toLowerCase();\n return s === \"auto\" || s === \"default\";\n}\n\n/**\n * 解析某角色要用的 ACP agent(核心实现,见文件头注释的三级优先级)。\n * 覆盖优先级:环境变量 > 本角色那段自带 > `presets[<agent 名>]` > 内置默认。\n * `ignoreEnv`:命令驱动的切换(`resolvePresetByName`)必须绕开 `WEMIND_BRAIN*`,否则会出现「点了切换、env 却压着它」的假成功。\n */\nfunction resolveAgentCore(\n role: AgentRole,\n cfg: BrainConfig | null,\n opts: { ignoreEnv?: boolean } = {},\n): ResolvedAgent {\n const envKey = opts.ignoreEnv ? undefined : envKeyFor(role);\n const roleCfg = agentConfigFor(cfg, role);\n const fileKey = roleCfg?.agent?.trim();\n const requestedKey = (envKey || fileKey || \"auto\").toLowerCase();\n const source: ResolvedAgent[\"source\"] = envKey ? \"env\" : fileKey ? \"file\" : \"auto\";\n\n // 选 agent:auto → 探测第一个可用;未知值 → 标 known:false(结构上退回 kiro 兜底,交启动校验报错)。\n let known = true;\n let key = requestedKey;\n if (key === \"auto\") {\n key = AUTO_PROBE_ORDER.find((k) => presetAvailable(k, cfg)) ?? \"kiro\";\n } else if (!ACP_AGENT_PRESETS[key]) {\n known = false;\n key = \"kiro\";\n }\n const preset = ACP_AGENT_PRESETS[key];\n // 按 agent 名存的启动覆盖:无论谁在用这个 agent 都适用(顶层覆盖只属于「写入磁盘那个 agent」,切走后不该跟过去)。\n const presetOv = cfg?.presets?.[key];\n\n // roleCfg 的 bin/args/shell/homeEnvVar 是**给该段所写那个 agent** 的启动覆盖;环境变量临时切到别的 agent 时不能沿用(否则会拿 deepseek 的 dsh.cmd 去起 qoder)。\n const overrideCfg =\n envKey && roleCfg?.agent?.trim() && roleCfg.agent.trim().toLowerCase() !== requestedKey\n ? null\n : roleCfg;\n\n const shellEnv = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_SHELL?.trim();\n const shell =\n shellEnv !== undefined && shellEnv !== \"\"\n ? shellEnv === \"1\" || shellEnv.toLowerCase() === \"true\"\n : (overrideCfg?.shell ?? presetOv?.shell ?? preset.shell ?? false);\n\n // 二进制:env 覆盖 > (kiro 旧变量) > 本段覆盖 > presets[agent 名] 覆盖 > PATH 解析 > agent 裸名。\n // 经 shell 启动时用裸名(让 shell 从 PATH 解析,避免带空格的全路径在 shell 下出问题)。\n const envBin = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_BIN?.trim();\n const kiroBin =\n opts.ignoreEnv || preset.name !== \"kiro\" ? undefined : process.env.KIRO_CLI_BIN?.trim();\n const explicitBin = envBin || kiroBin || overrideCfg?.bin?.trim() || presetOv?.bin?.trim() || \"\";\n const exe = presetExe(preset);\n const binPath = explicitBin\n ? locateBin(explicitBin)\n : (onPath(exe) ?? (preset.fallbackBinPaths ?? []).find((p) => fs.existsSync(p)) ?? null);\n const bin =\n explicitBin ||\n (shell ? (process.platform === \"win32\" ? exe : preset.bin) : binPath || preset.bin);\n const available = known && binPath !== null;\n\n const argsOverride = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_ARGS?.trim();\n const baseArgs = argsOverride\n ? argsOverride.split(/\\s+/)\n : overrideCfg?.args && overrideCfg.args.length\n ? overrideCfg.args\n : presetOv?.args && presetOv.args.length\n ? presetOv.args\n : preset.args;\n // 追加参数:与 args 的「整段替换」不同,extraArgs 永远拼在后面(保留预设里的 ACP 模式参数)。\n // 来源优先级同 args:env > 本段(该角色)> presets[agent 名]。\n const extraEnv = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_EXTRA_ARGS?.trim();\n const extraArgs = extraEnv\n ? extraEnv.split(/\\s+/)\n : overrideCfg?.extraArgs && overrideCfg.extraArgs.length\n ? overrideCfg.extraArgs\n : presetOv?.extraArgs && presetOv.extraArgs.length\n ? presetOv.extraArgs\n : [];\n const args = [...baseArgs, ...extraArgs];\n\n const homeEnvVar =\n (opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_HOME_ENV?.trim()) ||\n overrideCfg?.homeEnvVar?.trim() ||\n presetOv?.homeEnvVar?.trim() ||\n preset.homeEnvVar;\n\n // 继承「记忆/规则/技能」:来源目录与子目录清单按 agent 而异(下沉到 agent),env 可覆盖\n const inherit =\n (process.env.WEMIND_BRAIN_INHERIT ?? process.env.WECHAT_BRAIN_INHERIT ?? \"1\") !== \"0\";\n const realHome =\n process.env.WEMIND_BRAIN_REAL_HOME?.trim() ||\n process.env.WECHAT_REAL_KIRO_HOME?.trim() ||\n preset.realHome;\n\n // 模型:显式覆盖(本段 > presets[agent 名])优先;`auto`/`default` 或没写 = 跟随默认——\n // 配了 realProfile 的 agent(DSH)从用户真实 profile 读(优先 `- id: acp`,否则 `agent-default-model`;WeMind 只读不写,缺 `- id: acp` 由 `wemind brain` 提示用户补);\n // 读到的值经 `set_config_option` 下发;读不到时:`auto` = 不下发,没写 = 用内置预设值兜底。\n const rawModel = overrideCfg?.model?.trim() || presetOv?.model?.trim() || \"\";\n const explicitModel = rawModel && !isDefaultModel(rawModel) ? rawModel : \"\";\n const derivedModel =\n !explicitModel && preset.realProfile && realHome && inherit\n ? readProfileDefaultModel(realHome, preset.realProfile)\n : null;\n const model =\n explicitModel || derivedModel || (isDefaultModel(rawModel) ? undefined : preset.model);\n const dirsEnv = process.env.WEMIND_BRAIN_INHERIT_DIRS?.trim();\n const inheritDirs = dirsEnv\n ? dirsEnv.split(/[,\\s]+/).filter(Boolean)\n : (preset.inheritDirs ?? []);\n\n // 会话模式:预设默认;`WEMIND_ACP_SESSION_MODE=0/false/off` 关闭(让 agent 回到自己的审批流程),\n // 其它非空值 = 覆盖成该 modeId(自定义 agent 用)。环境变量是一次性覆盖,持久值仍以预设为准。\n const modeEnv = process.env.WEMIND_ACP_SESSION_MODE?.trim();\n const modeOff = !!modeEnv && [\"0\", \"false\", \"off\", \"no\"].includes(modeEnv.toLowerCase());\n const sessionMode = modeOff ? undefined : modeEnv || preset.sessionMode;\n\n return {\n name: preset.name,\n bin,\n args,\n homeEnvVar,\n shell,\n inherit,\n realHome,\n inheritDirs,\n inheritFiles: preset.inheritFiles ?? [],\n configSubdir: preset.configSubdir ?? \"\",\n mcpFormat: preset.mcpFormat ?? \"kiro\",\n mcpConfigFile: preset.mcpConfigFile ?? \"settings/mcp.json\",\n catalogFile: preset.catalogFile ?? \"settings/mcp.json\",\n env: preset.env ?? {},\n envDefaults: preset.envDefaults ?? {},\n credentialEnv: preset.credentialEnv,\n restore: preset.restore,\n firstFrameMs: preset.firstFrameMs,\n heartbeatMs: preset.heartbeatMs,\n images: preset.images ?? true,\n credentialFiles: preset.credentialFiles ?? [],\n identityFiles: preset.identityFiles ?? [],\n settingsBase: preset.settingsBase,\n model,\n realProfile: preset.realProfile,\n mcpViaAcp: preset.mcpViaAcp ?? false,\n sessionMode,\n requestedKey,\n source,\n known,\n available,\n binPath,\n binOverridden: !!explicitBin,\n };\n}\n\n/** 解析当前要用的**对话大脑**:环境变量 > 账号 brain.json 顶层 > auto。 */\nexport function resolveAgent(cfg?: BrainConfig | null): ResolvedAgent {\n return resolveAgentCore(\"brain\", cfg ?? null);\n}\n\n/** 解析某**角色**要用的 ACP agent:`brain` = 对话大脑(同 resolveAgent),`worker` = 任务 worker(`worker` 段 > 顶层)。 */\nexport function resolveAgentFor(\n cfg: BrainConfig | null | undefined,\n role: AgentRole,\n): ResolvedAgent {\n return resolveAgentCore(role, cfg ?? null);\n}\n\n/** 全部 agent 名(`auto` 不在其中:它只是「自动探测」的开关)。 */\nexport function presetNames(): string[] {\n return Object.keys(ACP_AGENT_PRESETS);\n}\n\n/**\n * 按名字解析一个 agent,**绕开环境变量**——供「切换大脑」这类命令使用。\n *\n * 为什么绕开:`WEMIND_BRAIN` 优先级高于 brain.json,命令若也走 env 优先级,会出现「用户点了切换、看着成功,实际还是 env 指的那个」。\n * 命令语义 = 用户此刻的明确意图,应压过一次性环境覆盖(`resolvePresetByName` 的结果由调用方写进 brain.json)。\n * 同理,属于「别的 agent」的启动覆盖不会被沿用;但 `presets[名]` 里按名字存的覆盖会(切走再切回不丢 `--bin`)。\n *\n * 失败返回面向用户的原因(未知 agent / 该 agent 二进制找不到),成功返回可直接 spawn 的 agent。\n */\nexport function resolvePresetByName(\n name: string,\n cfg?: BrainConfig | null,\n): { ok: true; agent: ResolvedAgent } | { ok: false; problem: string } {\n const key = (name ?? \"\").trim().toLowerCase();\n if (!key) return { ok: false, problem: \"需要指定 agent 名(或 `auto` 重新自动探测)。\" };\n const base: BrainConfig = { agent: key, presets: cfg?.presets };\n // 顶层启动覆盖只属于「写入磁盘那个 agent」:目标就是它时带上(例如 `brain set deepseek --bin …` 后切回 deepseek)。\n if (cfg?.agent?.trim().toLowerCase() === key) {\n if (cfg.bin) base.bin = cfg.bin;\n if (cfg.args?.length) base.args = cfg.args;\n if (cfg.extraArgs?.length) base.extraArgs = cfg.extraArgs;\n if (cfg.shell !== undefined) base.shell = cfg.shell;\n if (cfg.homeEnvVar) base.homeEnvVar = cfg.homeEnvVar;\n if (cfg.model) base.model = cfg.model;\n }\n const agent = resolveAgentCore(\"brain\", base, { ignoreEnv: true });\n const problem = agentProblem(agent);\n return problem ? { ok: false, problem } : { ok: true, agent };\n}\n\n/**\n * 适配器类 agent 的安装指引:找不到适配器二进制时**直接给出可粘贴的命令**。\n * 口径:**适配器一律全局安装**(daemon 常驻、worker 每条任务起进程;npx 每次冷启动要联网现下、版本不可复现)。\n */\nconst ADAPTER_INSTALL_HINT: Record<string, string> = {\n \"claude-code\":\n \"`npm i -g @agentclientprotocol/claude-agent-acp`(装完得到 claude-agent-acp;Windows 上是 claude-agent-acp.cmd;**需 Node ≥22**)\",\n};\n\n/**\n * 校验解析后的大脑是否可用:可用返回 null,否则返回一句面向用户的说明。\n * 供 daemon 启动校验(早失败、说人话)与 `wemind brain` / `wemind brain check` 复用。\n */\nexport function agentProblem(ag: ResolvedAgent): string | null {\n if (!ag.known)\n return `未知 agent「${ag.requestedKey}」。可选:${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}(自定义二进制可用 WEMIND_BRAIN_BIN 或 brain set --bin 指定)`;\n if (ag.requestedKey === \"auto\" && !ag.available)\n return `自动探测未找到可用的大脑(已试:${AUTO_PROBE_ORDER.join(\"、\")})。请安装其中之一,或运行 \\`wemind brain set <agent 名>\\``;\n if (!ag.available) {\n const hint = ADAPTER_INSTALL_HINT[ag.name];\n if (hint)\n return `找不到大脑「${ag.name}」的适配器「${ag.bin}」。先装适配器:${hint};装完仍找不到就 \\`wemind brain set ${ag.name} --bin \"<绝对路径>\"\\` 指定它。`;\n return ag.name === \"deepseek\"\n ? `找不到 DeepSeek 的「${ag.bin}」。桌面端通常有 dsh.cmd;把它的绝对路径传给 \\`wemind brain set deepseek --bin \"<...>\\\\dsh.cmd\"\\``\n : ag.name === \"qoder\"\n ? `找不到 Qoder CLI 的「${ag.bin}」。官方安装会放在 ~/.qoder/bin/qodercli/ 并加入 PATH;确认后重开终端,或 \\`wemind brain set qoder --bin \"<...>\\\\qodercli.exe\"\\``\n : ag.name === \"cline\"\n ? `找不到 Cline CLI 的「${ag.bin}」。安装:\\`npm i -g cline\\`(Node 20+),装完后重开终端;或用 \\`wemind brain set cline --bin \"<...>\\\\cline.cmd\"\\``\n : `找不到大脑「${ag.name}」的二进制「${ag.bin}」。安装它,或运行 \\`wemind brain set <agent 名>\\` 切换`;\n }\n return null;\n}\n\n/**\n * 认证失败时按 agent 补一句可执行的建议(`wemind brain check` 与 daemon 日志复用)。\n * Cline 的 ACP 只自动恢复它自己的 3 种登录;自定义 provider 必须靠环境变量——\n * 「重启 daemon」对它没用,所以不能只套用通用的「密钥/鉴权」提示。\n * 其它 agent 返回空串(不改变既有文案)。\n */\nexport function authHint(agentName: string, message: string): string {\n if (agentName !== \"cline\") return \"\";\n if (!/authentication required|authenticate|unauthorized|not authenticated/i.test(message))\n return \"\";\n return \"Cline 的 ACP 只自动恢复 cline / cline-pass / openai-codex 三种登录;用自定义 provider(如 openai-compatible)时,请在启动 wemind 的环境里设置 CLINE_PROVIDER 与 CLINE_API_KEY(设置后重启一次 daemon)。\";\n}\n","import { resolveAgentFor, type ResolvedAgent } from \"./brain-agents.js\";\nimport { loadBrainConfig, type AgentRole, type BrainConfig } from \"./brain-config.js\";\n\n/**\n * 角色的 agent「槽位」:把「当前用哪个 agent」从**模块级常量**(改造前 `acp-brain.ts:25`、\n * `worker-acp.ts:28`、`mcp-sandbox.ts:21`、`worker-sandbox.ts:22` 都是 import 时解析一次就冻死)\n * 变成**运行时可变状态**——否则「热切换大脑」只能靠重启整个 daemon。\n *\n * 两个刻意的设计:\n * - **懒解析 + 记忆化**:模块加载期不读配置,第一次真正用到才解析。沙箱测试靠「先设环境变量、再动态 import」\n * 来构造场景(`sandbox-qoder.test.ts`),加载期解析会让这些用例变成时序敏感。`runDaemon` 开头会\n * `seedSlots()` 显式种入,所以「启动即定」的语义没变。\n * - **变更只有一个入口**:`setAgent` 只由切换流程调用;运行中的回合一律**显式持有并传递**自己的 agent\n * (见 acp-brain 的 `ensureProcess(ag)/ensureSession(ag,…)/promptRequest(ag,…)`),过程内不再读槽位——\n * 于是「握手到一半换了 agent」在结构上不可能发生,不需要加锁。\n */\n\nconst slots = new Map<AgentRole, ResolvedAgent>();\n\n/** 取某角色当前的 agent(懒解析 + 记忆化)。 */\nexport function getAgent(role: AgentRole): ResolvedAgent {\n const hit = slots.get(role);\n if (hit) return hit;\n const ag = resolveAgentFor(loadBrainConfig(), role);\n slots.set(role, ag);\n return ag;\n}\n\n/** 只看不清:没种过返回 undefined(**不**触发解析)。用于「展示当前状态」这类不该有副作用的路径。 */\nexport function peekAgent(role: AgentRole): ResolvedAgent | undefined {\n return slots.get(role);\n}\n\n/** 启动时种入(runDaemon):按给定配置把两个角色定下来。 */\nexport function seedSlots(cfg: BrainConfig | null = loadBrainConfig()): void {\n slots.set(\"brain\", resolveAgentFor(cfg, \"brain\"));\n slots.set(\"worker\", resolveAgentFor(cfg, \"worker\"));\n}\n\n/** 覆盖槽位(切换成功后才调用)。传 null 清掉缓存,下次 `getAgent` 重新解析。 */\nexport function setAgent(role: AgentRole, ag: ResolvedAgent | null): void {\n if (ag) slots.set(role, ag);\n else slots.delete(role);\n}\n\n/** 仅供测试:清空槽位,让下一次解析重新读环境变量与配置。 */\nexport function resetSlotsForTest(): void {\n slots.clear();\n}\n","/**\n * MCP 配置的「格式」适配:不同 ACP agent 把 MCP server 定义放在不同文件、不同结构里。\n * 本模块只做纯数据的组织/解析(无文件 IO),便于单测;沙箱模块负责写入磁盘。\n *\n * - `kiro`:独立文件 `settings/mcp.json`,结构 `{ \"mcpServers\": { 名: {command,args,env} } }`\n * - `opencode`:主配置 `opencode.json`,结构 `{ \"mcp\": { \"servers\": { 名: {type,command,environment} } } }`(与 model 等共处一文件)\n */\nexport type McpFormat = \"kiro\" | \"opencode\";\n\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n\n/** 从配置对象读出 server 定义表(按格式);缺失/异常一律返回空表。 */\nexport function readMcpServers(obj: unknown, format: McpFormat): Record<string, unknown> {\n if (!isObj(obj)) return {};\n const servers =\n format === \"opencode\"\n ? isObj((obj as any).mcp)\n ? (obj as any).mcp.servers\n : undefined\n : (obj as any).mcpServers;\n return isObj(servers) ? servers : {};\n}\n\n/** 把 server 定义表写回配置对象(按格式),返回**新对象**(不改原对象),并保留其它键(如 opencode 的 model)。 */\nexport function withMcpServers(\n obj: unknown,\n servers: Record<string, unknown>,\n format: McpFormat,\n): Record<string, unknown> {\n const base: Record<string, unknown> = isObj(obj) ? { ...obj } : {};\n if (format === \"opencode\") {\n const mcp: Record<string, unknown> = isObj(base.mcp) ? { ...(base.mcp as object) } : {};\n mcp.servers = servers;\n base.mcp = mcp;\n } else {\n base.mcpServers = servers;\n }\n return base;\n}\n\n/**\n * 宽松解析 JSONC(去 // 与块注释、去尾随逗号)。仅用于把用户可能带注释的 opencode.jsonc 读成对象;失败返回 null(调用方按「无配置」处理,不抛)。\n */\nexport function parseJsonc(text: string): Record<string, unknown> | null {\n try {\n return JSON.parse(text) as Record<string, unknown>;\n } catch {\n /* 继续做宽松清洗 */\n }\n try {\n const cleaned = text\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // 块注释\n .replace(/(^|[^:])\\/\\/.*$/gm, \"$1\") // 行注释(避开 http:// 里的 //)\n .replace(/,(\\s*[}\\]])/g, \"$1\"); // 尾随逗号\n const parsed = JSON.parse(cleaned);\n return isObj(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n// ---- ACP 传输:把 catalog 条目转成 session/new 的 mcpServers(配了 mcpViaAcp 的 agent 用) ----\n\n/** ACP `session/new` 的 stdio MCP 声明(env 是 `{name,value}[]`,与 kiro/opencode 的 map 不同)。 */\nexport type AcpMcpServer = {\n name: string;\n command: string;\n args: string[];\n env: Array<{ name: string; value: string }>;\n};\n\n/** ACP `session/new` 的 HTTP MCP 声明(官方 DSH 已广告 http:true;SSE 不支持)。 */\nexport type AcpMcpHttpServer = {\n name: string;\n type: \"http\";\n url: string;\n headers: Array<{ name: string; value: string }>;\n};\n\nfunction envEntries(map: unknown): Array<{ name: string; value: string }> {\n if (!isObj(map)) return [];\n return Object.entries(map)\n .filter(([, v]) => v !== undefined && v !== null)\n .map(([name, v]) => ({ name, value: String(v) }));\n}\n\n/** 该 catalog 条目能否转成 ACP 声明(addMcp 加载前的预校验也用它)。 */\nexport function isConvertibleToAcp(entry: unknown): boolean {\n if (!isObj(entry)) return false;\n if (typeof entry.command === \"string\" && entry.command) return true; // kiro local\n if (Array.isArray(entry.command) && typeof entry.command[0] === \"string\" && entry.command[0])\n return true; // opencode local\n if (typeof entry.url === \"string\" && entry.url) return true; // remote/http\n return false;\n}\n\n/**\n * 把 catalog 的 server 定义表转成 ACP `mcpServers`(stdio / HTTP)。\n * - kiro `{ command, args, env }` → stdio;\n * - opencode `{ type:\"local\", command:[...], environment }` → stdio(拆 command[0]/args);\n * - `{ url }`(kiro 远程 / opencode remote)→ HTTP(SSE 不被 ACP 支持,无法从定义识别,由服务端拒绝)。\n * 返回转换结果与**被跳过的名字**(结构不认识/明显残缺)——调用方记日志,避免坏条目把整个 session 拖挂。\n */\nexport function toAcpMcpServers(\n servers: Record<string, unknown>,\n opts: { exclude?: Iterable<string> } = {},\n): { servers: Array<AcpMcpServer | AcpMcpHttpServer>; skipped: string[] } {\n const exclude = new Set(opts.exclude ?? []);\n const out: Array<AcpMcpServer | AcpMcpHttpServer> = [];\n const skipped: string[] = [];\n for (const [name, entry] of Object.entries(servers)) {\n if (exclude.has(name)) continue;\n if (!isObj(entry)) {\n skipped.push(name);\n continue;\n }\n // opencode local:command 是数组\n if (Array.isArray(entry.command)) {\n const cmd = entry.command[0];\n if (typeof cmd !== \"string\" || !cmd) {\n skipped.push(name);\n continue;\n }\n out.push({\n name,\n command: cmd,\n args: entry.command.slice(1).map(String),\n env: envEntries(entry.environment ?? entry.env),\n });\n continue;\n }\n // kiro local:command 是字符串\n if (typeof entry.command === \"string\" && entry.command) {\n out.push({\n name,\n command: entry.command,\n args: Array.isArray(entry.args) ? entry.args.map(String) : [],\n env: envEntries(entry.env),\n });\n continue;\n }\n // 远程:url → HTTP(header 兼容 map 与 {name,value}[] 两种写法)\n if (typeof entry.url === \"string\" && entry.url) {\n const rawHeaders = entry.headers;\n const headers = isObj(rawHeaders)\n ? envEntries(rawHeaders)\n : Array.isArray(rawHeaders)\n ? rawHeaders\n .filter(\n (h): h is { name: string; value: string } =>\n isObj(h) && typeof (h as any).name === \"string\",\n )\n .map((h) => ({\n name: String((h as any).name),\n value: String((h as any).value ?? \"\"),\n }))\n : [];\n out.push({ name, type: \"http\", url: entry.url, headers });\n continue;\n }\n skipped.push(name);\n }\n return { servers: out, skipped };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\nimport { getAgent } from \"./agent-slot.js\";\nimport { readMcpServers, withMcpServers, parseJsonc, toAcpMcpServers } from \"./agent-mcp.js\";\nimport { copyProfilePatch } from \"./profile-model.js\";\n\n/**\n * brain 的 MCP 沙箱:brain 跑在独立配置目录下,默认不加载任何 MCP。\n * 通过对话指令(加载/卸载/列出)动态调整「激活集」,从全局 MCP 目录取定义写入沙箱。\n * DATA_DIR 从 core 导入(支持 WECHAT_DATA_DIR 覆盖,便于多账号隔离)。\n *\n * 按 agent 适配(见 brain-agents.ts 里的 agent 配置与 docs/接入-OpenCode.md):\n * - 配置目录:kiro = 沙箱根(KIRO_HOME);opencode = <沙箱>/opencode(XDG_CONFIG_HOME)。\n * - MCP 文件:kiro = settings/mcp.json({mcpServers});opencode = opencode.json(mcp.servers)。\n * kiro 行为与改造前完全一致(默认值即 kiro 约定)。\n *\n * agent 不再存成模块常量(改造前是 import 时解析的 `AG`):每个函数显式收 `ag`,\n * 或在入口用 `getAgent(\"brain\")` 取一次当前大脑——热切换后沙箱会写到新 agent 的配置目录。\n */\n\n/** brain 专用沙箱根目录(由 agent 的 homeEnvVar 指向它)。与 agent 无关。 */\nexport const BRAIN_HOME =\n process.env.WECHAT_BRAIN_HOME?.trim() || path.join(DATA_DIR, \"brain-home\");\n\n/** 该 agent 在沙箱里的「配置目录」(kiro=沙箱根;opencode=<沙箱>/opencode)。 */\nfunction brainConfigDir(ag: ResolvedAgent): string {\n return path.join(BRAIN_HOME, ag.configSubdir);\n}\n\n/** MCP 配置文件(相对配置目录,按 agent 的 mcpConfigFile)。 */\nfunction brainMcpJson(ag: ResolvedAgent): string {\n return path.join(brainConfigDir(ag), ag.mcpConfigFile);\n}\n\n/** 激活集持久化文件(与 agent 无关:切脑后再切回,激活的 MCP 仍是那套)。 */\nconst ACTIVE_FILE = path.join(DATA_DIR, \"brain-mcps.json\");\n\n/** 「目录」:可选 MCP 的定义来源。默认取所用 agent 真实配置目录(realHome)下的\n * catalogFile(kiro: settings/mcp.json / opencode: opencode.json);可用 `WEMIND_MCP_CATALOG` / `WECHAT_MCP_CATALOG` 覆盖为任意路径。 */\nfunction catalogPath(ag: ResolvedAgent): string {\n return (\n process.env.WEMIND_MCP_CATALOG?.trim() ||\n process.env.WECHAT_MCP_CATALOG?.trim() ||\n path.join(ag.realHome || path.join(os.homedir(), \".kiro\"), ag.catalogFile)\n );\n}\n\n/** 永不允许 brain 加载(防它把消息发回自己)。 */\nconst FORBIDDEN = new Set([\"wechat\"]);\n\n/**\n * 让 brain 继承所用 agent 真实配置目录的「知识/记忆/技能」:\n * - 目录式(inheritDirs,如 kiro 的 steering/memories/skills、opencode 的 skills/agents/commands)用目录 junction(Windows)/ symlink 指向真实目录;\n * - 单文件式(inheritFiles,如 opencode 的 AGENTS.md)直接拷入配置目录。\n * 使微信里的大脑和你日常使用读到同样的记忆、规则、技能。\n * **MCP(settings/opencode.json 的 mcp 段)与 brain 自有会话(sessions)不照搬**:MCP 必须走沙箱(排除 wechat,防它把消息发回自己),会话不能混。\n */\nfunction mirrorRealKiro(ag: ResolvedAgent): void {\n if (!ag.inherit || !ag.realHome) return;\n const dir = brainConfigDir(ag);\n if (path.resolve(BRAIN_HOME) === path.resolve(ag.realHome)) return; // 安全:别把自己链给自己\n try {\n fs.mkdirSync(dir, { recursive: true });\n } catch {\n /* ignore */\n }\n for (const d of ag.inheritDirs) {\n const target = path.join(ag.realHome, d);\n const link = path.join(dir, d);\n if (!fs.existsSync(target)) continue;\n let present = false;\n try {\n fs.lstatSync(link);\n present = true;\n } catch {\n /* 不存在 */\n }\n if (present) {\n // 已是链接 → 不动;是早期自建的「空」真实目录 → 删掉改建 junction;非空真实目录 → 保留不动(避免误删数据)\n try {\n const st = fs.lstatSync(link);\n if (st.isSymbolicLink()) continue; // 已是链接 → 不动\n if (st.isDirectory()) {\n if (fs.readdirSync(link).length === 0) {\n fs.rmdirSync(link); // 空目录 → 删掉,下面改建 junction\n } else {\n // 非空真实目录(如 kiro 自建 agents 脚手架):把真实目录里「缺失」的文件补拷进来,补全继承(不删已有)\n for (const f of fs.readdirSync(target)) {\n const dst = path.join(link, f);\n const src = path.join(target, f);\n try {\n if (!fs.existsSync(dst) && fs.statSync(src).isFile()) fs.copyFileSync(src, dst);\n } catch {\n /* ignore */\n }\n }\n continue;\n }\n } else continue;\n } catch {\n continue;\n }\n }\n try {\n fs.symlinkSync(target, link, process.platform === \"win32\" ? \"junction\" : \"dir\");\n } catch {\n /* 权限不足等:best-effort 忽略 */\n }\n }\n // 单文件式记忆(如 opencode 的 AGENTS.md):真实目录有、沙箱没有才拷(不覆盖沙箱已有)\n for (const f of ag.inheritFiles) {\n const src = path.join(ag.realHome, f);\n const dst = path.join(dir, f);\n try {\n if (fs.existsSync(src) && !fs.existsSync(dst)) fs.copyFileSync(src, dst);\n } catch {\n /* ignore */\n }\n }\n}\n\nfunction readJson(file: string): Record<string, unknown> | null {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * 把 agent 的凭据文件同步进沙箱(如 DSH 的 `.credentials.yaml`、Qoder 的 `.auth/user`)。\n * - `newer`:首次复制;真实 home 的 token 刷新后覆盖沙箱;沙箱自己刷新过则保留沙箱版本。\n * - `always`:内容不同即覆盖(标识类文件,如 Qoder 的 `.auth/machine_id`——agent 在缺凭据时会自造一个,它比真实文件新,「新者胜」会把错的留下,导致永远认证失败)。\n * 这是敏感文件,仅放在账号沙箱内;Windows 无 POSIX 600,建议专用账号 + 目录 ACL。\n */\nfunction syncCredentialFile(ag: ResolvedAgent, file: string, mode: \"newer\" | \"always\"): void {\n if (!ag.realHome) return;\n const src = path.join(ag.realHome, file);\n const dst = path.join(brainConfigDir(ag), file);\n try {\n if (!fs.existsSync(src)) return;\n fs.mkdirSync(path.dirname(dst), { recursive: true });\n if (mode === \"always\") {\n const same = fs.existsSync(dst) && fs.readFileSync(src).equals(fs.readFileSync(dst));\n if (!same) fs.copyFileSync(src, dst);\n return;\n }\n const s = fs.statSync(src);\n const d = fs.existsSync(dst) ? fs.statSync(dst) : null;\n if (!d || s.mtimeMs > d.mtimeMs + 1000) fs.copyFileSync(src, dst);\n } catch {\n /* best-effort */\n }\n}\n\nfunction syncCredentials(ag: ResolvedAgent): void {\n for (const f of ag.credentialFiles) syncCredentialFile(ag, f, \"newer\");\n for (const f of ag.identityFiles) syncCredentialFile(ag, f, \"always\");\n}\n\nfunction loadCatalog(ag: ResolvedAgent): Record<string, unknown> {\n return readMcpServers(readJson(catalogPath(ag)), ag.mcpFormat);\n}\n\n/** 目录中可选的 server 名(排除禁用项)。 */\nexport function catalogNames(): string[] {\n const ag = getAgent(\"brain\");\n return Object.keys(loadCatalog(ag)).filter((n) => !FORBIDDEN.has(n));\n}\n\nexport function loadActive(): string[] {\n const j = readJson(ACTIVE_FILE);\n const a = j?.active;\n return Array.isArray(a) ? (a as string[]).filter((x) => typeof x === \"string\") : [];\n}\n\nfunction saveActive(list: string[]): void {\n fs.mkdirSync(DATA_DIR, { recursive: true });\n fs.writeFileSync(ACTIVE_FILE, JSON.stringify({ active: list }, null, 2), \"utf-8\");\n}\n\n/** 按激活集从目录取定义,写入沙箱 MCP 配置(默认空)。保留配置里其它键(如 opencode 的 model)。\n * 配了 `mcpViaAcp` 的 agent(DSH)不写文件——激活集经 activeMcpServers() 转成 ACP mcpServers 随 session 下发。 */\nfunction applySandbox(ag: ResolvedAgent): void {\n if (ag.mcpViaAcp) return;\n const cat = loadCatalog(ag);\n const active = loadActive().filter((n) => cat[n] && !FORBIDDEN.has(n));\n const servers: Record<string, unknown> = {};\n for (const n of active) servers[n] = cat[n];\n const file = brainMcpJson(ag);\n const existing = readJson(file) ?? {};\n const next = withMcpServers(existing, servers, ag.mcpFormat);\n // opencode 的免审批只能走配置(`opencode acp` 不接受 --auto,官方 issue #52216):默认给 \"allow\";\n // 用户自己在 opencode.json 里写了 permission(含 deny)就以用户的为准,不覆盖。\n if (ag.mcpFormat === \"opencode\" && next.permission === undefined) next.permission = \"allow\";\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(next, null, 2), \"utf-8\");\n}\n\n/** brain 当前应挂载的 ACP mcpServers(配了 mcpViaAcp 的 agent 用):激活集 → catalog 定义 → ACP 声明。\n * 跳过不可转换的坏条目并记日志——官方 DSH 的 session/new 遇到起不来的 MCP 会回滚整个会话。 */\nexport function activeMcpServers() {\n const ag = getAgent(\"brain\");\n const cat = loadCatalog(ag);\n const active = loadActive().filter((n) => cat[n] && !FORBIDDEN.has(n));\n const picked: Record<string, unknown> = {};\n for (const n of active) picked[n] = cat[n];\n const { servers, skipped } = toAcpMcpServers(picked, { exclude: FORBIDDEN });\n if (skipped.length)\n console.error(`[wemind/wechat-daemon/mcp] 跳过无法转换的 MCP:${skipped.join(\", \")}`);\n return servers;\n}\n\n/** kiro:继承真实 settings/ 下除 mcp.json 外的文件(cli.json 含模型/免确认等),让大脑与日常使用同款配置。 */\nfunction syncKiroSettings(ag: ResolvedAgent): void {\n const realSettings = path.join(ag.realHome!, \"settings\");\n const brainSettings = path.join(brainConfigDir(ag), \"settings\");\n if (!fs.existsSync(realSettings)) return;\n try {\n fs.mkdirSync(brainSettings, { recursive: true });\n } catch {\n /* ignore */\n }\n let names: string[] = [];\n try {\n names = fs.readdirSync(realSettings);\n } catch {\n return;\n }\n for (const f of names) {\n if (f === \"mcp.json\") continue; // MCP 必须用沙箱 curated 版本(防 wechat 回环)\n try {\n const src = path.join(realSettings, f);\n if (fs.statSync(src).isFile()) fs.copyFileSync(src, path.join(brainSettings, f));\n } catch {\n /* best-effort */\n }\n }\n}\n\n/** opencode:把用户全局 opencode.json(c)(去掉 mcp 段)作为基底写进沙箱,继承 model 等设置;mcp 仍由 applySandbox 权威覆盖。 */\nfunction syncOpencodeBase(ag: ResolvedAgent): void {\n const src = [\"opencode.json\", \"opencode.jsonc\"]\n .map((f) => path.join(ag.realHome!, f))\n .find((f) => fs.existsSync(f));\n if (!src) return;\n let parsed: Record<string, unknown> | null = null;\n try {\n const raw = fs.readFileSync(src, \"utf-8\");\n parsed = src.endsWith(\".jsonc\")\n ? parseJsonc(raw)\n : (JSON.parse(raw) as Record<string, unknown>);\n } catch {\n return;\n }\n if (!parsed || typeof parsed !== \"object\") return;\n delete (parsed as Record<string, unknown>).mcp; // MCP 必须走沙箱 curated(防 wechat 回环)\n const file = brainMcpJson(ag);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(parsed, null, 2), \"utf-8\");\n}\n\n/** Qoder 这类「设置与 MCP 同文件」的 agent:以真实 settings.json 为基底生成沙箱设置。\n * 剥离必须由沙箱权威决定的键:\n * - `mcpServers`:MCP 只能来自沙箱 curated(防 wechat 回环);\n * - `enabledPlugins`:插件可自带 MCP,且沙箱不继承 `plugins/`;\n * - `providers` / `model`:自定义供应商(BYOK)含明文密钥,`model` 又可能指向已剥离的供应商。\n * 并强制 `general.enableAutoUpdate=false`:否则每个沙箱都会各下一份约 210 MB 的 CLI。 */\nfunction settingsBase(ag: ResolvedAgent): Record<string, unknown> {\n let parsed: unknown = null;\n try {\n parsed = JSON.parse(fs.readFileSync(path.join(ag.realHome!, ag.settingsBase!), \"utf-8\"));\n } catch {\n /* 缺失/损坏 → 空基底 */\n }\n const base =\n parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? { ...(parsed as Record<string, unknown>) }\n : {};\n delete base.mcpServers;\n delete base.enabledPlugins;\n delete base.providers;\n delete base.model;\n const general = base.general;\n base.general = {\n ...(general && typeof general === \"object\" && !Array.isArray(general)\n ? (general as Record<string, unknown>)\n : {}),\n enableAutoUpdate: false,\n };\n return base;\n}\n\nfunction syncSettingsBase(ag: ResolvedAgent): void {\n const dir = brainConfigDir(ag);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(brainMcpJson(ag), JSON.stringify(settingsBase(ag), null, 2), \"utf-8\");\n}\n\n/** 继承真实 profile 的用户层(DSH:`profiles/<名>/cordis.patch.yml`,桌面端写 provider 目录与默认模型)。\n * **原样复制**——这份文件是用户自己的配置,WeMind 只镜像、不合成(`- id: acp` 这类覆盖由用户自己维护,\n * 缺失时 `wemind brain` 会提示怎么补;见 `profile-model.ts` 的 `acpOverrideHint`),这样「终端 dsh」与「微信大脑」行为一致。\n * 按内容同步(桌面端更新后覆盖);**唯独剔除用户自己挂的微信 MCP 条目**(`stripWechatMcpEntries`):\n * 沙箱里的 MCP 只能是 WeMind 的激活集(`wechat` 永远不在里面),否则 agent 会拿同一个 bot token 起第二个消费者。 */\nfunction syncProfilePatch(ag: ResolvedAgent): void {\n if (!ag.realProfile || !ag.realHome || !ag.inherit) return;\n if (path.resolve(BRAIN_HOME) === path.resolve(ag.realHome)) return; // 沙箱就是真实目录时不自我覆盖\n const src = path.join(ag.realHome, \"profiles\", ag.realProfile, \"cordis.patch.yml\");\n try {\n if (!fs.existsSync(src)) return;\n const removed = copyProfilePatch(ag.realHome, ag.realProfile, BRAIN_HOME);\n if (removed.length) {\n console.error(\n `[wemind/wechat-daemon/mcp] 真实 profile 里的微信 MCP 已从大脑沙箱副本剔除(防回环,真实文件未改):${removed.join(\", \")}`,\n );\n }\n } catch {\n /* best-effort */\n }\n}\n\n/** 继承真实配置目录的「非 MCP」设置(模型/免确认等),让大脑与日常使用同款配置;mcp 段保持沙箱权威。\n * 配了 `mcpViaAcp` 的 agent 也仍有「profile 用户层」可继承(DSH 的 provider 目录/默认模型),只是 MCP 不写文件。 */\nfunction syncSettings(ag: ResolvedAgent): void {\n syncProfilePatch(ag);\n if (ag.mcpViaAcp) return;\n if (ag.settingsBase && ag.realHome) {\n syncSettingsBase(ag); // 不依赖 inherit:沙箱硬化(关自动更新)与隔离始终生效\n return;\n }\n if (!ag.inherit || !ag.realHome) return;\n if (ag.mcpFormat === \"opencode\") syncOpencodeBase(ag);\n else syncKiroSettings(ag);\n}\n\n/**\n * 启动时确保沙箱存在并与激活集一致。\n * `forAgent` 可显式指定 agent(切换流程启动新大脑用:启动新大脑时要先备好**它**的沙箱,此刻槽位还指着旧大脑)。\n */\nexport function ensureBrainHome(forAgent?: ResolvedAgent): void {\n const ag = forAgent ?? getAgent(\"brain\");\n syncSettings(ag); // 先同步「非 MCP」设置(模型/免确认等),让大脑与日常使用同款配置\n applySandbox(ag); // 再写 curated mcp(权威,保持 MCP 隔离——防它把消息发回自己)\n mirrorRealKiro(ag); // 继承真实配置目录的记忆/steering/skills(sessions 仍隔离)\n syncCredentials(ag); // 登录态/凭据(如 DSH 的 .credentials.yaml、Qoder 的 .auth/*)\n}\n\nexport function addMcp(name: string): string {\n const ag = getAgent(\"brain\");\n if (FORBIDDEN.has(name)) return `不允许加载「${name}」(防它把消息发回自己)。`;\n const cat = loadCatalog(ag);\n if (!cat[name]) return `未找到 MCP「${name}」。可选:${catalogNames().join(\", \") || \"(无)\"}`;\n // 预校验:坏条目不进激活集(官方 DSH 的 session/new 会因起不来的 MCP 回滚整会话)\n if (ag.mcpViaAcp && !toAcpMcpServers({ [name]: cat[name] }).servers.length)\n return `MCP「${name}」的定义无法转换(需要 command 或 url),已拒绝加载。`;\n const a = loadActive();\n if (!a.includes(name)) {\n a.push(name);\n saveActive(a);\n }\n applySandbox(ag);\n return `已加载 MCP「${name}」。当前激活:${a.join(\", \")}(下一条消息生效)`;\n}\n\nexport function removeMcp(name: string): string {\n const a = loadActive();\n if (!a.includes(name)) return `「${name}」当前未激活。`;\n const next = a.filter((x) => x !== name);\n saveActive(next);\n applySandbox(getAgent(\"brain\"));\n return `已卸载 MCP「${name}」。当前激活:${next.join(\", \") || \"(空)\"}`;\n}\n\nexport function listMcp(): string {\n const avail = catalogNames();\n const active = loadActive();\n return `可选 MCP:${avail.join(\", \") || \"(无)\"}\\n已激活:${active.join(\", \") || \"(空,默认不加载)\"}`;\n}\n","// 进程身份校验:pid 会复用(尤其**重启后**),只凭「pid 还活着」(process.kill(pid, 0))就杀进程、或判定「daemon 在跑」,可能**误杀别人的进程**(陈旧 pid 文件 + pid 被复用)或**误判**(自启看似成功、其实压根没起来)。这里改用「命令行里有没有我们自己的特征串」来确认身份。\n//\n// 原则:查不到命令行 → 一律判否(保守,宁可不杀)。\n// 成本:只有 Windows 需要经 PowerShell 查 CIM(约 1~2s),所以调用方**先 isAlive() 过滤**——正常路径(陈旧 pid 早就不存在了)零开销,只有「确实有活着的 pid」时才付这笔查询成本。\nimport { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/** pid 是否活着(零成本,仅探活;不代表是我们的进程)。 */\nexport function isAlive(pid: number | null | undefined): boolean {\n if (pid === null || pid === undefined || !Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** 归一化:小写 + 反斜杠统一成正斜杠,便于跨平台、跨相对/绝对路径匹配。 */\nfunction normalize(s: string): string {\n return s.replaceAll(\"\\\\\", \"/\").toLowerCase();\n}\n\n/**\n * 取文件名。**不依赖平台**:`/` 与 `\\` 都当分隔符——POSIX 的 `path.basename` 不认 `\\`,会把 Windows 风格路径整条留下(CI 上踩过)。\n */\nfunction baseName(p: string): string {\n const i = Math.max(p.lastIndexOf(\"/\"), p.lastIndexOf(\"\\\\\"));\n return i >= 0 ? p.slice(i + 1) : p;\n}\n\n/** 某条命令行是否命中「任一 marker 组合」(组合内每个 token 都必须出现、大小写与斜杠不敏感)。 */\nexport function matchesMarkers(line: string | null | undefined, markerSets: string[][]): boolean {\n if (!line) return false;\n const hay = normalize(line);\n return markerSets.some((set) => set.length > 0 && set.every((t) => hay.includes(normalize(t))));\n}\n\nfunction parsePipePairs(stdout: string, out: Map<number, string>): void {\n for (const raw of stdout.split(/\\r?\\n/)) {\n const i = raw.indexOf(\"|\");\n if (i <= 0) continue;\n const pid = Number.parseInt(raw.slice(0, i).trim(), 10);\n if (Number.isInteger(pid) && pid > 0) out.set(pid, raw.slice(i + 1));\n }\n}\n\n/**\n * 批量查询 pid → 命令行。查不到(不存在 / 无权限 / 平台不支持)的 pid 不会出现在结果里。\n * 只查询「活着的」pid(调用方传一堆陈旧 pid 也不会白白起进程)。\n */\nexport function commandLines(pids: Array<number | null | undefined>): Map<number, string> {\n const out = new Map<number, string>();\n const list = [...new Set(pids.filter((p): p is number => isAlive(p)))];\n if (!list.length) return out;\n try {\n if (process.platform === \"win32\") {\n // wmic 已被新版 Windows 移除 → 用系统自带的 Windows PowerShell 5.1 + CIM;一次查询多个 pid。\n const ps = path.join(\n process.env.SystemRoot ?? \"C:\\\\Windows\",\n \"System32\",\n \"WindowsPowerShell\",\n \"v1.0\",\n \"powershell.exe\",\n );\n const filter = list.map((p) => `ProcessId=${p}`).join(\" or \");\n const script = `Get-CimInstance Win32_Process -Filter \"${filter}\" | ForEach-Object { \"$($_.ProcessId)|$($_.CommandLine)\" }`;\n const r = spawnSync(ps, [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n encoding: \"utf8\",\n windowsHide: true,\n timeout: 15000,\n });\n parsePipePairs(r.stdout ?? \"\", out);\n } else if (process.platform === \"linux\") {\n for (const p of list) {\n try {\n out.set(p, fs.readFileSync(`/proc/${p}/cmdline`, \"utf-8\").replaceAll(\"\\0\", \" \").trim());\n } catch {\n /* 读不到就当查不到 */\n }\n }\n } else {\n // darwin 等:用 ps\n for (const p of list) {\n const r = spawnSync(\"ps\", [\"-o\", \"command=\", \"-p\", String(p)], {\n encoding: \"utf8\",\n timeout: 5000,\n });\n const line = (r.stdout ?? \"\").trim();\n if (line) out.set(p, line);\n }\n }\n } catch {\n /* 查询失败 → 返回空(判否,保守) */\n }\n return out;\n}\n\n/** 该 pid 是否「活着**且确实是我们自己的**进程」(命令行命中 markerSets 之一)。 */\nexport function isOurProcess(pid: number | null | undefined, markerSets: string[][]): boolean {\n if (!isAlive(pid)) return false;\n return matchesMarkers(commandLines([pid]).get(pid as number), markerSets);\n}\n\n/**\n * 我们自己的 CLI / wrapper 进程特征:命令行里同时含 `wemind` + `cli.js`。\n *\n * 这串必须同时覆盖**四种**安装方式(少一种 → 对应场景下 stop/list/单实例判断全失效):\n * - 源码仓库(绝对路径):`<node> D:\\repo\\WeMind\\packages\\wechat-daemon\\dist\\cli.js daemon`(由 marker 组合里的 `wemind` 命中仓库名 WeMind;注意大小写不敏感比较在下面已处理);\n * - 源码仓库(**相对路径**):`<node> packages/wechat-daemon/dist/cli.js daemon`(命令行里既没有仓库名也没有包名,只有源码目录名 → 靠第三组 marker `wechat-daemon` + `cli.js` 命中)。没有这一组时,「在仓库根目录手跑 CLI + daemon」会互相认不出(daemon 明明活着,CLI 却说没运行,控制桥也拒绝发送);\n * - 全局/本地安装:`<node> …/node_modules/@wemind/cli/dist/cli.js daemon`(npm 的 bin shim 最终 exec 的正是这个路径;Windows 上可能走 `…\\@wemind\\cli\\dist\\cli.js` 短路径。两种形态都命中 marker 里的 `wemind`——它同时是 scope 名与命令名);\n * - 源码开发期的 PowerShell wrapper:`pwsh -File …\\scripts\\run-daemon.ps1`。\n *\n * 注意**不能**用 `wechat-daemon` **替代** `wemind`:源码目录名在发布版里不存在(包目录叫 `@wemind/cli`),只有它会让「装出来的 wemind」永远匹配不上、list 永远显示「已停止」。它只作为**追加**的一组,补上「相对路径启动」这一种形态;并要求 `dist` 一起出现,把「别的项目里恰好有个叫 wechat-daemon 的目录」这类误判(这串最终用于**杀进程**,宁可漏也要少误)。\n */\nexport const CLI_PID_MARKERS: string[][] = [\n [\"wemind\", \"cli.js\"],\n [\"wechat-daemon\", \"dist\", \"cli.js\"],\n [\"run-daemon.ps1\"],\n];\n\n/**\n * 大脑进程特征:`<bin> <args…>`(如 `opencode acp`、`kiro-cli acp`)。\n * 取 bin 的**文件名**并去掉扩展名,兼顾 shell=true(命令行是原始命令)与 `--bin` 绝对路径两种情况;args 是关键区分位——用户自己的 `opencode serve`(本机 OpenCode 服务)不含 `acp`,不会被误判。\n * bin 为空(配置异常)时返回空数组 → matchesMarkers 恒判否,绝不误杀。\n */\nexport function brainPidMarkers(bin: string, args: string[]): string[][] {\n const name = baseName(bin ?? \"\").replace(/\\.(exe|cmd|bat|ps1)$/i, \"\");\n if (!name) return []; // bin 为空 / 只剩目录(配置异常)→ 恒判否,绝不误杀\n return [[name, ...args].filter((t) => t !== \"\")];\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { brainPidMarkers } from \"./proc-identity.js\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\n\n/**\n * ACP 进程的「身份记录」文件(`brain.pid` / `workers.pid.json`)。\n *\n * 为什么从裸 pid 改成 JSON 记录:裸 pid 只说明「有个进程活着」,说明不了「它是不是我们起的、用的是哪个 agent」。\n * `manage.ts` 过去用**配置里**的 agent 算进程特征串(bin+args)来判定/杀进程,这在三种情况下会失效:\n * ① `wemind brain set X` 之后(配置已变、还跑着旧 agent 的进程);\n * ② 热切换大脑的窗口内;\n * ③ **多任务/多 agent 并存**时,live worker 永远不等于单个配置值(多 worker 之后这是常态)。\n * 于是把「起进程时**真实用的** bin/args」写进记录,`manage.ts` 直接按记录算特征串,不再猜。\n *\n * 兼容:旧文件是裸整数(`12345`)→ 读成一条 `legacy: true` 的记录(无 bin/args),\n * 调用方回退到「按配置算特征串」的旧行为;旧版 CLI 读新文件会拿到 NaN → 显示「已停止」且**不杀**(安全侧,记 CHANGELOG)。\n */\n\nexport type PidState = \"busy\" | \"idle\";\n\nexport type PidRecord = {\n pid: number;\n /** agent 名。 */\n agent: string;\n /** 起进程时真实用的二进制与参数(算特征串用)。 */\n bin: string;\n args: string[];\n shell: boolean;\n /** 任务 worker 专有:当前执行的任务编号。 */\n taskId?: number;\n /** 池中状态(worker);brain 恒为 busy。 */\n state: PidState;\n startedAt: number;\n /** 最近一次写入磁盘时间(状态变化时刷新)。 */\n writtenAt: number;\n /** 来自旧版「裸 pid」文件:没有 bin/args,判定需回退到配置。 */\n legacy?: boolean;\n};\n\n/** 读 pid 记录文件:兼容「裸整数」「单个 JSON 对象」「JSON 数组」三种形态;读不到/损坏 → 空数组。 */\nexport function readPidRecords(file: string): PidRecord[] {\n let raw: string;\n try {\n raw = fs.readFileSync(file, \"utf-8\").trim();\n } catch {\n return [];\n }\n if (!raw) return [];\n if (/^\\d+$/.test(raw)) {\n const pid = Number(raw);\n return pid > 0\n ? [\n {\n pid,\n agent: \"\",\n bin: \"\",\n args: [],\n shell: false,\n state: \"busy\",\n startedAt: 0,\n writtenAt: 0,\n legacy: true,\n },\n ]\n : [];\n }\n let j: unknown;\n try {\n j = JSON.parse(raw);\n } catch {\n return [];\n }\n const arr = Array.isArray(j) ? j : [j];\n const out: PidRecord[] = [];\n for (const e of arr as Array<Record<string, unknown>>) {\n const pid = Number(e?.pid);\n if (!Number.isInteger(pid) || pid <= 0) continue;\n const rec: PidRecord = {\n pid,\n agent: typeof e.agent === \"string\" ? e.agent : \"\",\n bin: typeof e.bin === \"string\" ? e.bin : \"\",\n args: Array.isArray(e.args)\n ? (e.args as unknown[]).filter((x): x is string => typeof x === \"string\")\n : [],\n shell: e.shell === true,\n state: e.state === \"idle\" ? \"idle\" : \"busy\",\n startedAt: Number(e.startedAt) || 0,\n writtenAt: Number(e.writtenAt) || 0,\n };\n const tid = Number(e.taskId);\n if (Number.isInteger(tid) && tid > 0) rec.taskId = tid;\n out.push(rec);\n }\n return out;\n}\n\n/** 原子写记录文件;空数组 = 没有进程 = 删除文件。单条写对象、多条写数组(读侧都认)。 */\nexport function writePidRecords(file: string, recs: PidRecord[]): void {\n try {\n if (!recs.length) {\n fs.unlinkSync(file);\n return;\n }\n } catch {\n /* 文件不存在 → 继续写 */\n }\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const tmp = `${file}.tmp`;\n const body = recs.length === 1 ? recs[0] : recs;\n fs.writeFileSync(tmp, JSON.stringify(body, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, file);\n } catch {\n /* best-effort:记录写失败不该影响起进程 */\n }\n}\n\n/** 由「刚 spawn 出来的 agent」构造一条记录。 */\nexport function pidRecordOf(\n pid: number,\n ag: Pick<ResolvedAgent, \"name\" | \"bin\" | \"args\" | \"shell\">,\n extra: { taskId?: number; state?: PidState } = {},\n): PidRecord {\n const now = Date.now();\n const rec: PidRecord = {\n pid,\n agent: ag.name,\n bin: ag.bin,\n args: ag.args,\n shell: ag.shell,\n state: extra.state ?? \"busy\",\n startedAt: now,\n writtenAt: now,\n };\n if (extra.taskId !== undefined) rec.taskId = extra.taskId;\n return rec;\n}\n\n/** 记录对应的进程特征串;`legacy`(无 bin)→ 空数组,调用方回退配置。 */\nexport function recordMarkers(rec: PidRecord): string[][] {\n return rec.bin ? brainPidMarkers(rec.bin, rec.args) : [];\n}\n","import {\n spawn,\n type ChildProcessWithoutNullStreams,\n type SpawnOptionsWithoutStdio,\n} from \"node:child_process\";\n\n/*\n * 启动 ACP agent 进程的**统一入口**。\n *\n * 为什么不是 `spawn(bin, args[], { shell: true })`:\n * Node 20+ 对这种写法报 DEP0190——`shell:true` 时 `args` 只是用空格拼进命令串、不做任何转义(既是注入面,也会在 daemon.log 里每次冷启动刷两行弃用警告)。\n * 正确做法是**自己把整条命令拼成一个字符串**再交给 shell,参数由我们逐条加引号。\n * 仓库里的 pm2-runner.ts 出于同样原因用了同样的写法。\n *\n * 为什么仍然要经 shell:\n * Windows 上 npm shim(opencode / npx / gemini)都是 `.cmd`,而 Node 20+ **禁止**无 shell 直接 spawn `.cmd`/`.bat`(EINVAL,CVE-2024-27980 之后的安全限制),所以只能让 cmd.exe 去解析 PATH。POSIX 上同一份代码走 `/bin/sh -c`,正好覆盖两侧。\n *\n * 注:既然经 shell,cmd 的元字符语义就生效(参数必须逐条加引号;`%VAR%` 仍会被展开),这与改动前的行为一致,不引入新语义。\n */\n\n/** 启动所需的最小 agent 描述(`ResolvedAgent` 的子集,避免反向依赖 brain-agents)。 */\nexport type SpawnableAgent = {\n bin: string;\n args: readonly string[];\n shell: boolean;\n};\n\n/** cmd 需要加引号的字符:空白 + 元字符(不加引号时 cmd 会自行解释:`&` 断命令、`|` 管道…)。 */\nconst CMD_NEEDS_QUOTE = /[\\s\"&|<>^()%!]/;\n\n/**\n * cmd.exe(MS CRT)参数转义:无空白/元字符则裸写;否则用双引号包裹,\n * 并按其规则处理引号前的前导反斜杠与结尾反斜杠。\n * 注:引号挡得住元字符,但挡不住 `%VAR%` 展开(cmd 命令行无法转义 `%`),与改动前行为一致。\n */\nexport function quoteCmdArg(arg: string): string {\n if (arg === \"\") return '\"\"';\n if (!CMD_NEEDS_QUOTE.test(arg)) return arg;\n return `\"${arg.replace(/(\\\\*)\"/g, '$1$1\\\\\"').replace(/(\\\\+)$/, \"$1$1\")}\"`;\n}\n\n/** POSIX sh 参数转义:安全字符集裸写,否则单引号包裹(内部单引号写成 `'\\''`)。 */\nexport function quoteShArg(arg: string): string {\n if (arg === \"\") return \"''\";\n if (!/[^\\w@%+=:,./-]/.test(arg)) return arg;\n return `'${arg.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** 把 `bin + args` 拼成一条交给 shell 的命令行(`platform` 仅为可测性暴露)。 */\nexport function buildShellLine(\n bin: string,\n args: readonly string[],\n platform: NodeJS.Platform = process.platform,\n): string {\n const quote = platform === \"win32\" ? quoteCmdArg : quoteShArg;\n return [bin, ...args].map(quote).join(\" \");\n}\n\n/**\n * 启动 agent:`shell=true` 的走「整串命令行 + shell」(见文件头注释),其余直接 spawn。\n * 默认 `windowsHide: true`(不闪控制台窗口),其余选项透传。\n */\nexport function spawnAgent(\n agent: SpawnableAgent,\n opts: SpawnOptionsWithoutStdio = {},\n): ChildProcessWithoutNullStreams {\n const base: SpawnOptionsWithoutStdio = { windowsHide: true, ...opts };\n if (!agent.shell) return spawn(agent.bin, agent.args, base);\n return spawn(buildShellLine(agent.bin, agent.args), { ...base, shell: true });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\n/**\n * brainstorm/worker 共用的 session 映射键规则:`<agent>:<key>`。\n * 换大脑后各 agent 的会话互不覆盖,切回原大脑仍能 session/load 回原上下文。\n */\nexport function sessionKey(agent: string, key: string): string {\n return `${agent}:${key}`;\n}\n\n/** 对话大脑的当前会话表路径(`<agent>:<userId>` → sessionId)。 */\nexport function brainSessionMapPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"brain-sessions.json\");\n}\n\n/** 任务 worker 的当前会话表路径(`<agent>:<taskKey>` → sessionId)。 */\nexport function workerSessionMapPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"worker-sessions.json\");\n}\n\n/** 读当前会话表;缺失/损坏/非对象 → 空表(幂等、不抛)。只保留字符串值,**不做格式迁移**(迁移归 normalizeSessionMap)。 */\nexport function readSessionMap(file: string): Record<string, string> {\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(file, \"utf-8\"));\n } catch {\n return {};\n }\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return {};\n const map: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof v === \"string\" && v) map[k] = v;\n }\n return map;\n}\n\n/** 原子写当前会话表(tmp + rename):这个文件在关键路径上(ensureSession → session/load),半截 JSON 等于「所有会话丢失」。 */\nexport function writeSessionMap(file: string, map: Record<string, string>): void {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const tmp = `${file}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(map, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, file);\n}\n\n/** 清掉某个键的当前会话记录(如「删除会话」删的正是当前那条);返回是否确有改动。 */\nexport function clearSessionPointer(file: string, key: string): boolean {\n const map = readSessionMap(file);\n if (!(key in map)) return false;\n delete map[key];\n writeSessionMap(file, map);\n return true;\n}\n\n/**\n * 把磁盘上的映射归一成 `<agent>:<key>` 形式。\n * - 已带前缀的条目原样保留(其它 agent 的会话不动);\n * - 旧格式(裸 key)由「当时那个 agent」写入、已无从得知是哪个 → 归给当前 agent;若同名的新格式条目已存在,则保留新格式(它的归属确定),只丢掉裸条目。\n * migrated=true 时调用方应立即回写,避免下次换 agent 时旧条目再被继承一次。\n */\nexport function normalizeSessionMap(\n raw: unknown,\n agent: string,\n): { map: Record<string, string>; migrated: boolean } {\n const map: Record<string, string> = {};\n const legacy: Array<[string, string]> = [];\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return { map, migrated: false };\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof v !== \"string\") continue;\n if (k.includes(\":\")) map[k] = v;\n else legacy.push([k, v]);\n }\n for (const [k, v] of legacy) {\n const nk = sessionKey(agent, k);\n if (!map[nk]) map[nk] = v;\n }\n return { map, migrated: legacy.length > 0 };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport {\n brainSessionMapPath,\n clearSessionPointer,\n readSessionMap,\n sessionKey,\n} from \"./session-map.js\";\n\n/**\n * 会话登记表(`<数据目录>/session-registry.json`):跨大脑记住「这个人有哪些会话」,\n * 让「会话列表 / 恢复会话 / 重命名 / 删除」有东西可列——`brain-sessions.json` 只存**当前那一个**会话的 id,\n * 一开新会话,旧的就失去引用、无处可恢复。\n *\n * 为什么与 `brain-sessions.json` 分开:\n * - 当前会话表在关键路径上(ensureSession → session/load)且已背了一层旧格式迁移(见 session-map.ts);\n * 把它的值从 string 改成 object 要再迁一次,还会波及 worker 的孪生代码。\n * - 登记表是**派生元数据**:损坏/丢失只应导致「列表空」,绝不能影响恢复。\n * 因此一致性的规则是:**当前会话表唯一权威地表示「当前是哪个」**;登记表只供列表,且自己**不存 current**\n * (渲染时才与当前会话记录比对);`删除会话` 若是当前那条,则同时清当前会话记录(下一次消息自然开新的)。\n *\n * 写入方只有 daemon(或 CLI 经控制桥,见 control-bridge.ts),避免多写者互相覆盖。\n * 所有写入走 tmp + rename;所有读取失败都降级为空表,只记日志、不抛——它不该有能力拖垮 daemon。\n */\n\nexport type SessionEntry = {\n /** 所属大脑(agent 名,小写)。 */\n agent: string;\n /** 会话属于谁(微信 userId);登记表按人隔离,别人的会话列不出来。 */\n userId: string;\n /** ACP sessionId。 */\n sessionId: string;\n /** 用户起的名字(可寻址:`恢复会话 项目A`);命名后不会被淘汰。 */\n name?: string;\n /** 首条用户消息的前若干字,作列表里的摘要。 */\n title?: string;\n /** 成功回合数(列表里的「轮次」)。 */\n turns: number;\n createdAt: number;\n lastUsedAt: number;\n /** 来自当前会话表回填(功能上线前就存在的会话,没有真实时间/轮次)。 */\n backfilled?: boolean;\n};\n\nexport type SessionRegistry = { version: number; entries: SessionEntry[] };\n\nexport const REGISTRY_VERSION = 1;\n\n/** 每个 (agent, userId) 的条数上限;超出先淘汰「未命名且最旧」的。 */\nexport const MAX_ENTRIES_PER_KEY = Number(process.env.WEMIND_SESSION_LOG_MAX) || 50;\n\n/** 列表分页大小。 */\nexport const PAGE_SIZE = Number(process.env.WEMIND_SESSION_PAGE_SIZE) || 10;\n\nexport function registryPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"session-registry.json\");\n}\n\n/** 归一化一条记录;缺 agent/userId/sessionId 视为无效(丢弃)。 */\nfunction normEntry(e: any, now: number): SessionEntry | null {\n if (!e || typeof e !== \"object\" || Array.isArray(e)) return null;\n const agent = typeof e.agent === \"string\" ? e.agent.trim().toLowerCase() : \"\";\n const userId = typeof e.userId === \"string\" ? e.userId.trim() : \"\";\n const sessionId = typeof e.sessionId === \"string\" ? e.sessionId.trim() : \"\";\n if (!agent || !userId || !sessionId) return null;\n const num = (v: unknown, d: number): number =>\n typeof v === \"number\" && Number.isFinite(v) && v >= 0 ? v : d;\n const out: SessionEntry = {\n agent,\n userId,\n sessionId,\n turns: Math.trunc(num(e.turns, 0)),\n createdAt: num(e.createdAt, now),\n lastUsedAt: num(e.lastUsedAt, now),\n };\n if (typeof e.name === \"string\" && e.name.trim()) out.name = e.name.trim();\n if (typeof e.title === \"string\" && e.title.trim()) out.title = e.title.trim();\n if (e.backfilled === true) out.backfilled = true;\n return out;\n}\n\n/** 读登记表;缺失/损坏 → 空表(幂等、不抛)。 */\nexport function loadRegistry(dir: string = DATA_DIR, now: number = Date.now()): SessionRegistry {\n let j: any;\n try {\n j = JSON.parse(fs.readFileSync(registryPath(dir), \"utf-8\"));\n } catch {\n return { version: REGISTRY_VERSION, entries: [] };\n }\n const entries: SessionEntry[] = [];\n if (j && typeof j === \"object\" && Array.isArray(j.entries)) {\n for (const e of j.entries) {\n const n = normEntry(e, now);\n if (n) entries.push(n);\n }\n }\n return { version: REGISTRY_VERSION, entries };\n}\n\n/** 原子写登记表。写失败只记日志、**不抛**:登记表是派生元数据,绝不能因为一次磁盘问题拖垮一个回合或一次启动。 */\nexport function saveRegistry(reg: SessionRegistry, dir: string = DATA_DIR): void {\n try {\n fs.mkdirSync(dir, { recursive: true });\n const fp = registryPath(dir);\n const tmp = `${fp}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(reg, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, fp);\n } catch (e) {\n console.error(\"[wemind/wechat-daemon] session-registry save failed:\", String(e));\n }\n}\n\n// ---- 纯函数(可直接单测) ----\n\n/** 最近使用在前;同刻按创建时间。 */\nexport function sortEntries(list: SessionEntry[]): SessionEntry[] {\n return [...list].sort(\n (a, b) =>\n b.lastUsedAt - a.lastUsedAt ||\n b.createdAt - a.createdAt ||\n a.sessionId.localeCompare(b.sessionId),\n );\n}\n\n/**\n * 按 (agent, userId) 分组做条数裁剪:超出上限时先淘汰「未命名且最旧」的;\n * 仍然超出才动有名字的。`keep`(当前会话)永不淘汰。\n * 非本组的条目原样保留。\n */\nexport function capEntries(\n list: SessionEntry[],\n agent: string,\n userId: string,\n max: number,\n keep: string[] = [],\n): SessionEntry[] {\n const a = agent.trim().toLowerCase();\n const group = list.filter((e) => e.agent === a && e.userId === userId);\n if (group.length <= max) return list;\n const keepSet = new Set(keep);\n const oldestFirst = [...group].sort(\n (x, y) => x.lastUsedAt - y.lastUsedAt || x.createdAt - y.createdAt,\n );\n const dropped = new Set<string>();\n for (const pass of [true, false]) {\n for (const e of oldestFirst) {\n if (group.length - dropped.size <= max) break;\n if (keepSet.has(e.sessionId) || dropped.has(e.sessionId)) continue;\n if (pass && e.name) continue; // 第一轮只淘汰未命名的\n dropped.add(e.sessionId);\n }\n }\n return list.filter((e) => !(e.agent === a && e.userId === userId && dropped.has(e.sessionId)));\n}\n\n/** 插入/更新一条记录(同 agent+userId+sessionId 视为同一条),并做裁剪。 */\nexport function upsertEntry(\n list: SessionEntry[],\n entry: SessionEntry,\n opts: { max?: number; keep?: string[] } = {},\n): SessionEntry[] {\n const next = list.filter(\n (e) =>\n !(e.agent === entry.agent && e.userId === entry.userId && e.sessionId === entry.sessionId),\n );\n next.push(entry);\n return capEntries(next, entry.agent, entry.userId, opts.max ?? MAX_ENTRIES_PER_KEY, [\n entry.sessionId,\n ...(opts.keep ?? []),\n ]);\n}\n\nexport type RefHit =\n | { ok: true; entry: SessionEntry; index: number }\n | { ok: false; error: string };\n\n/**\n * 解析「会话引用」:序号(1 起)> 名字(先精确后忽略大小写)> id 精确 > id 前缀。\n * 歧义不猜——给出前 5 个候选让用户挑。\n */\nexport function resolveRef(ref: string, list: SessionEntry[]): RefHit {\n const r = (ref ?? \"\").trim();\n if (!r) return { ok: false, error: \"需要指定会话(序号 / 名字 / id 前缀)。\" };\n if (!list.length) return { ok: false, error: \"还没有历史会话。\" };\n if (/^\\d+$/.test(r)) {\n const n = Number(r);\n if (n >= 1 && n <= list.length) return { ok: true, entry: list[n - 1], index: n };\n return { ok: false, error: `序号超范围:共 ${list.length} 条。` };\n }\n const brief = (e: SessionEntry): string => e.name ?? e.title ?? e.sessionId.slice(0, 8);\n const byName = list.filter((e) => e.name === r);\n if (byName.length === 1)\n return { ok: true, entry: byName[0], index: list.indexOf(byName[0]) + 1 };\n if (byName.length > 1)\n return {\n ok: false,\n error: `有 ${byName.length} 条会话都叫「${r}」,请用序号:${byName\n .slice(0, 5)\n .map((e) => list.indexOf(e) + 1)\n .join(\"、\")}。`,\n };\n const lower = r.toLowerCase();\n const byNameI = list.filter((e) => e.name?.toLowerCase() === lower);\n if (byNameI.length === 1)\n return { ok: true, entry: byNameI[0], index: list.indexOf(byNameI[0]) + 1 };\n if (byNameI.length > 1)\n return {\n ok: false,\n error: `有 ${byNameI.length} 条会话都叫「${r}」,请用序号:${byNameI\n .slice(0, 5)\n .map((e) => list.indexOf(e) + 1)\n .join(\"、\")}。`,\n };\n const exact = list.find((e) => e.sessionId === r);\n if (exact) return { ok: true, entry: exact, index: list.indexOf(exact) + 1 };\n if (r.length >= 2) {\n const hits = list.filter((e) => e.sessionId.startsWith(r));\n if (hits.length === 1) return { ok: true, entry: hits[0], index: list.indexOf(hits[0]) + 1 };\n if (hits.length > 1)\n return {\n ok: false,\n error: `id 前缀「${r}」匹配到 ${hits.length} 条:${hits.slice(0, 5).map(brief).join(\"、\")}。`,\n };\n }\n return { ok: false, error: `没有找到会话「${r}」(序号 / 名字 / id 前缀)。` };\n}\n\n/** 会话名校验:合法返回 null,否则返回一句面向用户的原因。 */\nexport function validateSessionName(name: string): string | null {\n const n = (name ?? \"\").trim();\n if (!n) return \"名字不能为空。\";\n if (n.length > 20) return \"名字最多 20 个字。\";\n if (/^\\d+$/.test(n)) return \"名字不能是纯数字(会和列表序号混淆)。\";\n if (/[\\r\\n\\t]/.test(n)) return \"名字不能包含换行/制表符。\";\n return null;\n}\n\nfunction pad(n: number): string {\n return n < 10 ? `0${n}` : String(n);\n}\n\n/** 列表里的时间:同年只给月日时分,跨年补年份;没有真实时间(回填)的给「时间未知」。 */\nexport function formatWhen(ts: number, now: number = Date.now()): string {\n if (!ts) return \"时间未知\";\n const d = new Date(ts);\n const hm = `${pad(d.getHours())}:${pad(d.getMinutes())}`;\n const md = `${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n return d.getFullYear() === new Date(now).getFullYear()\n ? `${md} ${hm}`\n : `${d.getFullYear()}-${md}`;\n}\n\n/** 列表里的标签:命名 > 首条消息摘要 > 短 id。 */\nexport function labelOf(e: SessionEntry, max = 24): string {\n const raw = e.name ? `「${e.name}」` : e.title ? e.title : `(未命名 ${e.sessionId.slice(0, 8)})`;\n return raw.length > max ? `${raw.slice(0, max - 1)}…` : raw;\n}\n\n/**\n * 列表里的标题来源:用户消息的头 30 字(换行/多空格折叠成一个空格)。\n * 空文本返回 undefined(纯图片消息没有可当标题的文字)——留给下一次有文字的回合再写。\n */\nexport function titleOf(text: string, max = 30): string | undefined {\n const t = (text ?? \"\").replace(/\\s+/g, \" \").trim();\n if (!t) return undefined;\n return t.length > max ? `${t.slice(0, max)}…` : t;\n}\n\n/**\n * 渲染会话列表(微信侧)。`verbose` 追加完整 sessionId——CLI 侧有地方,微信侧靠序号/名字即可。\n */\nexport function renderSessionList(\n entries: SessionEntry[],\n opts: {\n agent: string;\n page?: number;\n pageSize?: number;\n currentSessionId?: string;\n others?: number;\n now?: number;\n verbose?: boolean;\n },\n): string {\n const pageSize = opts.pageSize ?? PAGE_SIZE;\n const total = entries.length;\n if (!total) {\n return `📋 当前大脑(${opts.agent})还没有历史会话。\\n发一条消息就会建第一条;「新会话 名字」可新建并命名。`;\n }\n const pages = Math.max(1, Math.ceil(total / pageSize));\n const page = Math.min(Math.max(1, Math.trunc(opts.page ?? 1)), pages);\n const slice = entries.slice((page - 1) * pageSize, page * pageSize);\n const lines = [`📋 会话列表(${opts.agent},共 ${total} 条,第 ${page}/${pages} 页)`];\n slice.forEach((e, i) => {\n const idx = (page - 1) * pageSize + i + 1;\n const cur = e.sessionId === opts.currentSessionId ? \" ← 当前\" : \"\";\n const id = opts.verbose ? ` · id=${e.sessionId}` : \"\";\n lines.push(\n ` ${idx}. ${labelOf(e)} · ${formatWhen(e.lastUsedAt, opts.now)} · ${e.turns} 轮${cur}${id}`,\n );\n });\n if (pages > 1) lines.push(`(下一页:「会话列表 ${page + 1}」)`);\n lines.push(`回复「恢复会话 <序号|名字>」切过去;「重命名会话 3 新名字」「删除会话 3」亦可。`);\n if (opts.others) lines.push(`另有 ${opts.others} 条属于其它大脑的会话(切回该大脑后可恢复)。`);\n return lines.join(\"\\n\");\n}\n\n// ---- 读写入口(daemon / CLI 经桥调用) ----\n\n/** 当前大脑下、本人的会话 + 其它大脑的条数 + 当前会话记录。 */\nexport function listSessions(\n agent: string,\n userId: string,\n dir: string = DATA_DIR,\n): { entries: SessionEntry[]; others: number; currentSessionId?: string } {\n const a = agent.trim().toLowerCase();\n const all = loadRegistry(dir).entries;\n const entries = sortEntries(all.filter((e) => e.agent === a && e.userId === userId));\n const others = all.filter((e) => e.agent !== a && e.userId === userId).length;\n return { entries, others, currentSessionId: currentSessionIdOf(a, userId, dir) };\n}\n\n/** 当前会话表里该人当前的会话 id(登记表不存 current,渲染时才比对)。 */\nexport function currentSessionIdOf(\n agent: string,\n userId: string,\n dir: string = DATA_DIR,\n): string | undefined {\n return readSessionMap(brainSessionMapPath(dir))[sessionKey(agent.trim().toLowerCase(), userId)];\n}\n\n/** 新建会话时登记(`turns` 从 0 起);已存在则只补 name/keep,不动时间与轮次。 */\nexport function ensureEntry(\n agent: string,\n userId: string,\n sessionId: string,\n opts: { name?: string; keep?: string[]; dir?: string; now?: number } = {},\n): void {\n const a = agent.trim().toLowerCase();\n const now = opts.now ?? Date.now();\n const reg = loadRegistry(opts.dir, now);\n const hit = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === sessionId,\n );\n if (hit) {\n let changed = false;\n if (opts.name && hit.name !== opts.name) {\n hit.name = opts.name;\n changed = true;\n }\n if (changed) saveRegistry(reg, opts.dir);\n return;\n }\n const entry: SessionEntry = {\n agent: a,\n userId,\n sessionId,\n turns: 0,\n createdAt: now,\n lastUsedAt: now,\n };\n if (opts.name) entry.name = opts.name;\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: upsertEntry(reg.entries, entry, { keep: opts.keep }),\n };\n saveRegistry(next, opts.dir);\n}\n\n/**\n * 每完成一个成功回合调用:`lastUsedAt` 刷新、轮次 +1、标题(首条消息摘要)仅首次写入。\n * `turns` 传绝对值时直接覆盖(恢复旧会话后的首次校准用)。\n */\nexport function touchEntry(\n agent: string,\n userId: string,\n sessionId: string,\n opts: { title?: string; turns?: number; keep?: string[]; dir?: string; now?: number } = {},\n): void {\n const a = agent.trim().toLowerCase();\n const now = opts.now ?? Date.now();\n const reg = loadRegistry(opts.dir, now);\n const hit = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === sessionId,\n );\n if (!hit) {\n const entry: SessionEntry = {\n agent: a,\n userId,\n sessionId,\n turns: opts.turns ?? 1,\n createdAt: now,\n lastUsedAt: now,\n };\n if (opts.title) entry.title = opts.title;\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: upsertEntry(reg.entries, entry, { keep: opts.keep }),\n };\n saveRegistry(next, opts.dir);\n return;\n }\n hit.lastUsedAt = now;\n hit.turns = opts.turns ?? hit.turns + 1;\n if (opts.title && !hit.title) hit.title = opts.title;\n hit.backfilled = undefined;\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: upsertEntry(reg.entries, hit, { keep: opts.keep }),\n };\n saveRegistry(next, opts.dir);\n}\n\nexport type SessionOpResult =\n | { ok: true; entry: SessionEntry; clearedPointer: boolean }\n | { ok: false; error: string };\n\n/**\n * 只刷新「最近使用」:把一条旧会话切回当前(`恢复会话`、切大脑后 load 回来的那条)时用。\n * 不动轮次、不写标题——它不是「又聊了一轮」。未登记时补一条(turns 0),\n * 否则「直接 load 一个登记表里没有的 id」会变成列表里看不见的会话。\n */\nexport function markUsed(\n agent: string,\n userId: string,\n sessionId: string,\n opts: { dir?: string; now?: number } = {},\n): void {\n const a = agent.trim().toLowerCase();\n const now = opts.now ?? Date.now();\n const reg = loadRegistry(opts.dir, now);\n const hit = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === sessionId,\n );\n const entry: SessionEntry = hit ?? {\n agent: a,\n userId,\n sessionId,\n turns: 0,\n createdAt: now,\n lastUsedAt: now,\n };\n entry.lastUsedAt = now;\n entry.backfilled = undefined; // 现在有真实的使用时间了\n saveRegistry({ version: REGISTRY_VERSION, entries: upsertEntry(reg.entries, entry) }, opts.dir);\n}\n\n/** 改名(按引用解析);命名后不会被条数裁剪淘汰。 */\nexport function renameSession(\n agent: string,\n userId: string,\n ref: string,\n name: string,\n dir: string = DATA_DIR,\n): SessionOpResult {\n const bad = validateSessionName(name);\n if (bad) return { ok: false, error: bad };\n const a = agent.trim().toLowerCase();\n const { entries } = listSessions(a, userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return { ok: false, error: hit.error };\n const reg = loadRegistry(dir);\n const target = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === hit.entry.sessionId,\n );\n const entry = target ?? hit.entry;\n entry.name = name.trim();\n saveRegistry({ version: REGISTRY_VERSION, entries: upsertEntry(reg.entries, entry) }, dir);\n return { ok: true, entry, clearedPointer: false };\n}\n\n/**\n * 删除登记项。删的若是当前会话,**同时清当前会话记录**(下一条消息自然开新的)——当前会话记录不清会让「删掉的会话」继续被使用。\n * agent 侧的历史归档不动(ACP 无可验证的 session/delete,如实告知用户)。\n */\nexport function deleteSession(\n agent: string,\n userId: string,\n ref: string,\n opts: { dir?: string } = {},\n): SessionOpResult {\n const a = agent.trim().toLowerCase();\n const dir = opts.dir;\n const { entries, currentSessionId } = listSessions(a, userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return { ok: false, error: hit.error };\n const reg = loadRegistry(dir);\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: reg.entries.filter(\n (e) => !(e.agent === a && e.userId === userId && e.sessionId === hit.entry.sessionId),\n ),\n };\n saveRegistry(next, dir);\n const clearedPointer =\n currentSessionId === hit.entry.sessionId\n ? clearSessionPointer(brainSessionMapPath(dir ?? DATA_DIR), sessionKey(a, userId))\n : false;\n return { ok: true, entry: hit.entry, clearedPointer };\n}\n\n/**\n * 一次性回填:把当前会话表里存在、登记表里还没有的会话补进去(功能上线前的老会话)。\n * 只由 daemon 启动时调用(幂等);之后会话的登记走 ensureEntry/touchEntry。\n */\nexport function syncFromPointerMap(dir: string = DATA_DIR, now: number = Date.now()): number {\n const map = readSessionMap(brainSessionMapPath(dir));\n const reg = loadRegistry(dir, now);\n let added = 0;\n for (const [k, sid] of Object.entries(map)) {\n const i = k.indexOf(\":\");\n if (i <= 0 || !sid) continue;\n const agent = k.slice(0, i).trim().toLowerCase();\n const userId = k.slice(i + 1).trim();\n if (!agent || !userId) continue;\n if (reg.entries.some((e) => e.agent === agent && e.userId === userId && e.sessionId === sid))\n continue;\n reg.entries.push({\n agent,\n userId,\n sessionId: sid,\n turns: 0,\n createdAt: now,\n lastUsedAt: now,\n backfilled: true,\n });\n added++;\n }\n if (added) saveRegistry(reg, dir);\n return added;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { formatWhen } from \"./session-registry.js\";\n\n/**\n * 会话交接(`交接` / `wemind brain handoff`):换大脑**不自动迁移上下文**(各 agent 的会话互不相通),\n * 要接着聊就显式交接一次——让旧大脑把上下文写成一段摘要,存盘,然后**作为下一条用户消息的前缀**带进新大脑。\n *\n * 为什么是「前缀」而不是「单独占一个回合」:单独发摘要等于让新大脑先空转一轮(多一次模型调用、多占微信额度),\n * 而且那轮它的回复用户并不想看。作为前缀带上是零额外成本的:用户本来就要说下一句话,摘要只是挂在前面。\n *\n * 三条克制规则(都由 `takeHandoffFor` 钉死):\n * - **只给本人用**:给 A 生成的摘要绝不带上 B 的消息;\n * - **切回来源大脑不带上**:那是它自己的上下文,塞回去等于对着本人复述;\n * - **有 TTL**(默认 24h):过期摘要不带上——几天前的上下文塞给新大脑只会污染判断。\n *\n * 消费语义:注入的那一回合**成功**之后才删(见 index.ts)——失败就留着,下次还能用。\n * 文本处理全是纯函数(`handoffPrefix`/`stripHandoffPrefix`),可单测;文件读写只有 save/load/clear。\n */\n\nexport const HANDOFF_VERSION = 1;\n\n/** 摘要有效期:超过就不再带上(可通过 `WEMIND_HANDOFF_TTL_MS` 调整)。 */\nexport const HANDOFF_TTL_MS = Number(process.env.WEMIND_HANDOFF_TTL_MS) || 24 * 3600_000;\n\n/** 前缀与用户新消息之间的分隔行;带上与剥离靠同一常量,两边不会写岔。 */\nexport const HANDOFF_SEP = \"——以下才是用户的新消息——\";\n\n/** 前缀开头,`stripHandoffPrefix` 据此确认「这确实是我们的前缀」而不是用户自己发的正文。 */\nexport const HANDOFF_HEAD = \"【交接摘要 · 来自 \";\n\nexport type Handoff = {\n version: number;\n /** 由哪个大脑写的(agent 名,小写)。 */\n fromAgent: string;\n /** 给谁写的(微信 userId)。 */\n userId: string;\n /** 摘要正文(Markdown)。 */\n text: string;\n createdAt: number;\n expiresAt: number;\n};\n\nexport function handoffPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"handoff.json\");\n}\n\n/** 读摘要:缺失/损坏/字段不全/已过期 → null(带不带只该由「有没有可用的」决定,不该抛)。 */\nexport function loadHandoff(dir: string = DATA_DIR, now: number = Date.now()): Handoff | null {\n let j: any;\n try {\n j = JSON.parse(fs.readFileSync(handoffPath(dir), \"utf-8\"));\n } catch {\n return null;\n }\n if (!j || typeof j !== \"object\" || Array.isArray(j)) return null;\n const fromAgent = typeof j.fromAgent === \"string\" ? j.fromAgent.trim().toLowerCase() : \"\";\n const userId = typeof j.userId === \"string\" ? j.userId.trim() : \"\";\n const text = typeof j.text === \"string\" ? j.text.trim() : \"\";\n if (!fromAgent || !userId || !text) return null;\n const num = (v: unknown, d: number): number =>\n typeof v === \"number\" && Number.isFinite(v) && v > 0 ? v : d;\n const h: Handoff = {\n version: HANDOFF_VERSION,\n fromAgent,\n userId,\n text,\n createdAt: num(j.createdAt, now),\n expiresAt: num(j.expiresAt, now + HANDOFF_TTL_MS),\n };\n return h.expiresAt > now ? h : null;\n}\n\n/** 原子写摘要(tmp + rename)。 */\nexport function saveHandoff(h: Handoff, dir: string = DATA_DIR): void {\n fs.mkdirSync(dir, { recursive: true });\n const fp = handoffPath(dir);\n const tmp = `${fp}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(h, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, fp);\n}\n\n/** 删摘要(注入的那回合成功后调用);返回是否确有文件。 */\nexport function clearHandoff(dir: string = DATA_DIR): boolean {\n try {\n fs.unlinkSync(handoffPath(dir));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 判断这份摘要要不要带给 `agent` 的这次对话(纯函数,调用方负责读盘与消费)。\n * 返回正文表示「要带上」,null 表示「不带上」——四种不带上的情形各是一句能对用户解释的话。\n */\nexport function takeHandoffFor(\n h: Handoff | null,\n agent: string,\n userId: string,\n now: number = Date.now(),\n): string | null {\n if (!h) return null;\n const text = h.text?.trim();\n if (!text) return null;\n if (h.userId !== userId) return null;\n if (h.fromAgent === agent.trim().toLowerCase()) return null; // 切回来源大脑:那是它自己的上下文\n if (h.expiresAt <= now) return null;\n return text;\n}\n\n/** 交接摘要的生成指令(固定模板:四节、无前后缀,便于新大脑直接当上下文读)。 */\nexport const HANDOFF_PROMPT = [\n \"请把**现在这段对话**整理成一份交接摘要,写给另一个 AI 助手看(它看不到我们的聊天记录,也不会问你)。\",\n \"只要下面四节,每节几条要点;不要客套、不要复述我的原话、不要写「以下是」这类前后缀:\",\n \"1. 目标:我们在做什么、做到哪一步;\",\n \"2. 已确认的结论与决定(含我明确表达过的偏好与约束);\",\n \"3. 未决问题:还没定的事、等我回答的问题;\",\n \"4. 关键事实:文件路径、命令、报错信息、外部资源等硬信息(有多少写多少,别概括掉)。\",\n \"直接输出 Markdown 摘要正文。\",\n].join(\"\\n\");\n\n/** 带上摘要用的前缀(以分隔行结尾,调用方把用户新消息接在后面)。 */\nexport function handoffPrefix(fromAgent: string, text: string, now: number = Date.now()): string {\n return `${HANDOFF_HEAD}${fromAgent},${formatWhen(now, now)}】\\n${text}\\n${HANDOFF_SEP}\\n`;\n}\n\n/**\n * 从作为前缀带上后的消息里取回用户真正说的那句(登记表的标题用)。\n * 只在确实是我们的前缀时才剥(开头对得上 `HANDOFF_HEAD`),用户自己打那行字不会被误伤。\n */\nexport function stripHandoffPrefix(message: string): string {\n const m = message ?? \"\";\n if (!m.startsWith(HANDOFF_HEAD)) return m;\n const i = m.indexOf(HANDOFF_SEP);\n if (i < 0) return m;\n return m.slice(i + HANDOFF_SEP.length).trim();\n}\n","import { spawnSync, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { BRAIN_HOME, activeMcpServers } from \"./mcp-sandbox.js\";\nimport { getAgent } from \"./agent-slot.js\";\nimport { acpArgsHint, agentEnv, type ResolvedAgent } from \"./brain-agents.js\";\nimport { pidRecordOf, writePidRecords } from \"./pid-record.js\";\nimport { spawnAgent } from \"./spawn-agent.js\";\nimport {\n brainSessionMapPath,\n normalizeSessionMap,\n sessionKey,\n writeSessionMap,\n} from \"./session-map.js\";\nimport { ensureEntry, markUsed, titleOf, touchEntry } from \"./session-registry.js\";\nimport { stripHandoffPrefix } from \"./handoff.js\";\n\n/**\n * ACP 大脑:维护一个常驻的 ACP agent 进程(JSON-RPC over stdio,换行分隔)。\n * 通过标准 ACP 协议驱动,agent 可配置(默认 kiro-cli,见 brain-agents.ts),任意兼容 ACP 的 CLI 均可接入。\n * - 每用户一个 ACP session(原生多轮记忆);session 映射持久化到磁盘。\n * - 自愈:session/prompt 超时/失败 → 杀进程重建;重建后用 session/load 恢复上下文。\n * - 诊断日志([wemind/wechat-daemon/acp] 前缀,进 daemon.log):发出请求、收帧、超时时已收 chunk 数与最后收帧时间差。\n * 跑在 brain 沙箱目录下(默认空 MCP),通过 agent 配置的环境变量把该目录传给 agent。\n *\n * **agent 显式传参**:本模块不再持有「当前 agent」常量(改造前是 import 时解析的 `AGENT`,\n * 于是热切换只能重启整个 daemon)。入口 `askBrain`/`newSession` 取一次槽位(`getAgent(\"brain\")`),\n * 之后 `ensureProcess(ag)` / `ensureSession(ag,…)` / `applyModel(ag,…)` / `promptRequest(ag,…)` 一路显式传下去——\n * 过程内不再读槽位,「握手到一半被换掉 agent」在结构上不可能发生(不需要锁)。\n */\n\nfunction dlog(...a: unknown[]): void {\n console.error(\"[wemind/wechat-daemon/acp]\", ...a);\n}\n\nconst REQ_TIMEOUT = 30_000; // initialize / session/new / session/load\nconst PROMPT_IDLE_MS = Number(process.env.WECHAT_BRAIN_IDLE_MS) || 120_000; // 多久没收到任何帧才算\"卡死\"。重任务单次慢抓取可静默较久(直连 kiro-cli 无此限制),放宽到 2min 兜底真死\nconst PROMPT_CAP_MS = Number(process.env.WECHAT_BRAIN_CAP_MS) || 900_000; // 单回合硬上限 15min。实测重任务(联网核实)需 ~6.5min;context_token 无时间 TTL(官方),长回合用本消息 token 仍可送达。卡死由 idle/首帧超时兜底\nconst BRAIN_PID_FILE = path.join(DATA_DIR, \"brain.pid\"); // 身份记录(JSON,见 pid-record.ts);跨入口用同一文件名\n\n/** 首帧超时:env 覆盖 > 配置覆盖(DSH 实测首帧 1–4s,兜底 60s)> 默认 25s。\n * 按 agent 算而不是模块常量——热切换后新 agent 的启动快慢与旧的不同。 */\nfunction firstFrameMs(ag: ResolvedAgent): number {\n return Number(process.env.WECHAT_BRAIN_FIRST_FRAME_MS) || ag.firstFrameMs || 25_000;\n}\n\ntype Pending = {\n resolve: (v: unknown) => void;\n reject: (e: Error) => void;\n clear: () => void;\n method: string;\n};\n\nlet child: ChildProcessWithoutNullStreams | null = null;\nlet initialized = false;\nlet idCounter = 0;\nconst pending = new Map<number, Pending>();\nconst userSessions = new Map<string, string>(); // 本进程已建立的 session\nconst assembling = new Map<string, string[]>(); // sessionId -> 当前回合文本块\nlet stdoutBuf = \"\";\nlet lastFrameAt = 0; // 最近一次收到任何 stdout 帧的时间\nlet frameSeq = 0; // 收到帧的累计序号(用于判定本回合是否出过帧)\ntype ProgressEvent =\n | { type: \"tool\"; kind?: string; purpose?: string; title?: string }\n | { type: \"answer\" }\n | { type: \"thinking\" }\n | { type: \"activity\" };\nlet currentOnProgress: ((ev: ProgressEvent) => void) | null = null; // 当前回合进度回调\nlet answerStarted = false; // 当前回合是否已通知\"开始写回复\"\nlet thoughtStarted = false; // 当前回合是否已通知\"正在思考\"\nconst seenUpdateTypes = new Set<string>(); // 诊断:本回合出现过的 sessionUpdate 类型\nconst seenTools = new Set<string>(); // 当前回合已上报的工具标题,去重\nconst hintedSessions = new Set<string>(); // 已带上「可发图」约定提示的 session(每会话首轮带上一次)\n\n// ---- 并发门(切脑用)----\n// 一回合是一段长异步(可能几分钟):热切换必须先**等在跑的回合结束**,绝不在整回合上持锁。\n// 反过来,切换期间新回合要在门口等(有界),否则会与启动新大脑时**双起进程**(后台守护进程/重试也走这条路)。\nlet turnInFlight = false; // 有回合正在跑(askBrain / newSession 全程)\nlet switching = false; // 正在切换大脑\nconst SWITCH_WAIT_MS = Number(process.env.WEMIND_SWITCH_TIMEOUT_MS) || 120_000; // 回合等切换的上限,与切换自身的启动新大脑的上限一致\n\nexport function isTurnInFlight(): boolean {\n return turnInFlight;\n}\n\n/** 切换流程开始时调用:此后新回合在门口等,直到 `endSwitch()`。 */\nexport function beginSwitch(): void {\n switching = true;\n}\n\nexport function endSwitch(): void {\n switching = false;\n}\n\n/** 切换流程用:等在跑的回合结束(诚实失败——超时说明情况让用户重试,绝不半路砍掉回合)。 */\nexport async function drainTurn(timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (turnInFlight) {\n if (Date.now() > deadline) return false;\n await new Promise((r) => setTimeout(r, 100));\n }\n return true;\n}\n\n/** 回合入口用:切换进行中就先等它完成(有界),再继续——避免与启动新大脑抢同一个 ACP 进程。 */\nasync function waitWhileSwitching(): Promise<void> {\n if (!switching) return;\n dlog(\"turn waits for in-flight brain switch…\");\n await drainSwitch(SWITCH_WAIT_MS);\n}\n\nasync function drainSwitch(timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (switching) {\n if (Date.now() > deadline) return false;\n await new Promise((r) => setTimeout(r, 100));\n }\n return true;\n}\n\nfunction isAlive(): boolean {\n return !!child && child.exitCode === null && !child.killed;\n}\n\n/** 杀进程(按 PID 进程树)并清空内部态;不清磁盘 session 映射(留给重建后 load)。 */\nfunction reset(): void {\n const c = child;\n child = null;\n initialized = false;\n if (c?.pid) {\n try {\n if (process.platform === \"win32\")\n spawnSync(\"taskkill\", [\"/PID\", String(c.pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n else c.kill();\n } catch {\n /* ignore */\n }\n }\n writePidRecords(BRAIN_PID_FILE, []); // 没有进程 = 没有记录\n for (const p of pending.values()) {\n p.clear();\n p.reject(new Error(\"acp process reset\"));\n }\n pending.clear();\n userSessions.clear();\n assembling.clear();\n stdoutBuf = \"\";\n}\n\n// ---- 磁盘 session 映射(`<agent>:<userId>` -> sessionId),跨进程重建保上下文 ----\nconst keyOf = (ag: ResolvedAgent, userId: string): string => sessionKey(ag.name, userId);\n\nfunction loadSessionMap(ag: ResolvedAgent): Record<string, string> {\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(brainSessionMapPath(DATA_DIR), \"utf-8\"));\n } catch {\n return {};\n }\n const { map, migrated } = normalizeSessionMap(raw, ag.name);\n if (migrated) saveSessionMap(map); // 立即写入磁盘,避免下次换 agent 时旧条目再被“继承”一次\n return map;\n}\nfunction saveSessionMap(m: Record<string, string>): void {\n writeSessionMap(brainSessionMapPath(DATA_DIR), m); // 原子写:半截 JSON 等于所有会话丢失\n}\n\nfunction handleReverseRequest(f: Record<string, any>): void {\n const method = String(f.method);\n dlog(`← reverse request ${method}#${f.id}`);\n if (method === \"session/request_permission\") {\n const options = Array.isArray(f.params?.options) ? f.params.options : [];\n // 默认拒绝(安全);可信部署可显式开启一次性放行\n const allow =\n process.env.WEMIND_ACP_AUTO_ALLOW === \"1\"\n ? options.find((o: any) => o?.kind === \"allow_once\" || o?.kind === \"allow_always\")\n : undefined;\n const result = allow\n ? { outcome: { outcome: \"selected\", optionId: allow.optionId } }\n : { outcome: { outcome: \"cancelled\" } };\n child?.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", id: f.id, result }) + \"\\n\");\n dlog(`permission ${allow ? \"allowed\" : \"rejected\"}`);\n return;\n }\n // 未知反向请求也要回错误,否则对端会挂起\n child?.stdin.write(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: f.id,\n error: { code: -32601, message: \"method not supported\" },\n }) + \"\\n\",\n );\n}\n\nfunction handleFrame(f: Record<string, any>): void {\n lastFrameAt = Date.now();\n frameSeq += 1;\n if (typeof f.id === \"number\" && (f.result !== undefined || f.error !== undefined)) {\n const p = pending.get(f.id);\n if (!p) return;\n p.clear();\n pending.delete(f.id);\n if (f.error) {\n dlog(`← ${p.method}#${f.id} ERROR ${JSON.stringify(f.error).slice(0, 200)}`);\n p.reject(new Error(JSON.stringify(f.error)));\n } else p.resolve(f.result);\n return;\n }\n // agent -> client 的反向请求(带 method + id);必须应答,否则会挂到超时\n if (f.method && f.id !== undefined) {\n handleReverseRequest(f);\n return;\n }\n if (f.method === \"session/update\") {\n const u = f.params?.update;\n if (u?.sessionUpdate) seenUpdateTypes.add(String(u.sessionUpdate)); // 诊断\n // 任意 session/update 帧都视为\"活动\" → 让 daemon 静默刷新心跳基线(流式期间不误报\"仍在处理\")\n if (currentOnProgress) {\n try {\n currentOnProgress({ type: \"activity\" });\n } catch {\n /* ignore */\n }\n }\n if (u?.sessionUpdate === \"agent_message_chunk\" && u.content?.type === \"text\") {\n const arr = assembling.get(f.params.sessionId);\n if (arr) arr.push(String(u.content.text ?? \"\"));\n if (!answerStarted && currentOnProgress) {\n answerStarted = true;\n try {\n currentOnProgress({ type: \"answer\" });\n } catch {\n /* ignore */\n }\n }\n } else if (u?.sessionUpdate === \"agent_thought_chunk\") {\n if (!thoughtStarted && currentOnProgress) {\n thoughtStarted = true;\n try {\n currentOnProgress({ type: \"thinking\" });\n } catch {\n /* ignore */\n }\n }\n } else if (\n (u?.sessionUpdate === \"tool_call\" || u?.sessionUpdate === \"tool_call_update\") &&\n currentOnProgress\n ) {\n const tid = String(u.toolCallId ?? u.title ?? \"\"); // 每个工具调用只上报一次\n if (tid && !seenTools.has(tid)) {\n seenTools.add(tid);\n const purpose =\n typeof u.rawInput?.__tool_use_purpose === \"string\"\n ? u.rawInput.__tool_use_purpose\n : undefined;\n try {\n currentOnProgress({ type: \"tool\", kind: u.kind, purpose, title: u.title });\n } catch {\n /* ignore */\n }\n }\n }\n }\n}\n\nfunction onStdout(d: Buffer): void {\n stdoutBuf += d.toString();\n let i: number;\n while ((i = stdoutBuf.indexOf(\"\\n\")) >= 0) {\n const line = stdoutBuf.slice(0, i).trim();\n stdoutBuf = stdoutBuf.slice(i + 1);\n if (!line) continue;\n let f: Record<string, any>;\n try {\n f = JSON.parse(line);\n } catch {\n continue;\n }\n handleFrame(f);\n }\n}\n\nfunction request(method: string, params: unknown, timeoutMs: number): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!child) {\n reject(new Error(\"no acp process\"));\n return;\n }\n const id = ++idCounter;\n dlog(`→ ${method}#${id}`);\n const t = setTimeout(() => {\n pending.delete(id);\n reject(new Error(`${method} timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n pending.set(id, { resolve, reject, clear: () => clearTimeout(t), method });\n child.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", id, method, params }) + \"\\n\");\n });\n}\n\n/**\n * session/prompt 专用:空闲超时(PROMPT_IDLE_MS 内无任何帧才判卡死)+ 硬上限(PROMPT_CAP_MS)。\n * 只要 brain 还在出帧(工具进度/正文),就不打断——避免误杀\"慢但在干活\"的长查询。\n */\nfunction promptRequest(ag: ResolvedAgent, params: unknown): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!child) {\n reject(new Error(\"no acp process\"));\n return;\n }\n const id = ++idCounter;\n const startedAt = Date.now();\n lastFrameAt = Date.now(); // 重置空闲基线:从本次 prompt 发出算起,避免用上一回合的旧时间戳误判\n const startSeq = frameSeq; // 记录起点,用于判定本回合是否出过帧\n const firstFrame = firstFrameMs(ag);\n dlog(\n `→ session/prompt#${id} (idle=${PROMPT_IDLE_MS}ms cap=${PROMPT_CAP_MS}ms firstFrame=${firstFrame}ms)`,\n );\n const iv = setInterval(() => {\n const idle = Date.now() - lastFrameAt;\n const total = Date.now() - startedAt;\n const noFrameYet = frameSeq === startSeq; // 本回合从未出过帧\n const firstFrameTimeout = noFrameYet && total > firstFrame;\n if (firstFrameTimeout || idle > PROMPT_IDLE_MS || total > PROMPT_CAP_MS) {\n if (pending.delete(id)) {\n clearInterval(iv);\n const why = firstFrameTimeout ? \"no-first-frame\" : idle > PROMPT_IDLE_MS ? \"idle\" : \"cap\";\n reject(new Error(`session/prompt ${why} timeout (idle=${idle}ms total=${total}ms)`));\n }\n }\n }, 5000);\n pending.set(id, { resolve, reject, clear: () => clearInterval(iv), method: \"session/prompt\" });\n child.stdin.write(\n JSON.stringify({ jsonrpc: \"2.0\", id, method: \"session/prompt\", params }) + \"\\n\",\n );\n });\n}\n\nasync function ensureProcess(ag: ResolvedAgent): Promise<void> {\n if (isAlive() && initialized) return;\n reset();\n const kept: string[] = [];\n const env = agentEnv(ag, BRAIN_HOME, kept); // 父进程 env → 沙箱目录 → 预设 env → 预设 envDefaults(父进程已设的不覆盖)\n if (kept.length) dlog(`env defaults kept from parent: ${kept.join(\",\")}`); // 部署方显式设置的审批类开关\n const proc = spawnAgent(ag, { cwd: BRAIN_HOME, env });\n child = proc;\n dlog(`spawn ${ag.name} acp pid=${proc.pid} bin=${ag.bin} args=[${ag.args.join(\" \")}]`);\n // 身份记录(JSON):manage.ts 据此算进程特征串与展示 live 状态,不必猜「配置里的 agent 是不是正在跑的那个」\n writePidRecords(BRAIN_PID_FILE, [pidRecordOf(proc.pid!, ag)]);\n proc.stdout.on(\"data\", onStdout);\n proc.stderr.on(\"data\", () => {\n /* ignore */\n });\n // 只在\"退出的就是当前 child\"时才重置,避免旧进程迟到的 exit 误杀新进程\n proc.on(\"exit\", (code) => {\n // A:握手前就退出时,把「args 可能漏了 ACP 模式参数」直接写进日志(比只报一行 exited 好查)\n const hint = child === proc && !initialized ? acpArgsHint(ag) : \"\";\n dlog(`acp exited code=${code}${hint}`);\n if (child === proc) reset();\n });\n proc.on(\"error\", (e) => {\n dlog(`acp error ${String(e)}`);\n if (child === proc) reset();\n });\n try {\n await request(\"initialize\", { protocolVersion: 1, clientCapabilities: {} }, REQ_TIMEOUT);\n } catch (e) {\n // A:initialize 超时/失败最常见的原因是 args 覆盖漏了 ACP 子命令——把诊断提示附在同一行日志里\n dlog(`initialize failed: ${String(e)}${acpArgsHint(ag)}`);\n throw e;\n }\n initialized = true;\n dlog(\"initialized\");\n}\n\n/** 按配置选模型路由(如 DSH 的 deepseek-account);没有 model 的 agent 跳过。 */\nasync function applyModel(ag: ResolvedAgent, sid: string): Promise<void> {\n if (!ag.model) return;\n try {\n await request(\n \"session/set_config_option\",\n { sessionId: sid, configId: \"model\", value: ag.model },\n REQ_TIMEOUT,\n );\n dlog(`model set ${ag.model}`);\n } catch (e) {\n dlog(`model set failed (${String(e)})`);\n throw e;\n }\n}\n\n/** 从 `session/new|load|resume` 的回包里取可用模式 id;agent 不报 `modes` 时返回 null(=不校验,照样尝试)。 */\nfunction availableModeIds(res: any): string[] | null {\n const list = res?.modes?.availableModes;\n if (!Array.isArray(list)) return null;\n const ids = list.map((m: any) => String(m?.id ?? m)).filter(Boolean);\n return ids.length ? ids : null;\n}\n\n/** 会话建立/恢复后切到预设声明的模式(免审批:claude-code 的 bypassPermissions、codex 的 agent-full-access)。\n * **失败只记日志、不抛**:模式不可用(如 claude-code 以 root 跑却没有 IS_SANDBOX)时进程仍可用,\n * 审批请求继续由应答器处理(默认拒绝,WEMIND_ACP_AUTO_ALLOW=1 放行);抛错反而会把整轮打成失败。 */\nasync function applySessionMode(\n ag: ResolvedAgent,\n sid: string,\n modes: string[] | null = null,\n): Promise<void> {\n if (!ag.sessionMode) return;\n if (modes && !modes.includes(ag.sessionMode)) {\n dlog(\n `session mode ${ag.sessionMode} 不在 agent 报告的可用模式 [${modes.join(\",\")}] 里——跳过;审批请求仍由应答器处理`,\n );\n return;\n }\n try {\n await request(\"session/set_mode\", { sessionId: sid, modeId: ag.sessionMode }, REQ_TIMEOUT);\n dlog(`session mode set ${ag.sessionMode}`);\n } catch (e) {\n dlog(`session mode set failed (${String(e)})——继续;审批请求仍由应答器处理`);\n }\n}\n\nasync function ensureSession(ag: ResolvedAgent, userId: string): Promise<string> {\n const existing = userSessions.get(userId);\n if (existing) return existing;\n // 配了 mcpViaAcp 的 agent(DSH):激活集随 session 下发;其余 agent 传空(走各自沙箱配置文件)\n const mcpServers = ag.mcpViaAcp ? activeMcpServers() : [];\n const map = loadSessionMap(ag);\n const saved = map[keyOf(ag, userId)];\n if (saved) {\n // agent 指定 resume(DSH);其余仍先 load,失败再试 resume,最后 new\n const methods: Array<\"load\" | \"resume\"> =\n ag.restore === \"resume\" ? [\"resume\"] : [\"load\", \"resume\"];\n for (const method of methods) {\n try {\n const restored = await request(\n `session/${method}`,\n { sessionId: saved, cwd: BRAIN_HOME, mcpServers },\n REQ_TIMEOUT,\n );\n await applyModel(ag, saved);\n await applySessionMode(ag, saved, availableModeIds(restored));\n userSessions.set(userId, saved);\n markUsed(ag.name, userId, saved); // 会话登记表:把它顶到「最近使用」(列表按此排序)\n dlog(`session/${method} ok ${saved} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`);\n return saved;\n } catch (e) {\n dlog(`session/${method} failed (${String(e)})`);\n }\n }\n }\n const res = (await request(\"session/new\", { cwd: BRAIN_HOME, mcpServers }, REQ_TIMEOUT)) as {\n sessionId?: string;\n };\n if (!res?.sessionId) throw new Error(\"session/new returned no sessionId\");\n await applyModel(ag, res.sessionId);\n await applySessionMode(ag, res.sessionId, availableModeIds(res));\n userSessions.set(userId, res.sessionId);\n map[keyOf(ag, userId)] = res.sessionId;\n saveSessionMap(map);\n ensureEntry(ag.name, userId, res.sessionId); // 会话登记表:新会话进列表(turns 从 0 起)\n dlog(`session/new ok ${res.sessionId} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`);\n return res.sessionId;\n}\n\n/** 向大脑(配置的 ACP agent)提问(常驻 + 原生多轮)。失败即重建进程(下条用 session/load 恢复上下文)。 */\nexport async function askBrain(\n userId: string,\n message: string,\n onProgress?: (ev: ProgressEvent) => void,\n images?: Array<{ data: string; mimeType: string }>,\n): Promise<string> {\n await waitWhileSwitching(); // 切换进行中就先在门口等(有界):绝不在启动新大脑时双起进程\n const ag = getAgent(\"brain\"); // 等完之后才取槽位——拿到的是切换后的新大脑;此后全程用它,过程内不再读槽位\n turnInFlight = true; // 从这一刻起,切脑会等本回合结束(drainTurn)\n try {\n return await runTurn(ag, userId, message, onProgress, images);\n } finally {\n turnInFlight = false;\n }\n}\n\nasync function runTurn(\n ag: ResolvedAgent,\n userId: string,\n message: string,\n onProgress?: (ev: ProgressEvent) => void,\n images?: Array<{ data: string; mimeType: string }>,\n): Promise<string> {\n await ensureProcess(ag);\n const sid = await ensureSession(ag, userId);\n assembling.set(sid, []);\n currentOnProgress = onProgress ?? null;\n answerStarted = false;\n thoughtStarted = false;\n seenUpdateTypes.clear();\n seenTools.clear();\n const t0 = Date.now();\n const prompt: unknown[] = [];\n if (images?.length && !ag.images) {\n prompt.push({\n type: \"text\",\n text: \"(用户发来图片,但当前大脑不支持图片输入;请让用户改用文字,或切换到支持图片的大脑。不要臆测图片内容。)\",\n });\n }\n if (ag.images) {\n for (const im of images ?? [])\n prompt.push({ type: \"image\", mimeType: im.mimeType, data: im.data });\n }\n if (!hintedSessions.has(sid)) {\n hintedSessions.add(sid);\n prompt.push({\n type: \"text\",\n text: \"(系统提示:你正在通过微信和用户对话。需要给用户发图片时,在回复里用 Markdown 图片语法 ,系统会自动把它作为图片消息发出(你本身不会文生图,可发联网搜到的图、本地已有的图、或自己用代码渲染保存的图表)。重要:服务器从中国大陆下载该图,请选「可直接下载的直链」——优先 raw.githubusercontent.com 等原始文件直链;避免 wikipedia/wikimedia(大陆被墙)以及 pixabay/unsplash 等带热链保护、直链常返回 403 的图床。拿不准时优先把图先下载到本地再用本地绝对路径引用,或直接发链接让用户自己点。其余正常用文字/Markdown 回复。此提示只出现一次,无需理会本句。)\",\n });\n }\n prompt.push({ type: \"text\", text: message || \"(用户发来一张图片,请查看并回应)\" });\n try {\n await promptRequest(ag, { sessionId: sid, prompt });\n const txt = (assembling.get(sid) ?? []).join(\"\").trim();\n // 成功回合才登记:轮次 +1、刷新最近使用、首次写入标题(用户消息前 30 字;纯图片消息没有文字 → 留给下一次)\n // 标题取**剥掉交接前缀**后的正文:不然列表里全是「【交接摘要 · 来自 …】」,用户认不出哪条是哪条。\n touchEntry(ag.name, userId, sid, { title: titleOf(stripHandoffPrefix(message)) });\n dlog(\n `prompt ok ${Date.now() - t0}ms chunks=${(assembling.get(sid) ?? []).length} updateTypes=[${[...seenUpdateTypes].join(\",\")}]`,\n );\n return txt;\n } catch (e) {\n const chunks = (assembling.get(sid) ?? []).length;\n const sinceFrame = lastFrameAt ? Date.now() - lastFrameAt : -1;\n dlog(\n `prompt FAIL ${Date.now() - t0}ms chunks=${chunks} lastFrameAgo=${sinceFrame}ms err=${String(e)} -> rebuild`,\n );\n reset(); // 自愈:杀掉可能假死的进程,下条消息重建并 session/load 恢复\n throw e;\n } finally {\n currentOnProgress = null;\n assembling.delete(sid);\n }\n}\n\n/** 用户主动开新会话:丢弃旧 session(含磁盘映射),重建一个全新 session。 */\nexport async function newSession(userId: string): Promise<void> {\n await waitWhileSwitching();\n const ag = getAgent(\"brain\");\n turnInFlight = true; // 与 askBrain 共用「有回合在碰 ACP 进程」这一事实(切脑据此等待)\n try {\n const map = loadSessionMap(ag);\n delete map[keyOf(ag, userId)]; // 只清当前 agent 的;换大脑前那个 agent 的上下文仍保留\n saveSessionMap(map);\n userSessions.delete(userId);\n await ensureProcess(ag);\n await ensureSession(ag, userId);\n } finally {\n turnInFlight = false;\n }\n}\n\n/**\n * 「恢复会话」:把某条**已有的**会话切回当前,**严格恢复**——只认真接上的 load/resume,\n * 接不上就如实报错,**绝不**回退成 session/new(那会让用户以为上下文回来了,其实是一张白纸),\n * 也**不改**会话当前会话记录(当前会话原样保留,用户还能继续用)。\n * 成功才动磁盘:userSessions + 当前会话表(`<agent>:<userId>` → 这条)。\n */\nexport async function resumeSession(\n userId: string,\n sessionId: string,\n): Promise<{ ok: true } | { ok: false; problem: string }> {\n await waitWhileSwitching();\n const ag = getAgent(\"brain\");\n turnInFlight = true; // 与 askBrain 共用「有回合在碰 ACP 进程」这一事实(切脑据此等待)\n const short = sessionId.slice(0, 8);\n try {\n await ensureProcess(ag);\n const mcpServers = ag.mcpViaAcp ? activeMcpServers() : [];\n const methods: Array<\"load\" | \"resume\"> =\n ag.restore === \"resume\" ? [\"resume\"] : [\"load\", \"resume\"];\n let lastErr = \"\";\n for (const method of methods) {\n try {\n const restored = await request(\n `session/${method}`,\n { sessionId, cwd: BRAIN_HOME, mcpServers },\n REQ_TIMEOUT,\n );\n await applyModel(ag, sessionId);\n await applySessionMode(ag, sessionId, availableModeIds(restored));\n userSessions.set(userId, sessionId);\n const map = loadSessionMap(ag);\n map[keyOf(ag, userId)] = sessionId;\n saveSessionMap(map);\n dlog(`resume ok ${sessionId}`);\n return { ok: true };\n } catch (e) {\n lastErr = String(e);\n dlog(`resume session/${method} failed (${lastErr})`);\n }\n }\n return {\n ok: false,\n problem: `接不上会话 ${short}(该 agent 不支持恢复,或这条会话已被它自己清理)。当前会话没变,可以继续用;要开新的就发「新会话」。${lastErr ? `\\n(原因:${lastErr.slice(0, 120)})` : \"\"}`,\n };\n } finally {\n turnInFlight = false;\n }\n}\n\n/**\n * 切换流程用:为新 agent 备好进程与(触发者)会话——启动成功才算「新大脑真的能用 ACP 说话」。\n * 会话走既有 ensureSession:该 agent 里有本人的旧会话就 load/resume 恢复上下文,没有就新建。\n * `userId` 为空(CLI 触发、还不知道是谁在聊)时只验进程能握手。\n */\nexport async function warmup(ag: ResolvedAgent, userId: string): Promise<string> {\n await ensureProcess(ag);\n if (!userId) return \"\";\n return ensureSession(ag, userId);\n}\n\n/** MCP 激活集变更后调用:重启 acp 进程(重建后会 session/load 恢复上下文)。 */\nexport function restart(): void {\n reset();\n}\n\n/** 当前大脑是否接受图片输入(配置里的能力;默认 true)。 */\nexport function supportsImages(): boolean {\n return getAgent(\"brain\").images;\n}\n\n/** 当前 agent 的静默期心跳覆盖(默认 undefined → 15s)。按回合取:热切换后心跳随之改变。 */\nexport function heartbeatMs(): number | undefined {\n return getAgent(\"brain\").heartbeatMs;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { ResolvedAgent } from \"./brain-agents.js\";\n\n/**\n * daemon 启动/热切换成功后,把「这一刻真正生效的对话大脑」写进 `<账号目录>/brain-runtime.json`。\n *\n * 为什么还需要这个文件:`brain.pid` 只在 **ACP 子进程真的 spawn 之后**才有内容,而大脑是懒启动的——\n * 刚启动、还没收到消息时没有 pid 记录。此前 `wemind list` 的 `agent=` 只能显示 `brain.json`(有意「不看\n * 当前 shell 的环境变量」),于是用 `WEMIND_BRAIN=cline` 启动的 daemon 会一直显示成配置里的 deepseek,\n * 让人以为切换没生效。\n *\n * 这个文件记录的是 **daemon 进程自己的解析结果**(含来源 env/file/auto),与 CLI 端环境变量无关;\n * daemon 没运行时 `manage.ts` 不会采信它(`stop` 也会清掉),所以不会把旧状态当成现在时。\n */\nexport const BRAIN_RUNTIME_FILE = \"brain-runtime.json\";\n\nexport type BrainRuntime = {\n /** 实际生效的 agent 名(如 `cline`)。 */\n agent: string;\n /** 选择来源:环境变量 / brain.json / 自动探测。 */\n source: \"env\" | \"file\" | \"auto\";\n bin: string;\n args: string[];\n startedAt: number;\n};\n\n/** 写入运行时大脑(原子替换;失败静默——显示用,不该影响收发)。 */\nexport function writeBrainState(dir: string, ag: ResolvedAgent): void {\n const body: BrainRuntime = {\n agent: ag.name,\n source: ag.source,\n bin: ag.bin,\n args: ag.args,\n startedAt: Date.now(),\n };\n try {\n fs.mkdirSync(dir, { recursive: true });\n const file = path.join(dir, BRAIN_RUNTIME_FILE);\n const tmp = `${file}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(body, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, file);\n } catch {\n /* best-effort */\n }\n}\n\n/** 读运行时大脑;没有/损坏/字段不对 → null(调用方按「没有运行时信息」处理)。 */\nexport function readBrainState(dir: string): BrainRuntime | null {\n try {\n const j = JSON.parse(fs.readFileSync(path.join(dir, BRAIN_RUNTIME_FILE), \"utf-8\")) as Record<\n string,\n unknown\n >;\n if (typeof j?.agent !== \"string\" || !j.agent) return null;\n const src = j.source;\n const source: BrainRuntime[\"source\"] =\n src === \"env\" || src === \"auto\" ? src : src === \"file\" ? \"file\" : \"file\";\n return {\n agent: j.agent,\n source,\n bin: typeof j.bin === \"string\" ? j.bin : \"\",\n args: Array.isArray(j.args)\n ? (j.args as unknown[]).filter((x): x is string => typeof x === \"string\")\n : [],\n startedAt: Number(j.startedAt) || 0,\n };\n } catch {\n return null;\n }\n}\n\n/** 停止 daemon 时清掉,避免下次「已停止」还显示上一次的运行时大脑。 */\nexport function clearBrainState(dir: string): void {\n try {\n fs.unlinkSync(path.join(dir, BRAIN_RUNTIME_FILE));\n } catch {\n /* 不存在即可 */\n }\n}\n","import { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\nimport { getAgent, setAgent } from \"./agent-slot.js\";\nimport { resolvePresetByName, type ResolvedAgent } from \"./brain-agents.js\";\nimport { loadBrainConfig, updateBrainConfig } from \"./brain-config.js\";\nimport { ensureBrainHome } from \"./mcp-sandbox.js\";\nimport { writeBrainState } from \"./brain-state.js\";\nimport { beginSwitch, drainTurn, endSwitch, restart, warmup } from \"./acp-brain.js\";\n\n/**\n * 运行时切换对话大脑。\n *\n * 为什么需要它:agent 过去是模块级常量(进程启动时冻结),切脑只能 `wemind brain set X --restart`——\n * 重启整个 daemon,打断在途任务与回合。现在槽位可变(agent-slot.ts),于是「切脑」可以是一等命令。\n *\n * 流程与取舍(每一步都在防一种具体的坏结果):\n * 1. 解析目标 agent:**绕开环境变量**(用户此刻的明确意图该压过一次性 env 覆盖),不可用直接答复失败,不 spawn。\n * 2. `beginSwitch()` → `drainTurn()`:等在跑的回合结束(默认 30s)。超时**诚实失败**,绝不半路砍回合丢答案。\n * 3. `ensureBrainHome(新 agent)` → `restart()`(杀旧进程)→ `warmup()`(initialize + session/load|new,硬上限 120s)。\n * 4. 成功才 `setAgent()` + 写 brain.json —— **持久化放最后**,于是「退回」不需要撤销任何东西:\n * 失败路径只做 `restart()`(清掉半截进程与 pid 记录),槽位仍指旧大脑,下一条消息懒重建它(上下文从磁盘 load 回来)。\n * 5. 同一目标的并发请求复用同一次切换;不同目标直接拒绝(不排队,避免「连点两下切到 A」这类歧义)。\n *\n * 已知边界:`WEMIND_BRAIN` 环境变量优先级高于 brain.json,命令绕开了它(进程内真的切了),\n * 但**重启后会回到 env 指的大脑** —— 答复里如实警告,不装作没这回事。\n */\n\nconst DRAIN_MS = Number(process.env.WEMIND_SWITCH_DRAIN_MS) || 30_000;\nconst TIMEOUT_MS = Number(process.env.WEMIND_SWITCH_TIMEOUT_MS) || 120_000;\n\nexport type SwitchOutcome =\n | {\n ok: true;\n agent: ResolvedAgent;\n /** 是否真的换了(false = 本来就是它)。 */\n changed: boolean;\n ms: number;\n /** 面向用户的答复(含 env 覆盖等警告)。 */\n notice: string;\n }\n | { ok: false; problem: string };\n\nlet inFlight: { target: string; promise: Promise<SwitchOutcome> } | null = null;\n\n/** 正在切换的目标(没有则 null)——`/ai` 与答复文案用。 */\nexport function switchInFlightTarget(): string | null {\n return inFlight?.target ?? null;\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number, msg: string): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const t = setTimeout(() => reject(new Error(msg)), ms);\n t.unref?.();\n p.then(\n (v) => {\n clearTimeout(t);\n resolve(v);\n },\n (e) => {\n clearTimeout(t);\n reject(e);\n },\n );\n });\n}\n\n/** 环境变量若压着命令结果,答复里必须说清楚(否则用户会以为切换没生效,或下次重启后莫名回到旧的)。 */\nfunction envNote(key: string): string {\n const env = process.env.WEMIND_BRAIN?.trim();\n if (!env || env.toLowerCase() === key) return \"\";\n return `\\n⚠️ 环境变量 WEMIND_BRAIN=${env} 仍在生效(优先级高于 brain.json):本次已切到 ${key},但**重启后会回到 ${env}**。想长期用 ${key},请去掉这个环境变量。`;\n}\n\nfunction sameAgent(a: ResolvedAgent, b: ResolvedAgent): boolean {\n return (\n a.name === b.name &&\n a.bin === b.bin &&\n a.args.join(\"\\u0000\") === b.args.join(\"\\u0000\") &&\n // 模型也算「是不是同一个」:`brain set --model` 只写盘,运行时应用新模型的唯一途径就是走一次真切换\n // (restart → session/load → applyModel)。漏比 model 会让「改完再 switch」回「无需切换」,旧模型一直用下去。\n a.model === b.model\n );\n}\n\n/**\n * 预检一个切换目标:名字 → 可用 agent,并告诉你「是不是本来就已经是它」。\n * **不 spawn、不碰磁盘**(只读配置),所以调用方可以在发 ack 之前先判一次——\n * 避免对「未知 agent / 已装不上」也回一句「正在切换」,再补一条失败。\n */\nexport function preflightSwitch(target: string, dir: string = DATA_DIR): PreflightResult {\n const key = (target ?? \"\").trim().toLowerCase();\n if (!key) return { ok: false, problem: \"需要指定 agent 名(或 `auto` 重新自动探测)。\" };\n const r = resolvePresetByName(key, loadBrainConfig(dir));\n if (!r.ok) return { ok: false, problem: r.problem };\n const current = getAgent(\"brain\");\n return { ok: true, key, agent: r.agent, current, already: sameAgent(current, r.agent) };\n}\n\nexport type PreflightResult =\n | { ok: true; key: string; agent: ResolvedAgent; current: ResolvedAgent; already: boolean }\n | { ok: false; problem: string };\n\n/**\n * 切到某个 agent(`auto` 表示重新自动探测)。`userId` 是启动新大脑时按谁恢复会话(缺省不建会话)。\n * `persist=false`:只在本进程生效、不写 brain.json(CLI 的「仅本次」场景)。\n */\nexport async function switchBrain(\n target: string,\n opts: { userId?: string; dir?: string; persist?: boolean } = {},\n): Promise<SwitchOutcome> {\n const key = (target ?? \"\").trim().toLowerCase();\n if (!key) return { ok: false, problem: \"需要指定 agent 名(或 `auto` 重新自动探测)。\" };\n if (inFlight) {\n if (inFlight.target === key) return inFlight.promise; // 同一目标:复用同一次切换(重复请求不会切两遍)\n return { ok: false, problem: `正在切到「${inFlight.target}」,等它完成后再试。` };\n }\n const promise = runSwitch(key, opts);\n inFlight = { target: key, promise };\n try {\n return await promise;\n } finally {\n if (inFlight?.promise === promise) inFlight = null;\n }\n}\n\nasync function runSwitch(\n key: string,\n opts: { userId?: string; dir?: string; persist?: boolean },\n): Promise<SwitchOutcome> {\n const dir = opts.dir ?? DATA_DIR;\n const pre = preflightSwitch(key, dir);\n if (!pre.ok) return { ok: false, problem: pre.problem };\n const target = pre.agent;\n const current = pre.current;\n const note = envNote(key);\n\n if (pre.already) {\n // 已经是它:不重启进程(免得白白打断上下文),只把选择落实到磁盘\n if (opts.persist !== false) updateBrainConfig({ agent: key }, dir);\n writeBrainState(dir, target); // 运行时身份:list 显示实际生效的大脑\n return {\n ok: true,\n agent: target,\n changed: false,\n ms: 0,\n notice: `当前对话大脑已经是「${target.name}」,无需切换。${note}`,\n };\n }\n\n beginSwitch();\n try {\n const idle = await drainTurn(DRAIN_MS);\n if (!idle)\n return {\n ok: false,\n problem: `当前有回合正在进行(等了 ${Math.round(DRAIN_MS / 1000)} 秒仍未结束),这次切换没有执行——稍后再发一次即可。`,\n };\n\n const t0 = Date.now();\n try {\n ensureBrainHome(target); // 先备沙箱(只往新 agent 的配置目录里加东西,失败退回无需撤销)\n restart(); // 杀掉旧进程;旧会话在磁盘上,切回来时 session/load 恢复\n await withTimeout(warmup(target, opts.userId ?? \"\"), TIMEOUT_MS, warmupTimeoutMsg());\n setAgent(\"brain\", target); // 成功之后才更新槽位\n if (opts.persist !== false) updateBrainConfig({ agent: key }, dir); // 持久化放最后:失败无需撤销\n writeBrainState(dir, target); // 运行时身份:list 显示实际生效的大脑\n const ms = Date.now() - t0;\n return {\n ok: true,\n agent: target,\n changed: true,\n ms,\n notice: `✅ 已切换对话大脑:${current.name} → ${target.name}(${(ms / 1000).toFixed(1)}s)。${note}`,\n };\n } catch (e) {\n restart(); // 清掉半截进程与 pid 记录\n const why = e instanceof Error ? e.message : String(e);\n return {\n ok: false,\n problem: `切换失败,已退回原来那个「${current.name}」:${why}\\n下一条消息会自动重启旧大脑(上下文从磁盘恢复),不用手动处理。`,\n };\n }\n } finally {\n endSwitch();\n }\n}\n\nfunction warmupTimeoutMsg(): string {\n return `新大脑启动超时(${Math.round(TIMEOUT_MS / 1000)}s 内没有完成 initialize/会话)`;\n}\n","import { ACP_AGENT_PRESETS } from \"./brain-agents.js\";\n\n/**\n * 微信指令表:**解析与帮助是同一张表**(一处改动不会让「帮助里写的」和「真正认的」不一致)。\n *\n * 为什么独立成模块:`manage.ts`(CLI 侧)要用它渲染 `wemind help`,而 `manage.ts` **不能** import `index.ts`\n * (那会把 daemon、长轮询、账户状态全拖进每次 CLI 调用)。所以这里保持**无副作用**:不读环境、不碰磁盘、\n * 不在模块加载时解析任何东西——只做纯字符串匹配。\n *\n * 解析规则(与既有 parseControl/parseTaskControl 一致,**整串锚定**):\n * - 「整串锚定」意味着 `帮助我写代码`、`mcp 是什么`、`任务很重要` 都是**普通消息**(照旧转给大脑);\n * - `任务 <agent 名>:正文` 里的「冒号前」必须**恰好是一个已知 agent 名**才算指定 agent,\n * 所以 `任务 明天:去买菜`、`> nav: 去某地` 仍是普通内容(不会因为带冒号就被误当成指定 agent);\n * - `#<数字> 正文` 只在 `#`/`#` 后跟纯数字时才寻址(`#abc 你好` 是普通消息;`#1 号文件在哪` 说的是「1 号文件」,\n * 数字后紧跟「号」也按普通消息,见 task.reply 的 match);\n * - 中文写法与斜杠写法**等价**;`/ai` 家族是统一入口(`/ai`、`/ai brain X`、`/ai task X`)。\n */\n\nexport type ChatCommand =\n /** mcp 列表(沿用旧 kind 名,历史测试与调用方不受影响)。 */\n | { kind: \"list\" }\n /** 新会话(可命名)。 */\n | { kind: \"new\"; name?: string }\n | { kind: \"add\"; name: string }\n | { kind: \"remove\"; name: string }\n /** 会话列表(分页)。 */\n | { kind: \"sessions\"; page?: number }\n /** 恢复(切回)某个旧会话。 */\n | { kind: \"resume\"; ref: string }\n | { kind: \"rename\"; ref: string; name: string }\n | { kind: \"delete\"; ref: string }\n /** AI 总览:可选 agent + 当前对话大脑 / 默认任务大脑 / 池中进程。 */\n | { kind: \"ai\" }\n /** 切换对话大脑(无参 = 显示当前)。 */\n | { kind: \"brain\"; target?: string }\n /** 切默认任务大脑(本次运行内一直生效;不写 brain.json)。 */\n | { kind: \"workerBrain\"; target?: string }\n /** 生成切换用摘要并写入磁盘。 */\n | { kind: \"handoff\" }\n | { kind: \"help\" };\n\nexport type TaskCommand =\n /** 派发任务 / 给任务回话(可选指定 agent)。 */\n | { kind: \"toWorker\"; text: string; agentToken?: string }\n /** 结束任务(无 id = 唯一那条;`all` = 全部)。 */\n | { kind: \"stop\"; id?: number | \"all\" }\n /** 向指定编号的任务回话。 */\n | { kind: \"reply\"; id: number; text: string }\n /** 任务列表。 */\n | { kind: \"tasks\" };\n\n/** owner-only 的指令:非登录者发送 → 短提示拒绝,**不**转发给大脑(避免大脑替用户执行越权动作)。 */\nexport const OWNER_ONLY_KINDS: ReadonlySet<ChatCommand[\"kind\"]> = new Set([\n \"ai\",\n \"brain\",\n \"workerBrain\",\n \"handoff\",\n]);\n\nexport function isOwnerOnly(cmd: ChatCommand): boolean {\n return OWNER_ONLY_KINDS.has(cmd.kind);\n}\n\nexport type CommandSection = \"session\" | \"task\" | \"brain\" | \"mcp\" | \"misc\";\n\nexport type CommandSpec<C> = {\n id: string;\n section: CommandSection;\n /** 仅登录者可用(兜底校验:微信平台只发送登录者的消息,正常不会触发)。 */\n ownerOnly?: boolean;\n /** 帮助里的中文写法。 */\n zh: string;\n /** 等价的斜杠写法(无则省略)。 */\n slash?: string;\n /** 等价的 CLI 命令(`wemind help` 与文档用)。 */\n cli?: string;\n brief: string;\n match: (t: string) => C | null;\n};\n\n/** 可作为 agent 名的集合(小写):`任务 <agent 名>:正文` 的判定表;`auto` 也算(自动探测)。 */\nfunction agentTokenOk(token: string): boolean {\n const k = token.trim().toLowerCase();\n return k === \"auto\" || Object.prototype.hasOwnProperty.call(ACP_AGENT_PRESETS, k);\n}\n\n/** 中文里常见的「页」写法:`会话列表 2` / `会话列表 第2页` / `会话列表 2 页`。 */\nconst PAGE_SUFFIX = String.raw`(?:\\s*第?\\s*(\\d+)\\s*页?)?`;\n\nexport const COMMAND_SPECS: CommandSpec<ChatCommand>[] = [\n {\n id: \"mcp.list\",\n section: \"mcp\",\n zh: \"mcp\",\n slash: \"/mcp\",\n brief: \"列出可选/已激活的 MCP\",\n match: (t) => (/^(?:\\/?mcp|列出\\s*mcp|mcp\\s*list)$/i.test(t) ? { kind: \"list\" } : null),\n },\n {\n id: \"mcp.add\",\n section: \"mcp\",\n zh: \"加载 <名字>\",\n brief: \"加载一个 MCP(下条消息生效)\",\n match: (t) => {\n const m = t.match(/^(?:加载|load)\\s+(\\S+)$/i);\n return m ? { kind: \"add\", name: m[1] } : null;\n },\n },\n {\n id: \"mcp.remove\",\n section: \"mcp\",\n zh: \"卸载 <名字>\",\n brief: \"卸载一个 MCP\",\n match: (t) => {\n const m = t.match(/^(?:卸载|unload)\\s+(\\S+)$/i);\n return m ? { kind: \"remove\", name: m[1] } : null;\n },\n },\n {\n id: \"session.new\",\n section: \"session\",\n zh: \"新会话 [名字]\",\n slash: \"/new [名字]\",\n cli: \"wemind session new [名字]\",\n brief: \"清空上下文重开(可命名)\",\n match: (t) => {\n const m = t.match(/^(?:新会话|重置|清空上下文|\\/new|new\\s*session)(?:\\s+([\\s\\S]+))?$/i);\n if (!m) return null;\n const name = m[1]?.trim();\n return name ? { kind: \"new\", name } : { kind: \"new\" };\n },\n },\n {\n id: \"session.list\",\n section: \"session\",\n zh: \"会话列表 [页]\",\n slash: \"/sessions [页]\",\n cli: \"wemind session list [--page N]\",\n brief: \"列出本人会话(← 当前 标当前)\",\n match: (t) => {\n const m = t.match(\n new RegExp(\n String.raw`^(?:会话列表|查看会话|我的会话|\\/sessions?)` + PAGE_SUFFIX + \"$\",\n \"i\",\n ),\n );\n if (!m) return null;\n const page = m[1] ? Number(m[1]) : undefined;\n return page ? { kind: \"sessions\", page } : { kind: \"sessions\" };\n },\n },\n {\n id: \"session.resume\",\n section: \"session\",\n zh: \"恢复会话 <序号|名字|id前缀>\",\n slash: \"/resume <同上>\",\n cli: \"wemind session resume <ref>\",\n brief: \"切回旧会话(失败明确报错,不静默新建)\",\n match: (t) => {\n const m = t.match(/^(?:恢复会话|回到会话|切换会话|\\/resume)\\s+([\\s\\S]+)$/i);\n return m ? { kind: \"resume\", ref: m[1].trim() } : null;\n },\n },\n {\n id: \"session.rename\",\n section: \"session\",\n zh: \"重命名会话 <序号|名字> <新名字>\",\n slash: \"/rename <同上>\",\n cli: \"wemind session rename <ref> <名字>\",\n brief: \"给会话起名字(命名后不会被淘汰)\",\n match: (t) => {\n const m = t.match(/^(?:重命名会话|会话重命名|会话改名|\\/rename)\\s+(\\S+)\\s+([\\s\\S]+)$/i);\n return m ? { kind: \"rename\", ref: m[1], name: m[2].trim() } : null;\n },\n },\n {\n id: \"session.delete\",\n section: \"session\",\n zh: \"删除会话 <序号|名字|id前缀>\",\n slash: \"/delete <同上>\",\n cli: \"wemind session delete <ref>\",\n brief: \"只删登记(agent 侧的历史记录不动)\",\n match: (t) => {\n const m = t.match(/^(?:删除会话|删会话|\\/delete|\\/del)\\s+([\\s\\S]+)$/i);\n return m ? { kind: \"delete\", ref: m[1].trim() } : null;\n },\n },\n {\n id: \"agent.overview\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"AI / AI 列表\",\n slash: \"/ai\",\n cli: \"wemind brain\",\n brief: \"看可选 agent 与当前配置\",\n match: (t) => (/^(?:ai|ai\\s*列表|列出\\s*ai|\\/ai)$/i.test(t) ? { kind: \"ai\" } : null),\n },\n {\n id: \"agent.brain\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"切换大脑 [agent 名|auto]\",\n slash: \"/brain [agent 名|auto]、/ai brain [agent 名|auto]\",\n cli: \"wemind brain switch <agent 名|auto>\",\n brief: \"换对话大脑,不用重启(失败自动退回原来那个)\",\n match: (t) => {\n const m = t.match(/^(?:切换大脑|ai\\s*大脑|\\/brain|\\/ai\\s+brain)(?:\\s+([\\s\\S]+))?$/i);\n if (!m) return null;\n const target = m[1]?.trim();\n return target ? { kind: \"brain\", target } : { kind: \"brain\" };\n },\n },\n {\n id: \"agent.workerBrain\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"切换任务大脑 [agent 名|auto]\",\n slash: \"/worker [agent 名|auto]、/ai task [agent 名|auto]\",\n cli: \"wemind brain switch <agent 名> --worker\",\n brief: \"设默认任务大脑(本次运行内一直生效,不写 brain.json)\",\n match: (t) => {\n const m = t.match(\n /^(?:切换任务大脑|任务大脑|ai\\s*任务|\\/worker|\\/ai\\s+task)(?:\\s+([\\s\\S]+))?$/i,\n );\n if (!m) return null;\n const target = m[1]?.trim();\n return target ? { kind: \"workerBrain\", target } : { kind: \"workerBrain\" };\n },\n },\n {\n id: \"brain.handoff\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"交接\",\n slash: \"/handoff\",\n cli: \"wemind brain handoff\",\n brief: \"生成当前会话摘要,切换后给新大脑接手\",\n match: (t) => (/^(?:交接|会话交接|\\/handoff)$/i.test(t) ? { kind: \"handoff\" } : null),\n },\n {\n id: \"misc.help\",\n section: \"misc\",\n zh: \"帮助\",\n slash: \"/help\",\n cli: \"wemind help\",\n brief: \"这张表\",\n match: (t) => (/^(?:帮助|指令|命令|\\/help|\\/h)$/i.test(t) ? { kind: \"help\" } : null),\n },\n];\n\nexport const TASK_COMMAND_SPECS: CommandSpec<TaskCommand>[] = [\n {\n id: \"task.dispatch\",\n section: \"task\",\n zh: \"任务 [<agent 名>:]正文\",\n slash: \"/task [<agent 名>] 正文\",\n cli: 'wemind task \"正文\" [--agent <名字>]',\n brief: \"派发新任务;带 agent 名就由它执行(只影响这一条)\",\n match: (t) => {\n // ① `任务:正文` / `task: 正文`(沿用旧写法,不带 agent 名)\n let m = t.match(/^(?:任务|task)[::]\\s*([\\s\\S]+)$/i);\n if (m) return { kind: \"toWorker\", text: m[1].trim() };\n // ② `任务 <agent 名>:正文` —— 冒号前必须恰好是已知 agent 名,否则整串都是正文\n m = t.match(/^(?:任务|task)\\s+(\\S+?)[::]\\s*([\\s\\S]+)$/i);\n if (m) {\n if (agentTokenOk(m[1]))\n return { kind: \"toWorker\", text: m[2].trim(), agentToken: m[1].toLowerCase() };\n return { kind: \"toWorker\", text: t.replace(/^(?:任务|task)\\s+/i, \"\").trim() };\n }\n // ③ `/task <agent 名> 正文`(斜杠形式用空格分隔,agent 名同样必须命中)\n m = t.match(/^\\/task\\s+(\\S+)\\s+([\\s\\S]+)$/i);\n if (m && agentTokenOk(m[1]))\n return { kind: \"toWorker\", text: m[2].trim(), agentToken: m[1].toLowerCase() };\n m = t.match(/^\\/task\\s+([\\s\\S]+)$/i);\n if (m) return { kind: \"toWorker\", text: m[1].trim() };\n // ④ `> 正文` 快捷别名(沿用旧写法)\n m = t.match(/^>\\s*([\\s\\S]+)$/);\n if (m) return { kind: \"toWorker\", text: m[1].trim() };\n return null;\n },\n },\n {\n id: \"task.reply\",\n section: \"task\",\n zh: \"#<编号> 正文\",\n slash: \"/reply <编号> 正文\",\n cli: 'wemind task-reply <编号> \"正文\"',\n brief: \"给指定编号的任务回话(多任务时必须)\",\n match: (t) => {\n const m = t.match(/^(?:[##](\\d+)|\\/reply\\s+(\\d+))\\s+([\\s\\S]+)$/i);\n if (!m) return null;\n // `#1 号文件在哪` 说的是「1 号文件」,不是给 1 号任务回话 —— 数字后面紧跟「号」时按普通消息处理。\n if (m[1] !== undefined && m[3].startsWith(\"号\")) return null;\n return { kind: \"reply\", id: Number(m[1] ?? m[2]), text: m[3].trim() };\n },\n },\n {\n id: \"task.list\",\n section: \"task\",\n zh: \"任务列表\",\n slash: \"/tasks\",\n cli: \"wemind task-list\",\n brief: \"编号 / agent / 状态 / 轮次 / 已运行\",\n match: (t) => (/^(?:任务列表|任务清单|列出任务|\\/tasks?)$/i.test(t) ? { kind: \"tasks\" } : null),\n },\n {\n id: \"task.stop\",\n section: \"task\",\n zh: \"结束任务 [编号]、结束全部任务\",\n slash: \"/stop [编号|全部]\",\n cli: \"wemind task-stop [编号|全部]\",\n brief: \"结束任务(进程留着复用)\",\n match: (t) => {\n if (/^(?:结束全部任务|停止全部任务|\\/stop\\s+(?:all|全部))$/i.test(t))\n return { kind: \"stop\", id: \"all\" };\n const m = t.match(/^(?:结束任务|停止任务|\\/stop)(?:\\s*(\\d+))?$/i);\n if (!m) return null;\n return m[1] ? { kind: \"stop\", id: Number(m[1]) } : { kind: \"stop\" };\n },\n },\n];\n\n/** 解析一条微信指令(命中 → daemon 自己处理,不转发大脑);不命中 → null(普通消息)。 */\nexport function parseChatCommand(text: string): ChatCommand | null {\n const t = (text ?? \"\").trim();\n if (!t) return null;\n for (const spec of COMMAND_SPECS) {\n const hit = spec.match(t);\n if (hit) return hit;\n }\n return null;\n}\n\n/** 解析「任务/worker 相关」指令。先于普通消息判定,后于 parseChatCommand。 */\nexport function parseTaskCommand(text: string): TaskCommand | null {\n const t = (text ?? \"\").trim();\n if (!t) return null;\n for (const spec of TASK_COMMAND_SPECS) {\n const hit = spec.match(t);\n if (hit) return hit;\n }\n return null;\n}\n\nconst SECTION_TITLE: Record<CommandSection, string> = {\n session: \"会话(各自只管自己的)\",\n task: \"任务\",\n brain: \"大脑(仅登录者)\",\n mcp: \"MCP\",\n misc: \"\",\n};\n\n/** 渲染帮助只需这些字段(两种指令表的公共形状)。 */\ntype HelpSpec = {\n section: CommandSection;\n ownerOnly?: boolean;\n zh: string;\n slash?: string;\n cli?: string;\n brief: string;\n};\n\nfunction specLines(specs: HelpSpec[], mode: \"wechat\" | \"cli\"): string[] {\n const out: string[] = [];\n for (const s of specs) {\n if (mode === \"cli\") {\n out.push(s.cli ? ` ${s.cli} — ${s.brief}` : ` (无 CLI)${s.zh} — ${s.brief}`);\n } else {\n const alt = s.slash ? ` | ${s.slash}` : \"\";\n out.push(` ${s.zh}${alt} — ${s.brief}`);\n }\n }\n return out;\n}\n\n/**\n * 渲染帮助。`role` 非 owner 时**隐藏** owner-only 指令(只留一行说明),避免「看得见、发过去被拒」的挫败。\n * `mode=cli` 时同一张表换成 CLI 写法(`wemind help` 用);`sections` 可只取几个分区\n * (CLI 帮助里 MCP 是微信侧才有的操作,列进去只会是「(无 CLI)」噪音)。\n */\nexport function renderHelp(\n role: \"owner\" | \"user\" = \"owner\",\n mode: \"wechat\" | \"cli\" = \"wechat\",\n opts: { sections?: CommandSection[] } = {},\n): string {\n const chat = COMMAND_SPECS.filter((s) => role === \"owner\" || !s.ownerOnly);\n const tasks = TASK_COMMAND_SPECS;\n const head =\n mode === \"cli\"\n ? \"📖 WeMind 指令(CLI 写法;微信里中文/斜杠等价)\"\n : \"📖 WeMind 指令(中文与斜杠写法等价,任选一种)\";\n const lines: string[] = [head];\n const sections =\n opts.sections ?? ([\"session\", \"task\", \"brain\", \"mcp\", \"misc\"] as CommandSection[]);\n for (const section of sections) {\n const specs = section === \"task\" ? tasks : chat.filter((s) => s.section === section);\n if (!specs.length) continue;\n const title = SECTION_TITLE[section];\n if (title) lines.push(`【${title}】`);\n lines.push(...specLines(specs, mode));\n }\n if (role !== \"owner\")\n lines.push(\"(切换大脑 / 交接这类账号级指令仅登录者可用;会话与任务指令永远只属于你自己。)\");\n lines.push(\"提示:<尖括号> 表示占位内容,实际发送不要带尖括号。\");\n return lines.join(\"\\n\");\n}\n","import { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport {\n currentSessionIdOf,\n deleteSession,\n ensureEntry,\n labelOf,\n listSessions,\n markUsed,\n renameSession,\n renderSessionList,\n resolveRef,\n validateSessionName,\n} from \"./session-registry.js\";\n\n/**\n * 会话管理的**操作层**:把「新会话 / 列表 / 恢复 / 重命名 / 删除」拼成一句答复。\n *\n * 为什么单独成模块(而不是写在 index.ts 的 handleControl 里):\n * - 恢复是**有副作用的两步**(严格恢复的 load → 成功才改当前会话记录),值得单独钉测试;\n * - 依赖注入(`create`/`resume`)让测试不必起真 ACP 进程——ACP 侧的行为由 acp-switch.test.ts 用假 agent 覆盖,\n * 这里只验「顺序、失败不留痕、文案如实」。\n * 文案里的承诺必须与实现一致:恢复失败**不换当前会话记录**、删除**只删登记**(agent 侧归档不在我们手里)。\n */\n\n/** 恢复会话的实现(daemon 注入 acp-brain 的严格恢复(load);测试注入假实现)。 */\nexport type ResumeFn = (\n userId: string,\n sessionId: string,\n) => Promise<{ ok: true } | { ok: false; problem: string }>;\n\n/** 新建会话的实现(daemon 注入 acp-brain.newSession)。 */\nexport type CreateFn = (userId: string) => Promise<void>;\n\n/** 会话操作的最小上下文:谁、哪个大脑、哪个数据目录。 */\nexport type SessionCtx = {\n /** 当前对话大脑的 agent 名(登记表按 agent 分开存)。 */\n agent: string;\n /** 会话属于谁(微信 userId / CLI 的登录者)。 */\n userId: string;\n dir?: string;\n /** 注入 `now` 只为测试可复现;不传就用真实时间。 */\n now?: number;\n};\n\n/**\n * `新会话 [名字]`:先校验名字(非法就不建——不能因为名字打错白清一次上下文),再新建、再登记命名。\n * 名字登记在**新建出来的那条**上(登记表不存 current,所以按当前会话表读刚写下的 id)。\n */\nexport async function newSessionCmd(\n c: SessionCtx & { name?: string; create: CreateFn },\n): Promise<string> {\n const dir = c.dir ?? DATA_DIR;\n const name = c.name?.trim();\n if (name) {\n const bad = validateSessionName(name);\n if (bad) return `❌ ${bad}`;\n }\n await c.create(c.userId);\n const sid = currentSessionIdOf(c.agent, c.userId, dir);\n if (sid && name)\n ensureEntry(c.agent, c.userId, sid, { name, dir, ...(c.now ? { now: c.now } : {}) });\n return name\n ? `✅ 已开启新会话「${name}」(上下文已清空)。`\n : \"✅ 已开启新会话(上下文已清空)。\";\n}\n\n/** `会话列表 [页]`:本人 + 当前大脑;末尾提示其它大脑另有几条。 */\nexport function sessionsCmd(c: SessionCtx & { page?: number }): string {\n const dir = c.dir ?? DATA_DIR;\n const { entries, others, currentSessionId } = listSessions(c.agent, c.userId, dir);\n return renderSessionList(entries, {\n agent: c.agent,\n page: c.page,\n currentSessionId,\n others,\n now: c.now,\n });\n}\n\n/**\n * `恢复会话 <序号|名字|id前缀>`:ref 先在**当前大脑的本人会话**里解析,再走严格恢复(load)。\n * 成功才 markUsed(并已由 ACP 层改当前会话记录);失败**明确报错、当前会话不变**(绝不静默新建一条顶替)。\n */\nexport async function resumeSessionCmd(\n c: SessionCtx & { resume: ResumeFn },\n ref: string,\n): Promise<string> {\n const dir = c.dir ?? DATA_DIR;\n const { entries, currentSessionId } = listSessions(c.agent, c.userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return `❌ ${hit.error}`;\n if (hit.entry.sessionId === currentSessionId) {\n return `ℹ️ 当前就在这条会话上:${labelOf(hit.entry)}(${hit.entry.turns} 轮)。直接发消息即可。`;\n }\n const r = await c.resume(c.userId, hit.entry.sessionId);\n if (!r.ok) return `❌ ${r.problem}`;\n markUsed(c.agent, c.userId, hit.entry.sessionId, { dir, ...(c.now ? { now: c.now } : {}) });\n return `✅ 已切回会话 ${labelOf(hit.entry)}(${hit.entry.turns} 轮)。接下来发的消息会接上它的上下文。`;\n}\n\n/** `重命名会话 <ref> <名字>`:改名后不会被条数上限淘汰。 */\nexport function renameSessionCmd(c: SessionCtx, ref: string, name: string): string {\n const dir = c.dir ?? DATA_DIR;\n const bad = validateSessionName(name);\n if (bad) return `❌ ${bad}`;\n const { entries } = listSessions(c.agent, c.userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return `❌ ${hit.error}`;\n const before = labelOf(hit.entry);\n const r = renameSession(c.agent, c.userId, ref, name, dir);\n if (!r.ok) return `❌ ${r.error}`;\n return `✅ 已重命名:${before} → 「${name.trim()}」。`;\n}\n\n/**\n * `删除会话 <ref>`:只删登记项;删的若是当前会话则**同时清当前会话记录**(下一条消息自然开新的)。\n * agent 自己那份历史记录不在 WeMind 手里(ACP 无可验证的 session/delete),如实写在答复里。\n */\nexport function deleteSessionCmd(c: SessionCtx, ref: string): string {\n const dir = c.dir ?? DATA_DIR;\n const r = deleteSession(c.agent, c.userId, ref, { dir });\n if (!r.ok) return `❌ ${r.error}`;\n const tail = r.clearedPointer ? \"它就是当前会话,下一条消息会开一个新的。\" : \"当前会话没有变。\";\n return `✅ 已从列表删除:${labelOf(r.entry)}。${tail}\\n(agent 自己那份历史记录不在 WeMind 手里、仍留在它的存储里;这里删掉的是 WeMind 的列表登记。)`;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\n/**\n * CLI ↔ daemon 的控制桥(文件式):CLI 写 `control-inbox/<ts>-<id>.json` 请求,daemon 扫描执行后写\n * `control-out/<id>.json` 应答;CLI 短轮询应答并打印。\n *\n * 为什么要有它:切换大脑/开新会话/恢复会话都必须**在 daemon 进程里**发生(进程内的 ACP 连接、\n * 会话当前会话记录、任务表都是 daemon 的活状态)。CLI 另起一个进程去改文件只能改「下次启动才读」的配置,\n * 那就回到「切脑要重启 daemon、打断在途任务」的老问题。桥让 CLI 与微信指令走同一段代码。\n *\n * 与既有 task-inbox 的分工:task-inbox 是**单向**(CLI 派活,结果从微信回);控制桥要**回话**(CLI 要打印成功/失败),\n * 所以多了 out 目录与轮询。\n *\n * **安全边界**:桥只接受**agent 名与纯数据**(target/ref/name/text/id/page),**绝不接受 bin/args/shell**——\n * 就算被伪造(进程能写账号目录,task-inbox 本来也是这个性质),最坏也只是「选一个已装的 agent 或清掉自己的会话」,\n * 不能借此让 daemon 执行任意程序。`parseControlRequest` 是唯一的入口校验。\n */\n\nexport type ControlKind =\n /** 热切换对话大脑(写入磁盘 + 启动新大脑 + 失败退回)。 */\n | \"brain.switch\"\n /** 设默认任务大脑(本次运行内一直生效,不写入磁盘)。 */\n | \"brain.worker\"\n /** 生成交接摘要(写入磁盘,返回摘要正文)。 */\n | \"brain.handoff\"\n | \"session.new\"\n | \"session.list\"\n | \"session.resume\"\n | \"session.rename\"\n | \"session.delete\"\n | \"task.list\"\n | \"task.reply\"\n | \"task.stop\";\n\nconst KINDS: ReadonlySet<string> = new Set<ControlKind>([\n \"brain.switch\",\n \"brain.worker\",\n \"brain.handoff\",\n \"session.new\",\n \"session.list\",\n \"session.resume\",\n \"session.rename\",\n \"session.delete\",\n \"task.list\",\n \"task.reply\",\n \"task.stop\",\n]);\n\nexport type ControlRequest = {\n id: string;\n kind: ControlKind;\n at: number;\n /** agent 名(brain.switch / brain.worker)。 */\n target?: string;\n /** 会话引用:序号 / 名字 / id 前缀(session.resume / rename / delete)。 */\n ref?: string;\n /** 会话名字(session.new / session.rename)。 */\n name?: string;\n /** 任务正文(task.reply)。 */\n text?: string;\n /** 任务编号(task.reply / task.stop;\"all\" = 全部)。 */\n taskId?: number | \"all\";\n /** 会话列表页码。 */\n page?: number;\n};\n\nexport type ControlResponse = {\n id: string;\n at: number;\n /** 命令是否成功(决定 CLI 的退出码展示)。 */\n ok: boolean;\n /** 打印给用户的那段话(就是微信里会看到的那句)。 */\n text: string;\n};\n\nexport function controlInboxDir(dir: string = DATA_DIR): string {\n return path.join(dir, \"control-inbox\");\n}\nexport function controlOutDir(dir: string = DATA_DIR): string {\n return path.join(dir, \"control-out\");\n}\n\n/** 请求 id:带时间戳(文件名排序即时间序)+ 随机后缀(同一毫秒内不发重)。 */\nexport function newRequestId(now: number = Date.now()): string {\n return `${now}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\n/** 写请求(原子:tmp + rename,避免 daemon 读到半截 JSON)。 */\nexport function writeControlRequest(req: ControlRequest, dir: string = DATA_DIR): void {\n const d = controlInboxDir(dir);\n fs.mkdirSync(d, { recursive: true });\n const tmp = path.join(d, `${req.at}-${req.id}.json.tmp`);\n fs.writeFileSync(tmp, JSON.stringify(req, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, path.join(d, `${req.at}-${req.id}.json`));\n}\n\n/** 读收件箱(按文件名升序 = 时间序);`req` 为 null 表示坏文件/非法 kind(调用方直接丢弃)。 */\nexport function readControlInbox(\n dir: string = DATA_DIR,\n): { file: string; req: ControlRequest | null }[] {\n let files: string[];\n try {\n files = fs.readdirSync(controlInboxDir(dir)).filter((f) => f.endsWith(\".json\"));\n } catch {\n return [];\n }\n files.sort();\n return files.map((file) => {\n try {\n const raw = JSON.parse(fs.readFileSync(path.join(controlInboxDir(dir), file), \"utf-8\"));\n return { file, req: parseControlRequest(raw) };\n } catch {\n return { file, req: null };\n }\n });\n}\n\n/** 入口校验:只有已知 kind + 字符串/数字字段能通过(**没有** bin/args/shell 这类字段的位置)。 */\nexport function parseControlRequest(raw: unknown): ControlRequest | null {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n const o = raw as Record<string, unknown>;\n const kind = typeof o.kind === \"string\" ? o.kind : \"\";\n if (!KINDS.has(kind)) return null;\n const id = typeof o.id === \"string\" && o.id ? o.id : \"\";\n if (!id) return null;\n const str = (v: unknown): string | undefined =>\n typeof v === \"string\" && v.trim() ? v.trim().slice(0, 2000) : undefined;\n const req: ControlRequest = {\n id,\n kind: kind as ControlKind,\n at: typeof o.at === \"number\" && Number.isFinite(o.at) ? o.at : Date.now(),\n };\n const target = str(o.target);\n if (target) req.target = target;\n const ref = str(o.ref);\n if (ref) req.ref = ref;\n const name = str(o.name);\n if (name) req.name = name;\n const text = str(o.text);\n if (text) req.text = text;\n if (o.taskId === \"all\") req.taskId = \"all\";\n else if (typeof o.taskId === \"number\" && Number.isInteger(o.taskId) && o.taskId > 0)\n req.taskId = o.taskId;\n if (typeof o.page === \"number\" && Number.isInteger(o.page) && o.page > 0) req.page = o.page;\n return req;\n}\n\n/** 写应答(原子)。 */\nexport function writeControlResponse(res: ControlResponse, dir: string = DATA_DIR): void {\n const d = controlOutDir(dir);\n fs.mkdirSync(d, { recursive: true });\n const tmp = path.join(d, `${res.id}.json.tmp`);\n fs.writeFileSync(tmp, JSON.stringify(res, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, path.join(d, `${res.id}.json`));\n}\n\nexport function readControlResponse(id: string, dir: string = DATA_DIR): ControlResponse | null {\n try {\n const j = JSON.parse(fs.readFileSync(path.join(controlOutDir(dir), `${id}.json`), \"utf-8\"));\n if (!j || typeof j !== \"object\") return null;\n return {\n id: String(j.id ?? id),\n at: Number(j.at) || 0,\n ok: j.ok === true,\n text: typeof j.text === \"string\" ? j.text : \"\",\n };\n } catch {\n return null;\n }\n}\n\n/** 普通命令的等待上限(30s;`WEMIND_CONTROL_TIMEOUT_MS` 可调)。 */\nexport const CONTROL_TIMEOUT_DEFAULT_MS = Number(process.env.WEMIND_CONTROL_TIMEOUT_MS) || 30_000;\n\n/** 短轮询等应答;超时返回 null(CLI 据此如实说「没等到回复」而不是假装成功)。 */\nexport async function awaitControlResponse(\n id: string,\n dir: string = DATA_DIR,\n timeoutMs: number = CONTROL_TIMEOUT_DEFAULT_MS,\n pollMs = 100,\n): Promise<ControlResponse | null> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const r = readControlResponse(id, dir);\n if (r) return r;\n if (Date.now() >= deadline) return null;\n await new Promise((r2) => setTimeout(r2, pollMs));\n }\n}\n\n/** daemon 启动时清空桥目录(作废重启前的残留命令,防重放)。 */\nexport function clearControlDirs(dir: string = DATA_DIR): void {\n for (const d of [controlInboxDir(dir), controlOutDir(dir)]) {\n try {\n fs.rmSync(d, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n try {\n fs.mkdirSync(d, { recursive: true });\n } catch {\n /* ignore */\n }\n }\n}\n\n/**\n * 「这个 daemon 认识控制桥」的凭据:`control-out/.bridge`,内容是**它自己的 pid**。\n *\n * 为什么需要:升级 CLI 后 daemon 往往还在跑旧版本(它没有桥的扫描器)。CLI 若径直把请求写进收件箱,\n * 只会干等到超时(默认 30s,切换 165s),用户看到的是「莫名其妙失败」。有了凭据,CLI 能在**发出前**就说清\n * 「daemon 是旧版,重启一次再用」,把三分钟的静默等待换成一句能照做的话。\n */\nexport function bridgeMarkerPath(dir: string = DATA_DIR): string {\n return path.join(controlOutDir(dir), \".bridge\");\n}\n\nexport function markBridgeReady(dir: string = DATA_DIR, pid: number = process.pid): void {\n try {\n fs.mkdirSync(controlOutDir(dir), { recursive: true });\n fs.writeFileSync(bridgeMarkerPath(dir), JSON.stringify({ pid, at: Date.now() }), \"utf-8\");\n } catch {\n /* ignore */\n }\n}\n\n/** 凭据里的 pid;没有/坏了 → null(CLI 视为「daemon 不是认识桥的版本」)。 */\nexport function bridgePidOf(dir: string = DATA_DIR): number | null {\n try {\n const j = JSON.parse(fs.readFileSync(bridgeMarkerPath(dir), \"utf-8\"));\n return typeof j?.pid === \"number\" && j.pid > 0 ? j.pid : null;\n } catch {\n return null;\n }\n}\n\n/**\n * 按 kind 给等待上限:`brain.switch` 要跑「等在途回合结束 + 启动新大脑」(启动本身就有 120s 硬上限),\n * 用普通上限会让 CLI 在正确的长切换上误报超时。\n */\nexport function controlTimeoutMs(kind: ControlKind, env: NodeJS.ProcessEnv = process.env): number {\n const base = Number(env.WEMIND_CONTROL_TIMEOUT_MS) || CONTROL_TIMEOUT_DEFAULT_MS;\n if (kind !== \"brain.switch\") return base;\n const warm = Number(env.WEMIND_SWITCH_TIMEOUT_MS) || 120_000;\n const drain = Number(env.WEMIND_SWITCH_DRAIN_MS) || 30_000;\n return Math.max(base, warm + drain + 15_000);\n}\n","import fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * 从 brain 的文本回复里提取「要作为图片发送」的引用,并返回去掉这些引用后的纯文本。\n * 覆盖三种来源:\n * A. Markdown 图片 ``,src 可为 http(s) URL 或本地绝对路径;\n * B. brain 按约定输出的也是上述 Markdown 语法(见 acp-brain 注入的系统提示);\n * C. 回复中裸露的图片 URL(http(s) 且以图片扩展名结尾)。\n */\n\nconst IMG_EXT = /\\.(png|jpe?g|gif|webp|bmp)(\\?[^\\s)]*)?$/i;\nconst MAX_BYTES = 10 * 1024 * 1024;\n\nexport interface ExtractedImage {\n src: string;\n}\n\nexport function extractImages(text: string): { images: ExtractedImage[]; text: string } {\n const images: ExtractedImage[] = [];\n const seen = new Set<string>();\n const push = (src: string) => {\n const s = src.trim();\n if (s && !seen.has(s)) {\n seen.add(s);\n images.push({ src: s });\n }\n };\n\n let out = text ?? \"\";\n // A/B:Markdown 图片 \n out = out.replace(/!\\[[^\\]]*\\]\\(\\s*<?([^)\\s>]+)>?(?:\\s+\"[^\"]*\")?\\s*\\)/g, (_m, src: string) => {\n push(src);\n return \"\";\n });\n // C:裸图片 URL(排除 markdown 链接/图片里的,即前面不是 `(`)\n out = out.replace(\n /(?<!\\()(https?:\\/\\/[^\\s)]+\\.(?:png|jpe?g|gif|webp|bmp)(?:\\?[^\\s)]*)?)/gi,\n (m: string) => {\n push(m);\n return \"\";\n },\n );\n\n out = out\n .replace(/[ \\t]+\\n/g, \"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n return { images, text: out };\n}\n\n/** 把图片引用解析成字节:http(s) 下载,或本地文件读取。带大小/类型校验。 */\nexport async function resolveImageBytes(src: string): Promise<Buffer> {\n if (/^https?:\\/\\//i.test(src)) {\n const res = await fetch(src, {\n signal: AbortSignal.timeout(15000),\n // 不少图床(pixabay 等)对无 UA 的直链返回 403,带上浏览器 UA 规避热链拦截\n headers: {\n \"User-Agent\":\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\",\n },\n });\n if (!res.ok) throw new Error(`下载图片失败 ${res.status}`);\n const buf = Buffer.from(await res.arrayBuffer());\n if (buf.length > MAX_BYTES) throw new Error(`图片过大 ${buf.length}`);\n const ct = res.headers.get(\"content-type\") || \"\";\n if (!ct.startsWith(\"image/\") && !IMG_EXT.test(src)) throw new Error(`非图片内容(${ct})`);\n return buf;\n }\n const p = src.startsWith(\"file://\") ? fileURLToPath(src) : src;\n if (!IMG_EXT.test(p)) throw new Error(`非图片文件(仅按图片扩展名发送): ${p}`);\n const stat = fs.statSync(p);\n if (!stat.isFile()) throw new Error(\"不是文件\");\n if (stat.size > MAX_BYTES) throw new Error(`图片过大 ${stat.size}`);\n return fs.readFileSync(p);\n}\n","/**\n * 长回复转 Markdown 文件:超过阈值的回复正文,作为 .md 文件发送(一条消息),比拆成多条文本更不易触发 iLink 连续发送上限、且阅读体验完整。\n * 文件名由内容生成一个合适的标题。\n */\n\n/** 超过此字符数的回复转 .md 文件发送。可用 WECHAT_REPLY_FILE_THRESHOLD 覆盖。 */\nexport const LONG_REPLY_THRESHOLD = Number(process.env.WECHAT_REPLY_FILE_THRESHOLD) || 1500;\n\nconst ILLEGAL_FILENAME = /[\\\\/:*?\"<>|\\r\\n\\t]/g;\n\n/** 由内容生成一个合适的标题:取首个 Markdown 标题或首个非空行,清洗为合法文件名片段。 */\nexport function generateTitle(text: string): string {\n let first = \"\";\n for (const ln of text.split(/\\r?\\n/)) {\n const t = ln.trim();\n if (t) {\n first = t;\n break;\n }\n }\n // 去掉行首的 Markdown 标记(标题井号、列表符、有序号)\n first = first\n .replace(/^#{1,6}\\s*/, \"\")\n .replace(/^[-*+]\\s+/, \"\")\n .replace(/^\\d+[.)]\\s+/, \"\")\n .replace(/[*_`~]/g, \"\");\n first = first.replace(ILLEGAL_FILENAME, \" \").replace(/\\s+/g, \" \").trim();\n if (first.length > 40) first = first.slice(0, 40).trim();\n return first || \"回复\";\n}\n\n/**\n * 构造长回复的 md 文件(不写入磁盘,直接内存 buffer)。\n * 若正文未以 Markdown 标题开头,则前置一个由内容生成的 H1,便于打开后阅读。\n */\nexport function buildMdReply(text: string): { fileName: string; data: Buffer } {\n const title = generateTitle(text);\n const body = /^\\s*#/.test(text) ? text : `# ${title}\\n\\n${text}`;\n return { fileName: `${title}.md`, data: Buffer.from(body, \"utf-8\") };\n}\n","/**\n * 「必须送达」外发的挂起队列(重启后旧 context_token 期间)。\n *\n * 背景:daemon 启动时 `state.contextTokens` 里还是上次运行留下的 context_token,服务端已经认不得;新 token 只有该用户发来下一条消息时才会到(`index.ts` 的 poller 登记处)。这段时间里任务结果、CLI 指令答复这类「不在某个回合里」的推送会 ret=-2 失败,重试一次仍是旧 token,消息直接丢失。\n *\n * 做法:失败时按收件人把文本挂起(内存队列),等新 token 到达时按原顺序补发。队列只在内存里,daemon 再重启一次就丢(任务结果仍能在 `任务列表`/`tasks.json` 里看到)。\n */\nexport const PENDING_PUSH_LIMIT = 20;\n/** 一条推送最多补发几次(首次补发 + 失败后重挂 = 每次该用户发消息时试一次);超过就放弃并记日志——防一条内容永远被服务端拒绝的消息每次都拖着 replyChain。 */\nexport const PENDING_PUSH_MAX_TRIES = 3;\n\nexport type PendingPush = { text: string; tries: number };\n\nexport class PendingPushes {\n private readonly queues = new Map<string, PendingPush[]>();\n private readonly limit: number;\n\n constructor(limit: number = PENDING_PUSH_LIMIT) {\n this.limit = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : PENDING_PUSH_LIMIT;\n }\n\n /** 挂起一条(按收件人分开;`tries` 为已经补发失败的次数);超过上限丢最旧的——保留最新的更可能还有用。返回该收件人当前挂起的条数。 */\n push(to: string, text: string, tries = 0): number {\n const q = this.queues.get(to) ?? [];\n q.push({ text, tries });\n if (q.length > this.limit) q.splice(0, q.length - this.limit);\n this.queues.set(to, q);\n return q.length;\n }\n\n /** 取走该收件人最前面至多 `max` 条(按入队顺序);没取走的留在队里。没有则返回空数组。 */\n take(to: string, max: number = Number.POSITIVE_INFINITY): PendingPush[] {\n const q = this.queues.get(to);\n if (!q || q.length === 0) return [];\n const n = Math.max(0, Math.min(q.length, Math.floor(max)));\n const out = q.splice(0, n);\n if (q.length === 0) this.queues.delete(to);\n return out;\n }\n\n /** 该收件人当前挂起多少条。 */\n size(to: string): number {\n return this.queues.get(to)?.length ?? 0;\n }\n\n /** 全部收件人的挂起总条数(日志用)。 */\n total(): number {\n let n = 0;\n for (const q of this.queues.values()) n += q.length;\n return n;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\nimport { getAgent } from \"./agent-slot.js\";\nimport { readMcpServers, withMcpServers, parseJsonc, toAcpMcpServers } from \"./agent-mcp.js\";\nimport { copyProfilePatch } from \"./profile-model.js\";\n\n/**\n * worker 沙箱:与 brain 沙箱(mcp-sandbox.ts)并列,但 MCP 策略相反。\n *\n * worker 是无人值守的任务执行机,默认**继承真实配置目录的全部 MCP**(仅排除 wechat,防它把消息发回自己),而非 brain 的「默认空 + 对话中按需加载」——因为没人在旁边逐个加载工具,任务需要开箱即用的完整能力。\n * 记忆/技能/规则(mirrorRealKiro)与 settings(syncSettings,含模型/信任)的继承与 brain 一致。\n *\n * 按 agent 适配(同 mcp-sandbox.ts):配置目录、MCP 格式、设置继承方式均随 agent 而变。\n * agent 不再存成模块常量:函数显式收 `ag`,入口用 `getAgent(\"worker\")`(默认任务大脑)。\n *\n * 设计依据:docs/方案C-MVP-远程human-in-the-loop.md §8.4。\n * 与 mcp-sandbox.ts 有少量重复(mirrorRealKiro/syncSettings),刻意复制以隔离演进、不动已稳定的 brain 沙箱。\n */\n\n/** worker 专用沙箱根(由 agent 的 homeEnvVar 指向它)。与 agent 无关。 */\nexport const WORKER_HOME =\n process.env.WEMIND_WORKER_HOME?.trim() || path.join(DATA_DIR, \"worker-home\");\n\n/** 该 agent 在 worker 沙箱里的「配置目录」(kiro=沙箱根;opencode=<沙箱>/opencode)。 */\nfunction workerConfigDir(ag: ResolvedAgent): string {\n return path.join(WORKER_HOME, ag.configSubdir);\n}\n\nfunction workerMcpJson(ag: ResolvedAgent): string {\n return path.join(workerConfigDir(ag), ag.mcpConfigFile);\n}\n\n/** MCP「目录」:可继承的 MCP 定义来源(同 brain,默认取 agent 真实配置目录的 catalogFile)。 */\nfunction catalogPath(ag: ResolvedAgent): string {\n return (\n process.env.WEMIND_MCP_CATALOG?.trim() ||\n process.env.WECHAT_MCP_CATALOG?.trim() ||\n path.join(ag.realHome || path.join(os.homedir(), \".kiro\"), ag.catalogFile)\n );\n}\n\n/** worker 不继承的 MCP:wechat 始终强制排除(防它把消息发回自己),外加 WEMIND_WORKER_MCP_EXCLUDE 指定的。 */\nfunction excludeSet(): Set<string> {\n const raw = process.env.WEMIND_WORKER_MCP_EXCLUDE ?? \"wechat\";\n const s = new Set(\n raw\n .split(/[,\\s]+/)\n .map((x) => x.trim())\n .filter(Boolean),\n );\n s.add(\"wechat\"); // 强制:worker 加载 wechat 会用同一 bot token 长轮询、与 daemon 抢消息(坑①回环)\n return s;\n}\n\nfunction readJson(file: string): Record<string, unknown> | null {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/** 同 brain:把凭据文件(如 DSH 的 `.credentials.yaml`、Qoder 的 `.auth/user`)同步进 worker 沙箱。\n * `newer`=新者胜;`always`=内容不同即覆盖(标识类文件必须与真实目录一致,见 mcp-sandbox.ts)。 */\nfunction syncCredentialFile(ag: ResolvedAgent, file: string, mode: \"newer\" | \"always\"): void {\n if (!ag.realHome) return;\n const src = path.join(ag.realHome, file);\n const dst = path.join(workerConfigDir(ag), file);\n try {\n if (!fs.existsSync(src)) return;\n fs.mkdirSync(path.dirname(dst), { recursive: true });\n if (mode === \"always\") {\n const same = fs.existsSync(dst) && fs.readFileSync(src).equals(fs.readFileSync(dst));\n if (!same) fs.copyFileSync(src, dst);\n return;\n }\n const s = fs.statSync(src);\n const d = fs.existsSync(dst) ? fs.statSync(dst) : null;\n if (!d || s.mtimeMs > d.mtimeMs + 1000) fs.copyFileSync(src, dst);\n } catch {\n /* best-effort */\n }\n}\n\nfunction syncCredentials(ag: ResolvedAgent): void {\n for (const f of ag.credentialFiles) syncCredentialFile(ag, f, \"newer\");\n for (const f of ag.identityFiles) syncCredentialFile(ag, f, \"always\");\n}\n\nfunction loadCatalog(ag: ResolvedAgent): Record<string, unknown> {\n return readMcpServers(readJson(catalogPath(ag)), ag.mcpFormat);\n}\n\n/** worker 继承全量 MCP:复制 catalog 全部 server,去掉排除集与 disabled:true 的。\n * 配了 `mcpViaAcp` 的 agent(DSH)不写文件——全量集合经 workerMcpServers() 转成 ACP mcpServers 随 session 下发。 */\nfunction applyWorkerSandbox(ag: ResolvedAgent): void {\n if (ag.mcpViaAcp) return;\n const cat = loadCatalog(ag);\n const ex = excludeSet();\n const servers: Record<string, unknown> = {};\n for (const [name, def] of Object.entries(cat)) {\n if (ex.has(name)) continue;\n if (def && typeof def === \"object\" && (def as { disabled?: unknown }).disabled === true)\n continue; // 跳过用户已禁用的\n servers[name] = def;\n }\n const file = workerMcpJson(ag);\n const existing = readJson(file) ?? {};\n const next = withMcpServers(existing, servers, ag.mcpFormat);\n // opencode 的免审批只能走配置(`opencode acp` 不接受 --auto):默认 \"allow\",用户自己配了 permission 则以用户的为准。\n if (ag.mcpFormat === \"opencode\" && next.permission === undefined) next.permission = \"allow\";\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(next, null, 2), \"utf-8\");\n}\n\n/** worker 当前应挂载的 ACP mcpServers(配了 mcpViaAcp 的 agent 用):全量 catalog(除排除集/disabled/坏条目)→ ACP 声明。 */\nexport function workerMcpServers(forAgent?: ResolvedAgent) {\n const ag = forAgent ?? getAgent(\"worker\");\n const cat = loadCatalog(ag);\n const ex = excludeSet();\n const picked: Record<string, unknown> = {};\n for (const [name, def] of Object.entries(cat)) {\n if (ex.has(name)) continue;\n if (def && typeof def === \"object\" && (def as { disabled?: unknown }).disabled === true)\n continue;\n picked[name] = def;\n }\n const { servers, skipped } = toAcpMcpServers(picked, { exclude: ex });\n if (skipped.length)\n console.error(`[wemind/wechat-daemon/mcp] worker 跳过无法转换的 MCP:${skipped.join(\", \")}`);\n return servers;\n}\n\n/** kiro:继承真实 settings/ 下除 mcp.json 外的文件(cli.json 含模型/免确认等)。逻辑同 brain 的 syncKiroSettings。 */\nfunction syncKiroSettings(ag: ResolvedAgent): void {\n const realSettings = path.join(ag.realHome!, \"settings\");\n const workerSettings = path.join(workerConfigDir(ag), \"settings\");\n if (!fs.existsSync(realSettings)) return;\n try {\n fs.mkdirSync(workerSettings, { recursive: true });\n } catch {\n /* ignore */\n }\n let names: string[] = [];\n try {\n names = fs.readdirSync(realSettings);\n } catch {\n return;\n }\n for (const f of names) {\n if (f === \"mcp.json\") continue; // MCP 用 worker curated 版本(applyWorkerSandbox 全量除排除集)\n try {\n const src = path.join(realSettings, f);\n if (fs.statSync(src).isFile()) fs.copyFileSync(src, path.join(workerSettings, f));\n } catch {\n /* best-effort */\n }\n }\n}\n\n/** opencode:把用户全局 opencode.json(c)(去 mcp 段)作基底写入 worker 沙箱,继承 model 等;mcp 由 applyWorkerSandbox 覆盖。 */\nfunction syncOpencodeBase(ag: ResolvedAgent): void {\n const src = [\"opencode.json\", \"opencode.jsonc\"]\n .map((f) => path.join(ag.realHome!, f))\n .find((f) => fs.existsSync(f));\n if (!src) return;\n let parsed: Record<string, unknown> | null = null;\n try {\n const raw = fs.readFileSync(src, \"utf-8\");\n parsed = src.endsWith(\".jsonc\")\n ? parseJsonc(raw)\n : (JSON.parse(raw) as Record<string, unknown>);\n } catch {\n return;\n }\n if (!parsed || typeof parsed !== \"object\") return;\n delete (parsed as Record<string, unknown>).mcp;\n const file = workerMcpJson(ag);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(parsed, null, 2), \"utf-8\");\n}\n\n/** Qoder 这类「设置与 MCP 同文件」的 agent:与 brain 相同的基底疗法(剥离 MCP/插件/自定义供应商 + 关自动更新)。 */\nfunction settingsBase(ag: ResolvedAgent): Record<string, unknown> {\n let parsed: unknown = null;\n try {\n parsed = JSON.parse(fs.readFileSync(path.join(ag.realHome!, ag.settingsBase!), \"utf-8\"));\n } catch {\n /* 缺失/损坏 → 空基底 */\n }\n const base =\n parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? { ...(parsed as Record<string, unknown>) }\n : {};\n delete base.mcpServers;\n delete base.enabledPlugins;\n delete base.providers;\n delete base.model;\n const general = base.general;\n base.general = {\n ...(general && typeof general === \"object\" && !Array.isArray(general)\n ? (general as Record<string, unknown>)\n : {}),\n enableAutoUpdate: false,\n };\n return base;\n}\n\nfunction syncSettingsBase(ag: ResolvedAgent): void {\n const dir = workerConfigDir(ag);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(workerMcpJson(ag), JSON.stringify(settingsBase(ag), null, 2), \"utf-8\");\n}\n\n/** 继承真实 profile 的用户层(DSH:`profiles/<名>/cordis.patch.yml`,桌面端写 provider 目录与默认模型),**原样复制**。\n * 逻辑同 brain 的 syncProfilePatch:WeMind 只镜像用户配置、不合成(`- id: acp` 由用户自己维护,缺失时 CLI 提示怎么补);\n * **唯独剔除用户自己挂的微信 MCP 条目**(worker 的 MCP 也永远排除 `wechat`,防抢同一个 bot token)。 */\nfunction syncProfilePatch(ag: ResolvedAgent): void {\n if (!ag.realProfile || !ag.realHome || !ag.inherit) return;\n if (path.resolve(WORKER_HOME) === path.resolve(ag.realHome)) return;\n const src = path.join(ag.realHome, \"profiles\", ag.realProfile, \"cordis.patch.yml\");\n try {\n if (!fs.existsSync(src)) return;\n const removed = copyProfilePatch(ag.realHome, ag.realProfile, WORKER_HOME);\n if (removed.length) {\n console.error(\n `[wemind/wechat-daemon/mcp] 真实 profile 里的微信 MCP 已从 worker 沙箱副本剔除(防回环,真实文件未改):${removed.join(\", \")}`,\n );\n }\n } catch {\n /* best-effort */\n }\n}\n\nfunction syncSettings(ag: ResolvedAgent): void {\n syncProfilePatch(ag); // DSH 等:继承真实 profile 用户层(provider 目录 / 默认模型);MCP 仍走 ACP 注入\n if (ag.mcpViaAcp) return; // 无「配置文件式」的 MCP/settings 可继承(模型走 set_config_option)\n if (ag.settingsBase && ag.realHome) {\n syncSettingsBase(ag); // 不依赖 inherit:沙箱硬化(关自动更新)与隔离始终生效\n return;\n }\n if (!ag.inherit || !ag.realHome) return;\n if (ag.mcpFormat === \"opencode\") syncOpencodeBase(ag);\n else syncKiroSettings(ag);\n}\n\n/** 用 junction/symlink 把真实配置目录的记忆/技能/规则类子目录链入 worker 配置目录;单文件记忆直接拷。逻辑同 brain 的 mirrorRealKiro。 */\nfunction mirrorRealKiro(ag: ResolvedAgent): void {\n if (!ag.inherit || !ag.realHome) return;\n const dir = workerConfigDir(ag);\n if (path.resolve(WORKER_HOME) === path.resolve(ag.realHome)) return; // 安全:别把自己链给自己\n try {\n fs.mkdirSync(dir, { recursive: true });\n } catch {\n /* ignore */\n }\n for (const d of ag.inheritDirs) {\n const target = path.join(ag.realHome, d);\n const link = path.join(dir, d);\n if (!fs.existsSync(target)) continue;\n let present = false;\n try {\n fs.lstatSync(link);\n present = true;\n } catch {\n /* 不存在 */\n }\n if (present) {\n try {\n const st = fs.lstatSync(link);\n if (st.isSymbolicLink()) continue; // 已是链接 → 不动\n if (st.isDirectory()) {\n if (fs.readdirSync(link).length === 0) {\n fs.rmdirSync(link); // 空目录 → 删掉,下面改建 junction\n } else {\n // 非空真实目录:补拷真实目录里缺失的文件(不删已有)\n for (const f of fs.readdirSync(target)) {\n const dst = path.join(link, f);\n const src = path.join(target, f);\n try {\n if (!fs.existsSync(dst) && fs.statSync(src).isFile()) fs.copyFileSync(src, dst);\n } catch {\n /* ignore */\n }\n }\n continue;\n }\n } else continue;\n } catch {\n continue;\n }\n }\n try {\n fs.symlinkSync(target, link, process.platform === \"win32\" ? \"junction\" : \"dir\");\n } catch {\n /* 权限不足等:best-effort 忽略 */\n }\n }\n for (const f of ag.inheritFiles) {\n const src = path.join(ag.realHome, f);\n const dst = path.join(dir, f);\n try {\n if (fs.existsSync(src) && !fs.existsSync(dst)) fs.copyFileSync(src, dst);\n } catch {\n /* ignore */\n }\n }\n}\n\n/**\n * 启动时确保 worker 沙箱就绪:继承 settings(除 mcp)→ 写全量 MCP(除排除集)→ 链入记忆/技能 → 凭据。\n * `forAgent` 可显式指定 agent(多任务 / 进程池提前起进程时为**那个** agent 备沙箱,此刻槽位可能指着别的默认)。\n */\nexport function ensureWorkerHome(forAgent?: ResolvedAgent): void {\n const ag = forAgent ?? getAgent(\"worker\");\n syncSettings(ag); // 先同步「非 MCP」设置(模型/免确认等)\n applyWorkerSandbox(ag); // 再写全量 MCP(除 wechat 等排除集)\n mirrorRealKiro(ag); // 链入记忆/steering/skills(sessions 仍隔离)\n syncCredentials(ag); // 凭据(如 DSH 的 .credentials.yaml、Qoder 的 .auth/*)\n}\n","import { spawnSync, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { WORKER_HOME, workerMcpServers } from \"./worker-sandbox.js\";\nimport { spawnAgent } from \"./spawn-agent.js\";\nimport { normalizeSessionMap, sessionKey } from \"./session-map.js\";\nimport { acpArgsHint, agentEnv, type ResolvedAgent } from \"./brain-agents.js\";\n\n/**\n * worker ACP 连接:**一条连接=一个 ACP agent 进程**,约 70% 复制自 acp-brain.ts,差异:\n * - **每连接一实例**(child/pending/session/进度全在实例里,不再是模块级全局)——多任务并行要有多个进程;\n * 进程池(worker-pool.ts)持有它们、按 agent 复用,任务结束把连接**还给进程池**而不是杀掉;\n * - **每任务一个 session key**(`task-<编号>@<userId>`),持久化到 worker-sessions.json,重建后 session/load 续上下文;\n * - **cwd = 任务工作目录**(本地派发可指定,默认用户主目录),而沙箱走 WORKER_HOME(继承全量 MCP 除 wechat + 记忆);\n * - **更宽松超时**(漫长任务,env 可调);去掉 brain 的「可发图」首轮提示(任务执行不是聊天发图场景)。\n * 自愈:session/prompt 超时/失败 → 只杀**这一条**连接的进程;下次 ask 重建并 session/load 恢复(→ 用户回「继续」即可续跑)。\n */\n\nfunction dlog(...a: unknown[]): void {\n console.error(\"[wemind/wechat-daemon/worker]\", ...a);\n}\n\nconst WORKER_CWD = process.env.WEMIND_WORKER_CWD?.trim() || os.homedir(); // 任务工作目录默认值\nconst REQ_TIMEOUT = 30_000; // initialize / session/new / session/load\nconst PROMPT_IDLE_MS = Number(process.env.WEMIND_WORKER_IDLE_MS) || 180_000; // 无任何帧多久算\"卡死\"(比 brain 宽松:3min)\nconst PROMPT_CAP_MS = Number(process.env.WEMIND_WORKER_CAP_MS) || 1_800_000; // 单轮硬上限 30min(漫长任务)\nconst FIRST_FRAME_DEFAULT_MS = 30_000;\nconst SESSION_MAP_FILE = path.join(DATA_DIR, \"worker-sessions.json\");\n\n/** 首帧超时(配置可覆盖;每轮现算,热切换/指定 agent 后随之改变)。 */\nfunction firstFrameMs(a: ResolvedAgent): number {\n return (\n Number(process.env.WEMIND_WORKER_FIRST_FRAME_MS) || a.firstFrameMs || FIRST_FRAME_DEFAULT_MS\n );\n}\n\nexport type WorkerProgressEvent =\n | { type: \"tool\"; kind?: string; purpose?: string; title?: string }\n | { type: \"answer\" }\n | { type: \"thinking\" }\n | { type: \"activity\" };\n\ntype Pending = {\n resolve: (v: unknown) => void;\n reject: (e: Error) => void;\n clear: () => void;\n method: string;\n};\n\nlet connSeq = 0;\n\n// ---- 磁盘 session 映射(`<agent>:<sessionKey>` -> sessionId),与 brain 同样按 agent 分开存 ----\nfunction loadSessionMap(a: ResolvedAgent): Record<string, string> {\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(SESSION_MAP_FILE, \"utf-8\"));\n } catch {\n return {};\n }\n const { map, migrated } = normalizeSessionMap(raw, a.name);\n if (migrated) saveSessionMap(map); // 立即写入磁盘,避免下次换 agent 时旧条目再被“继承”一次\n return map;\n}\nfunction saveSessionMap(m: Record<string, string>): void {\n fs.mkdirSync(DATA_DIR, { recursive: true });\n const tmp = `${SESSION_MAP_FILE}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(m, null, 2), \"utf-8\");\n fs.renameSync(tmp, SESSION_MAP_FILE); // 原子写:多连接并发时会各自读改写这张表,半截文件会让读方整张丢\n}\n\n/** 一条连接:一个 ACP agent 进程 + 它自己的握手/会话/进度状态。 */\nexport class WorkerConn {\n readonly id: string;\n readonly agent: ResolvedAgent;\n /** 池用来标注「这条连接现在跑的是哪个任务」(空闲时为 undefined)。 */\n taskId?: number;\n\n private child: ChildProcessWithoutNullStreams | null = null;\n private initialized = false;\n private idCounter = 0;\n private pending = new Map<number, Pending>();\n private sessions = new Map<string, string>(); // sessionKey -> sessionId\n private assembling = new Map<string, string[]>(); // sessionId -> 当前轮文本块\n private stdoutBuf = \"\";\n private lastFrameAt = 0;\n private frameSeq = 0;\n private currentOnProgress: ((ev: WorkerProgressEvent) => void) | null = null;\n private answerStarted = false;\n private thoughtStarted = false;\n private seenUpdateTypes = new Set<string>();\n private seenTools = new Set<string>();\n private cwd?: string; // 本任务的工作目录(spawn 时生效;子进程 cwd 不可中途改)\n /** spawn 后立刻回调:池据此把 pid 身份记录写入磁盘(`wemind stop` 靠它精确找到本进程)。 */\n onSpawn?: (conn: WorkerConn) => void;\n\n constructor(agent: ResolvedAgent) {\n this.agent = agent;\n this.id = `w${++connSeq}`;\n }\n\n isAlive(): boolean {\n return !!this.child && this.child.exitCode === null && !this.child.killed;\n }\n\n pid(): number | null {\n return this.child?.pid ?? null;\n }\n\n /** 设置本任务的工作目录(下次 spawn 生效)。 */\n setCwd(cwd?: string): void {\n this.cwd = cwd?.trim() || undefined;\n }\n\n effectiveCwd(): string {\n return this.cwd ?? WORKER_CWD;\n }\n\n hasSession(key: string): boolean {\n return this.sessions.has(key);\n }\n\n /** 杀掉本连接的进程(按 PID 进程树)并清空内存态;不清磁盘 session 映射(留给重建后 load)。 */\n kill(): void {\n const c = this.child;\n this.child = null;\n this.initialized = false;\n if (c?.pid) {\n try {\n if (process.platform === \"win32\")\n spawnSync(\"taskkill\", [\"/PID\", String(c.pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n else c.kill();\n } catch {\n /* ignore */\n }\n }\n for (const p of this.pending.values()) {\n p.clear();\n p.reject(new Error(\"worker conn killed\"));\n }\n this.pending.clear();\n this.sessions.clear();\n this.assembling.clear();\n this.stdoutBuf = \"\";\n dlog(`${this.id}(${this.agent.name}) killed`);\n }\n\n /** 丢弃某个会话(内存 + 磁盘上的当前会话记录):任务结束用,下个任务全新开始。 */\n dropSession(key: string): void {\n this.sessions.delete(key);\n const map = loadSessionMap(this.agent);\n delete map[sessionKey(this.agent.name, key)];\n saveSessionMap(map);\n }\n\n /** 提前起进程:只做「起进程 + initialize」,不建会话(任务拿到连接时用;失败由调用方处理)。 */\n async warm(): Promise<void> {\n await this.ensureProcess();\n }\n\n /**\n * 发一轮 prompt(任务派发或回话续跑),等这一轮 ACP 响应结束,返回拼接文本。\n * 失败即杀本连接的进程(下一轮 ask 重建并 session/load 恢复上下文)→ 配合「继续」实现中断恢复。\n * `strictResume`:磁盘上有旧会话、但 load/resume 都失败时**不静默新建**,直接抛错(续跑场景要的是「上下文接上」,\n * 悄悄换一个空会话会让用户以为续上了、其实什么都不知道)。\n */\n async ask(\n instruction: string,\n key: string,\n onProgress?: (ev: WorkerProgressEvent) => void,\n opts: { strictResume?: boolean } = {},\n ): Promise<string> {\n await this.ensureProcess();\n const sid = await this.ensureSession(key, opts.strictResume === true);\n this.assembling.set(sid, []);\n this.currentOnProgress = onProgress ?? null;\n this.answerStarted = false;\n this.thoughtStarted = false;\n this.seenUpdateTypes.clear();\n this.seenTools.clear();\n const t0 = Date.now();\n const prompt: unknown[] = [{ type: \"text\", text: instruction || \"(继续)\" }];\n try {\n await this.promptRequest({ sessionId: sid, prompt });\n const txt = (this.assembling.get(sid) ?? []).join(\"\").trim();\n dlog(\n `${this.id}(${this.agent.name}) prompt ok ${Date.now() - t0}ms chunks=${(this.assembling.get(sid) ?? []).length} updateTypes=[${[...this.seenUpdateTypes].join(\",\")}]`,\n );\n return txt;\n } catch (e) {\n const chunks = (this.assembling.get(sid) ?? []).length;\n const sinceFrame = this.lastFrameAt ? Date.now() - this.lastFrameAt : -1;\n dlog(\n `${this.id}(${this.agent.name}) prompt FAIL ${Date.now() - t0}ms chunks=${chunks} lastFrameAgo=${sinceFrame}ms err=${String(e)} -> rebuild`,\n );\n this.kill();\n throw e;\n } finally {\n this.currentOnProgress = null;\n this.assembling.delete(sid);\n }\n }\n\n private write(line: string): void {\n this.child?.stdin.write(line + \"\\n\");\n }\n\n private handleReverseRequest(f: Record<string, any>): void {\n const method = String(f.method);\n dlog(`${this.id} ← reverse request ${method}#${f.id}`);\n if (method === \"session/request_permission\") {\n const options = Array.isArray(f.params?.options) ? f.params.options : [];\n const allow =\n process.env.WEMIND_ACP_AUTO_ALLOW === \"1\"\n ? options.find((o: any) => o?.kind === \"allow_once\" || o?.kind === \"allow_always\")\n : undefined;\n const result = allow\n ? { outcome: { outcome: \"selected\", optionId: allow.optionId } }\n : { outcome: { outcome: \"cancelled\" } };\n this.write(JSON.stringify({ jsonrpc: \"2.0\", id: f.id, result }));\n dlog(`permission ${allow ? \"allowed\" : \"rejected\"}`);\n return;\n }\n this.write(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: f.id,\n error: { code: -32601, message: \"method not supported\" },\n }),\n );\n }\n\n private handleFrame(f: Record<string, any>): void {\n this.lastFrameAt = Date.now();\n this.frameSeq += 1;\n if (typeof f.id === \"number\" && (f.result !== undefined || f.error !== undefined)) {\n const p = this.pending.get(f.id);\n if (!p) return;\n p.clear();\n this.pending.delete(f.id);\n if (f.error) {\n dlog(`${this.id} ← ${p.method}#${f.id} ERROR ${JSON.stringify(f.error).slice(0, 200)}`);\n p.reject(new Error(JSON.stringify(f.error)));\n } else p.resolve(f.result);\n return;\n }\n if (f.method && f.id !== undefined) {\n this.handleReverseRequest(f);\n return;\n }\n if (f.method === \"session/update\") {\n const u = f.params?.update;\n if (u?.sessionUpdate) this.seenUpdateTypes.add(String(u.sessionUpdate));\n if (this.currentOnProgress) {\n try {\n this.currentOnProgress({ type: \"activity\" });\n } catch {\n /* ignore */\n }\n }\n if (u?.sessionUpdate === \"agent_message_chunk\" && u.content?.type === \"text\") {\n const arr = this.assembling.get(f.params.sessionId);\n if (arr) arr.push(String(u.content.text ?? \"\"));\n if (!this.answerStarted && this.currentOnProgress) {\n this.answerStarted = true;\n try {\n this.currentOnProgress({ type: \"answer\" });\n } catch {\n /* ignore */\n }\n }\n } else if (u?.sessionUpdate === \"agent_thought_chunk\") {\n if (!this.thoughtStarted && this.currentOnProgress) {\n this.thoughtStarted = true;\n try {\n this.currentOnProgress({ type: \"thinking\" });\n } catch {\n /* ignore */\n }\n }\n } else if (\n (u?.sessionUpdate === \"tool_call\" || u?.sessionUpdate === \"tool_call_update\") &&\n this.currentOnProgress\n ) {\n const tid = String(u.toolCallId ?? u.title ?? \"\");\n if (tid && !this.seenTools.has(tid)) {\n this.seenTools.add(tid);\n const purpose =\n typeof u.rawInput?.__tool_use_purpose === \"string\"\n ? u.rawInput.__tool_use_purpose\n : undefined;\n try {\n this.currentOnProgress({ type: \"tool\", kind: u.kind, purpose, title: u.title });\n } catch {\n /* ignore */\n }\n }\n }\n }\n }\n\n private onStdout(d: Buffer): void {\n this.stdoutBuf += d.toString();\n let i: number;\n while ((i = this.stdoutBuf.indexOf(\"\\n\")) >= 0) {\n const line = this.stdoutBuf.slice(0, i).trim();\n this.stdoutBuf = this.stdoutBuf.slice(i + 1);\n if (!line) continue;\n let f: Record<string, any>;\n try {\n f = JSON.parse(line);\n } catch {\n continue; // 有的 agent 会往 stdout 混日志行(DSH 实测),非 JSON 直接跳过\n }\n this.handleFrame(f);\n }\n }\n\n private request(method: string, params: unknown, timeoutMs: number): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!this.child) {\n reject(new Error(\"no worker process\"));\n return;\n }\n const id = ++this.idCounter;\n dlog(`${this.id} → ${method}#${id}`);\n const t = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`${method} timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n this.pending.set(id, { resolve, reject, clear: () => clearTimeout(t), method });\n this.write(JSON.stringify({ jsonrpc: \"2.0\", id, method, params }));\n });\n }\n\n /** session/prompt 专用:空闲超时 + 首帧超时 + 硬上限(漫长任务,比 brain 宽松)。 */\n private promptRequest(params: unknown): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!this.child) {\n reject(new Error(\"no worker process\"));\n return;\n }\n const id = ++this.idCounter;\n const startedAt = Date.now();\n this.lastFrameAt = Date.now();\n const startSeq = this.frameSeq;\n const firstMs = firstFrameMs(this.agent);\n dlog(\n `${this.id} → session/prompt#${id} (idle=${PROMPT_IDLE_MS}ms cap=${PROMPT_CAP_MS}ms firstFrame=${firstMs}ms)`,\n );\n const iv = setInterval(() => {\n const idle = Date.now() - this.lastFrameAt;\n const total = Date.now() - startedAt;\n const noFrameYet = this.frameSeq === startSeq;\n const firstFrameTimeout = noFrameYet && total > firstMs;\n if (firstFrameTimeout || idle > PROMPT_IDLE_MS || total > PROMPT_CAP_MS) {\n if (this.pending.delete(id)) {\n clearInterval(iv);\n const why = firstFrameTimeout\n ? \"no-first-frame\"\n : idle > PROMPT_IDLE_MS\n ? \"idle\"\n : \"cap\";\n reject(new Error(`session/prompt ${why} timeout (idle=${idle}ms total=${total}ms)`));\n }\n }\n }, 5000);\n this.pending.set(id, {\n resolve,\n reject,\n clear: () => clearInterval(iv),\n method: \"session/prompt\",\n });\n this.write(JSON.stringify({ jsonrpc: \"2.0\", id, method: \"session/prompt\", params }));\n });\n }\n\n private async ensureProcess(): Promise<void> {\n if (this.isAlive() && this.initialized) return;\n this.kill();\n const kept: string[] = [];\n const env = agentEnv(this.agent, WORKER_HOME, kept); // 父进程 env → 沙箱目录 → 预设 env → 预设 envDefaults(父进程已设的不覆盖)\n if (kept.length) dlog(`${this.id} env defaults kept from parent: ${kept.join(\",\")}`);\n const cwd = this.effectiveCwd();\n const proc = spawnAgent(this.agent, {\n cwd, // 任务工作目录(本地派发可指定,否则默认 WORKER_CWD),区别于沙箱\n env,\n });\n this.child = proc;\n dlog(\n `${this.id} spawn ${this.agent.name} worker pid=${proc.pid} cwd=${cwd} home=${WORKER_HOME} task=${this.taskId ?? \"-\"}`,\n );\n this.onSpawn?.(this); // 立刻落 pid 身份记录(在握手之前,`wemind stop` 才来得及找到它)\n proc.stdout.on(\"data\", (d) => this.onStdout(d));\n proc.stderr.on(\"data\", () => {\n /* ignore */\n });\n proc.on(\"exit\", (code) => {\n // A:握手前就退出时附上 args 诊断(与 brain 侧同一提示,见 brain-agents.ts 的 acpArgsHint)\n const hint = this.child === proc && !this.initialized ? acpArgsHint(this.agent) : \"\";\n dlog(`${this.id}(${this.agent.name}) exited code=${code}${hint}`);\n if (this.child === proc) this.kill();\n });\n proc.on(\"error\", (e) => {\n dlog(`${this.id}(${this.agent.name}) error ${String(e)}`);\n if (this.child === proc) this.kill();\n });\n try {\n await this.request(\"initialize\", { protocolVersion: 1, clientCapabilities: {} }, REQ_TIMEOUT);\n } catch (e) {\n dlog(\n `${this.id}(${this.agent.name}) initialize failed: ${String(e)}${acpArgsHint(this.agent)}`,\n );\n throw e;\n }\n this.initialized = true;\n dlog(`${this.id} initialized`);\n }\n\n private async applyModel(sid: string): Promise<void> {\n if (!this.agent.model) return;\n try {\n await this.request(\n \"session/set_config_option\",\n { sessionId: sid, configId: \"model\", value: this.agent.model },\n REQ_TIMEOUT,\n );\n dlog(`${this.id} model set ${this.agent.model}`);\n } catch (e) {\n dlog(`${this.id} model set failed (${String(e)})`);\n throw e;\n }\n }\n\n /** 从 `session/new|load|resume` 的回包里取可用模式 id;agent 不报 `modes` 时返回 null(=不校验,照样尝试)。 */\n private static availableModeIds(res: any): string[] | null {\n const list = res?.modes?.availableModes;\n if (!Array.isArray(list)) return null;\n const ids = list.map((m: any) => String(m?.id ?? m)).filter(Boolean);\n return ids.length ? ids : null;\n }\n\n /** 会话建立/恢复后切到预设声明的模式(免审批);失败只记日志、不抛——审批请求仍由应答器兜底。 */\n private async applySessionMode(sid: string, modes: string[] | null = null): Promise<void> {\n if (!this.agent.sessionMode) return;\n if (modes && !modes.includes(this.agent.sessionMode)) {\n dlog(\n `${this.id} session mode ${this.agent.sessionMode} 不在可用模式 [${modes.join(\",\")}] 里——跳过;审批请求仍由应答器处理`,\n );\n return;\n }\n try {\n await this.request(\n \"session/set_mode\",\n { sessionId: sid, modeId: this.agent.sessionMode },\n REQ_TIMEOUT,\n );\n dlog(`${this.id} session mode set ${this.agent.sessionMode}`);\n } catch (e) {\n dlog(`${this.id} session mode set failed (${String(e)})——继续;审批请求仍由应答器处理`);\n }\n }\n\n /** 取会话:内存 → 磁盘(load/resume 恢复) → 新建。key 由调用方给(每任务一个)。\n * `strictResume`:有旧会话但恢复失败 → 抛「接不上」,绝不静默新建空会话。 */\n private async ensureSession(key: string, strictResume = false): Promise<string> {\n const existing = this.sessions.get(key);\n if (existing) return existing;\n // 配了 mcpViaAcp 的 agent(DSH):worker 继承全量 MCP(除排除集),随 session 下发;其余 agent 传空\n const mcpServers = this.agent.mcpViaAcp ? workerMcpServers(this.agent) : [];\n const mapKey = sessionKey(this.agent.name, key);\n const map = loadSessionMap(this.agent);\n const saved = map[mapKey];\n if (saved) {\n const methods: Array<\"load\" | \"resume\"> =\n this.agent.restore === \"resume\" ? [\"resume\"] : [\"load\", \"resume\"];\n for (const method of methods) {\n try {\n const restored = await this.request(\n `session/${method}`,\n { sessionId: saved, cwd: this.effectiveCwd(), mcpServers },\n REQ_TIMEOUT,\n );\n await this.applyModel(saved);\n await this.applySessionMode(saved, WorkerConn.availableModeIds(restored));\n this.sessions.set(key, saved);\n dlog(\n `${this.id} session/${method} ok ${saved} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`,\n );\n return saved;\n } catch (e) {\n dlog(`${this.id} session/${method} failed (${String(e)})`);\n }\n }\n if (strictResume)\n throw new Error(\n `接不上原会话 ${saved}(该 agent 不支持恢复、或这条会话已被它自己清理)。重试还是同一个结果——想重来请结束这条任务后重新派一条(把进展写进新任务里)。`,\n );\n }\n const res = (await this.request(\n \"session/new\",\n { cwd: this.effectiveCwd(), mcpServers },\n REQ_TIMEOUT,\n )) as { sessionId?: string };\n if (!res?.sessionId) throw new Error(\"session/new returned no sessionId\");\n await this.applyModel(res.sessionId);\n await this.applySessionMode(res.sessionId, WorkerConn.availableModeIds(res));\n this.sessions.set(key, res.sessionId);\n // 重新读改写:多个连接并存时,别用陈旧的记录整张覆盖(会抹掉别的任务刚写的 key)\n const fresh = loadSessionMap(this.agent);\n fresh[mapKey] = res.sessionId;\n saveSessionMap(fresh);\n dlog(\n `${this.id} session/new ok ${res.sessionId} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`,\n );\n return res.sessionId;\n }\n}\n\n/** 建一条连接(不 spawn;第一次 `warm()`/`ask()` 才起进程)。 */\nexport function createConn(agent: ResolvedAgent, onSpawn?: (c: WorkerConn) => void): WorkerConn {\n const c = new WorkerConn(agent);\n c.onSpawn = onSpawn;\n return c;\n}\n","import path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { createConn, type WorkerConn } from \"./worker-acp.js\";\nimport { pidRecordOf, writePidRecords, type PidRecord, type PidState } from \"./pid-record.js\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\n\n/**\n * worker 进程池:**一条任务一条 ACP 连接**,按 agent 复用常驻进程。\n *\n * 为什么需要池:多任务并行要求「a 大脑做任务1、b 大脑做任务2」,而一个 agent 进程同一时刻只跑一条会话;\n * 同时每个 agent 进程都是一个完整的 agent(内存/冷启动都不便宜),任务一结束就杀掉会让「紧接着派下一个任务」反复付冷启动。\n * 于是任务结束只是把连接**还给进程池**:同 agent 的下一个任务直接复用,空闲久了才回收。\n *\n * 策略(`planAcquire`/`planReap` 是纯函数,可单独测——IO 与判定分离):\n * - 同 agent 的空闲连接优先复用(**最旧的先用**:它本来就最接近被回收,先让它干活正好续命);\n * - 没有可复用的就 spawn;\n * - 同时运行的**任务**数上限 `WEMIND_MAX_WORKERS`(默认 3):超了**直接拒绝派发**(用户当场知道),不排队(排队会让任务悄悄等着);\n * - 空闲连接数上限 `WEMIND_IDLE_MAX`(默认 2)、空闲超 `WEMIND_WORKER_REAP_MS`(默认 5min)回收,都从最旧的开始;\n * - busy 连接永不回收(正在跑任务的进程,杀了就是丢任务)。\n *\n * 与 `WEMIND_WORKER_IDLE_MS` 的区别:那个是「单轮 prompt 多久没有帧算卡死」(既有语义,见 worker-acp.ts),\n * 这里是「空闲**连接**留多久」,两个时间尺度不同,故不复用同一个变量名。\n *\n * 同一 agent 并发 = 两个进程共用一个配置目录(等价「同一个 agent 开两个终端」):不同 agent 各有自己的子目录天然隔离,\n * 同 agent 的会话/缓存文件是否互踩是**待实测**项(见方案「待实测」5/6),文档如实写明。\n */\n\nexport const WORKERS_PID_FILE = path.join(DATA_DIR, \"workers.pid.json\");\n\nexport type PoolLimits = {\n /** 同时运行的任务数上限(每个运行中的任务占一个连接/进程)。 */\n maxConcurrent: number;\n /** 空闲连接数上限。 */\n idleMax: number;\n /** 空闲多久回收。 */\n idleMs: number;\n};\n\nexport function poolLimits(env: NodeJS.ProcessEnv = process.env): PoolLimits {\n const n = (v: string | undefined, d: number) => (Number(v) > 0 ? Number(v) : d);\n return {\n maxConcurrent: n(env.WEMIND_MAX_WORKERS, 3),\n idleMax: n(env.WEMIND_IDLE_MAX, 2),\n idleMs: n(env.WEMIND_WORKER_REAP_MS, 300_000),\n };\n}\n\n/** 连接的取用判定只需这些字段(纯函数入参,与真实 conn 解耦)。 */\nexport type ConnInfo = {\n id: string;\n agent: string;\n /** agent 身份键(名字+bin+args):不同的启动参数 = 不同的进程,不能互相复用。 */\n key: string;\n state: PidState;\n lastUsedAt: number;\n taskId?: number;\n};\n\nexport type AcquirePlan =\n | { kind: \"reuse\"; connId: string; reap: string[] }\n | { kind: \"spawn\"; reap: string[] }\n | { kind: \"reject\"; problem: string };\n\n/** agent 身份键:同一个 agent 但启动参数不同(如 `--bin` 改过)必须算两个进程。 */\nexport function agentKey(ag: ResolvedAgent): string {\n return `${ag.name}|${ag.bin}|${ag.args.join(\" \")}`;\n}\n\n/**\n * 决定「这条任务用哪条连接」。`reap` 里的是顺带该回收的空闲连接(调用方先复用/起新进程,再按它杀)。\n * 被复用的那条不会出现在 `reap` 里——哪怕它是过期的(复用比「先杀再起」便宜)。\n */\nexport function planAcquire(\n conns: ConnInfo[],\n key: string,\n limits: PoolLimits,\n now: number,\n): AcquirePlan {\n const busy = conns.filter((c) => c.state === \"busy\").length;\n if (busy >= limits.maxConcurrent)\n return {\n kind: \"reject\",\n problem: `同时运行的任务数已达上限(${limits.maxConcurrent} 个)。等一个跑完,或先「结束任务 <编号>」收掉不再需要的。`,\n };\n const reusable = conns\n .filter((c) => c.state === \"idle\" && c.key === key)\n .sort((a, b) => a.lastUsedAt - b.lastUsedAt || a.id.localeCompare(b.id))[0];\n if (reusable)\n return { kind: \"reuse\", connId: reusable.id, reap: planReap(conns, limits, now, reusable.id) };\n return { kind: \"spawn\", reap: planReap(conns, limits, now) };\n}\n\n/**\n * 该回收哪些空闲连接:① 空闲超时;② 回收后仍超过 idleMax 的部分(从最旧的开始)。\n * `keep` 是「刚用上的那条」——哪怕它最旧也不回收(收掉正在干活的东西是纯亏)。\n */\nexport function planReap(\n conns: ConnInfo[],\n limits: PoolLimits,\n now: number,\n keep?: string,\n): string[] {\n const idle = conns\n .filter((c) => c.state === \"idle\")\n .sort((a, b) => a.lastUsedAt - b.lastUsedAt || a.id.localeCompare(b.id));\n const reaped = new Set<string>();\n for (const c of idle) {\n if (c.id !== keep && now - c.lastUsedAt > limits.idleMs) reaped.add(c.id);\n }\n let excess = idle.filter((c) => !reaped.has(c.id)).length - limits.idleMax;\n for (const c of idle) {\n if (excess <= 0) break;\n if (c.id === keep || reaped.has(c.id)) continue;\n reaped.add(c.id);\n excess -= 1;\n }\n return [...reaped];\n}\n\ntype Entry = {\n conn: WorkerConn;\n key: string;\n state: PidState;\n taskId?: number;\n lastUsedAt: number;\n spawnedAt: number;\n};\n\nconst entries = new Map<string, Entry>(); // conn.id -> entry(Task 侧只拿 conn 引用,映射留在这里)\n\nfunction info(e: Entry): ConnInfo {\n const i: ConnInfo = {\n id: e.conn.id,\n agent: e.conn.agent.name,\n key: e.key,\n state: e.state,\n lastUsedAt: e.lastUsedAt,\n };\n if (e.taskId !== undefined) i.taskId = e.taskId;\n return i;\n}\n\n/** 池内记录(只读;判定一律走这个 + 纯函数)。 */\nexport function poolInfo(): ConnInfo[] {\n return [...entries.values()].filter((e) => e.conn.isAlive()).map(info);\n}\n\n/** 写入磁盘 pid 身份记录:`wemind stop` 据此精确找到全部 worker(多 agent 并存时新记录显得必要)。 */\nfunction writeRecords(): void {\n const recs: PidRecord[] = [];\n for (const e of entries.values()) {\n const pid = e.conn.pid();\n if (pid === null || !e.conn.isAlive()) continue;\n const extra: { taskId?: number; state: PidState } = { state: e.state };\n if (e.taskId !== undefined) extra.taskId = e.taskId;\n const rec = pidRecordOf(pid, e.conn.agent, extra);\n rec.startedAt = e.spawnedAt;\n recs.push(rec);\n }\n writePidRecords(WORKERS_PID_FILE, recs);\n}\n\nfunction reapByIds(ids: string[]): void {\n let changed = false;\n for (const id of ids) {\n const e = entries.get(id);\n if (!e || e.state === \"busy\") continue; // 复核:判定之后可能已被别的任务取用\n e.conn.kill();\n entries.delete(id);\n changed = true;\n }\n if (changed) writeRecords();\n}\n\n/**\n * 取一条连接跑任务:命中空闲就复用,否则起新进程(spawn + initialize 成功才算拿到)。\n * 同步登记(busy)**在** await spawn 之前——否则两个任务会同时规划到「空闲」的同一状态、各起一个进程。\n */\nexport async function acquire(\n ag: ResolvedAgent,\n taskId?: number,\n): Promise<{ ok: true; conn: WorkerConn } | { ok: false; problem: string }> {\n const key = agentKey(ag);\n const plan = planAcquire(poolInfo(), key, poolLimits(), Date.now());\n if (plan.kind === \"reject\") return { ok: false, problem: plan.problem };\n reapByIds(plan.reap);\n\n if (plan.kind === \"reuse\") {\n const e = entries.get(plan.connId);\n if (e) {\n e.state = \"busy\";\n e.taskId = taskId;\n e.lastUsedAt = Date.now();\n writeRecords();\n return { ok: true, conn: e.conn };\n }\n // 理论上到不了(plan 与 entries 之间没有 await);真到了就当真·spawn 处理,宁可多起一个也不假装拿到了\n }\n\n const conn = createConn(ag, () => {\n const e = entries.get(conn.id);\n if (e) {\n e.spawnedAt = Date.now();\n writeRecords(); // 进程刚起来就落记录(握手之前),`wemind stop` 才来得及抓到它\n }\n });\n const entry: Entry = {\n conn,\n key,\n state: \"busy\",\n lastUsedAt: Date.now(),\n spawnedAt: Date.now(),\n ...(taskId !== undefined ? { taskId } : {}),\n };\n entries.set(conn.id, entry); // 先登记:并发的第二个 acquire 立刻能看到它(busy)\n try {\n await conn.warm();\n writeRecords();\n return { ok: true, conn };\n } catch (e) {\n entries.delete(conn.id);\n conn.kill();\n writeRecords();\n const why = e instanceof Error ? e.message : String(e);\n return { ok: false, problem: `起 worker 进程失败(${ag.name}):${why}` };\n }\n}\n\n/** 任务结束:把连接还给进程池(置空闲)并按策略回收多余/过期的空闲连接;连接已经死了就地清掉。 */\nexport function release(conn: WorkerConn, limits: PoolLimits = poolLimits()): void {\n const e = entries.get(conn.id);\n if (!e) return;\n if (!conn.isAlive()) {\n entries.delete(conn.id); // 进程已死(任务中途被停/崩溃):没有可复用的东西,别在池里留空壳\n writeRecords();\n return;\n }\n e.state = \"idle\";\n delete e.taskId;\n e.lastUsedAt = Date.now();\n reapByIds(planReap(poolInfo(), limits, Date.now(), conn.id));\n writeRecords();\n}\n\n/** 定时清扫(index.ts 起 interval):清掉已死的空壳 + 处理「空闲过期/超编」,不动 busy。 */\nexport function sweep(limits: PoolLimits = poolLimits()): void {\n let changed = false;\n for (const [id, e] of entries) {\n if (e.conn.isAlive()) continue;\n entries.delete(id);\n changed = true;\n }\n const reap = planReap(poolInfo(), limits, Date.now());\n if (changed || reap.length) {\n reapByIds(reap);\n writeRecords();\n }\n}\n\n/**\n * 杀掉池里**全部**连接——只给「daemon 退出」用(`task-manager.ts` 的 `shutdownTasks()`,由 `wemind stop` / 进程退出触发)。\n * 结束单条任务**不经过这里**:那条路走 `task-manager.ts` 的「等回复的 `release()` 留着复用 / 正在跑的 `drop()` 杀一条」。\n */\nexport function killAll(): void {\n for (const e of entries.values()) e.conn.kill();\n entries.clear();\n writePidRecords(WORKERS_PID_FILE, []);\n}\n\n/** 弃用某条连接(任务被停时正在跑的那条):杀进程 + 从池里摘掉。 */\nexport function drop(conn: WorkerConn): void {\n conn.kill();\n if (entries.delete(conn.id)) writeRecords();\n}\n\n/** 只回收空闲连接(有任务在跑时用)。 */\nexport function killIdle(): void {\n let changed = false;\n for (const [id, e] of entries) {\n if (e.state !== \"idle\") continue;\n e.conn.kill();\n entries.delete(id);\n changed = true;\n }\n if (changed) writeRecords();\n}\n\n/** 池状态摘要:给 `wemind list` / 微信 `AI` 展示「哪些 agent 有几条忙/闲」。 */\nexport function poolSummary(): Array<{\n agent: string;\n busy: number;\n idle: number;\n taskIds: number[];\n}> {\n const byAgent = new Map<string, { busy: number; idle: number; taskIds: number[] }>();\n for (const e of entries.values()) {\n if (!e.conn.isAlive()) continue;\n let s = byAgent.get(e.conn.agent.name);\n if (!s) {\n s = { busy: 0, idle: 0, taskIds: [] };\n byAgent.set(e.conn.agent.name, s);\n }\n if (e.state === \"busy\") {\n s.busy += 1;\n if (e.taskId !== undefined) s.taskIds.push(e.taskId);\n } else s.idle += 1;\n }\n return [...byAgent.entries()].map(([agent, s]) => ({ agent, ...s }));\n}\n\n/** 某条任务当前用的连接(任务表与池的桥)。 */\nexport function connOfTask(taskId: number): WorkerConn | null {\n for (const e of entries.values()) if (e.taskId === taskId) return e.conn;\n return null;\n}\n\n/** 测试用:清空池(默认连进程一起杀)。 */\nexport function resetPoolForTest(kill = true): void {\n if (kill) for (const e of entries.values()) e.conn.kill();\n entries.clear();\n writePidRecords(WORKERS_PID_FILE, []);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\n/**\n * 任务表写入磁盘(`tasks.json`):只为「daemon 重启后能续跑」这一件事,不做历史归档\n * (微信聊天记录本身就是每条任务输出的归档,另存历史收益低、维护面大)。\n *\n * 三条设计约束:\n * - **编号跨重启不归零**:`nextId` 按用户单调递增、只增不减,重启后接着排(否则「任务2」在对话里会指代变化);\n * - **不自动恢复**:重启后未完成的任务一律标 `interrupted`,只在 `任务列表` 可见;用户回「继续」/`#n 继续` 才拉进程续跑\n * (3 个 agent 一起冷启动既重、也可能正好复现刚崩的原因);\n * - **损坏不阻塞启动**:读失败降级为空表(只记日志)——任务表坏了顶多「列表空」,绝不能让 daemon 起不来。\n */\n\nexport const TASK_STORE_VERSION = 1;\n\nexport type StoredTaskStatus = \"running\" | \"waiting\" | \"interrupted\";\n\nexport type StoredTask = {\n id: number;\n userId: string;\n agent: string;\n /** 会话键(`task-<id>@<userId>`):worker-sessions.json 里 `<agent>:<key>` 指向的 sessionId。 */\n key: string;\n status: StoredTaskStatus;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n /** 任务工作目录(本地派发可指定;续跑要还原成同一个 cwd)。 */\n cwd?: string;\n};\n\nexport type TaskStore = {\n version: number;\n tasks: StoredTask[];\n /** 按用户的「下一个编号」:只增,永不复用,跨重启连续。 */\n nextId: Record<string, number>;\n};\n\nexport function taskStorePath(dir: string = DATA_DIR): string {\n return path.join(dir, \"tasks.json\");\n}\n\nexport function emptyTaskStore(): TaskStore {\n return { version: TASK_STORE_VERSION, tasks: [], nextId: {} };\n}\n\n/** 读任务表:缺文件/坏 JSON/字段离谱 → 空表(降级,不抛)。 */\nexport function loadTaskStore(dir: string = DATA_DIR): TaskStore {\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(taskStorePath(dir), \"utf-8\"));\n } catch {\n return emptyTaskStore();\n }\n const j = raw as Record<string, unknown> | null;\n if (!j || typeof j !== \"object\" || Array.isArray(j)) return emptyTaskStore();\n const tasks: StoredTask[] = [];\n if (Array.isArray(j.tasks)) {\n for (const e of j.tasks as Array<Record<string, unknown>>) {\n const id = Number(e?.id);\n const userId = typeof e?.userId === \"string\" ? e.userId : \"\";\n if (!Number.isInteger(id) || id <= 0 || !userId) continue;\n const status: StoredTaskStatus =\n e.status === \"running\" || e.status === \"waiting\" ? e.status : \"interrupted\";\n const t: StoredTask = {\n id,\n userId,\n agent: typeof e.agent === \"string\" ? e.agent : \"\",\n key: typeof e.key === \"string\" ? e.key : `task-${id}@${userId}`,\n status,\n turns: Number(e.turns) || 0,\n startedAt: Number(e.startedAt) || 0,\n };\n if (typeof e.lastOutput === \"string\") t.lastOutput = e.lastOutput;\n if (typeof e.cwd === \"string\" && e.cwd) t.cwd = e.cwd;\n tasks.push(t);\n }\n }\n const nextId: Record<string, number> = {};\n const rawNext = (j.nextId ?? {}) as Record<string, unknown>;\n if (rawNext && typeof rawNext === \"object\" && !Array.isArray(rawNext)) {\n for (const [k, v] of Object.entries(rawNext)) {\n const n = Number(v);\n if (Number.isInteger(n) && n > 0) nextId[k] = n;\n }\n }\n // 表里的编号也要算进 nextId:手改过/半截文件时不会发号撞车\n for (const t of tasks) nextId[t.userId] = Math.max(nextId[t.userId] ?? 1, t.id + 1);\n return { version: TASK_STORE_VERSION, tasks, nextId };\n}\n\n/** 原子写(临时文件 + rename):daemon 可能随时被杀,别留半截表。 */\nexport function saveTaskStore(store: TaskStore, dir: string = DATA_DIR): void {\n const file = taskStorePath(dir);\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const tmp = `${file}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(store, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, file);\n } catch {\n /* best-effort:任务表写失败不该影响任务本身 */\n }\n}\n\n/** 重启后:未完成的任务一律标「已中断」(返回被标的那些,调用方据此记日志/展示)。 */\nexport function markInterrupted(store: TaskStore): StoredTask[] {\n const hit: StoredTask[] = [];\n for (const t of store.tasks) {\n if (t.status === \"running\" || t.status === \"waiting\") {\n t.status = \"interrupted\";\n hit.push(t);\n }\n }\n return hit;\n}\n\n/** 发号:按用户单调递增,不复用(返回新编号并推进 nextId)。 */\nexport function takeNextId(store: TaskStore, userId: string): number {\n const n = store.nextId[userId] ?? 1;\n store.nextId[userId] = n + 1;\n return n;\n}\n\nexport function upsertTask(store: TaskStore, task: StoredTask): void {\n const i = store.tasks.findIndex((t) => t.id === task.id);\n if (i >= 0) store.tasks[i] = task;\n else store.tasks.push(task);\n}\n\nexport function removeTask(store: TaskStore, id: number): boolean {\n const i = store.tasks.findIndex((t) => t.id === id);\n if (i < 0) return false;\n store.tasks.splice(i, 1);\n return true;\n}\n","import { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\nimport { getAgent } from \"./agent-slot.js\";\nimport { agentConfigFor, loadBrainConfig } from \"./brain-config.js\";\nimport { resolvePresetByName, type ResolvedAgent } from \"./brain-agents.js\";\nimport { acquire, drop, release, killAll, poolSummary } from \"./worker-pool.js\";\nimport type { WorkerConn, WorkerProgressEvent } from \"./worker-acp.js\";\nimport {\n emptyTaskStore,\n loadTaskStore,\n markInterrupted,\n removeTask as removeStoredTask,\n saveTaskStore,\n takeNextId,\n type StoredTask,\n type TaskStore,\n type StoredTaskStatus,\n} from \"./task-store.js\";\n\n/**\n * 任务表:**一条任务一条 worker 连接**,多条任务并行;编号按用户单调递增、永不复用(避免「任务2」在对话里指代变化)。\n *\n * 与改造前的差别(单任务单进程 → 任务表):\n * - 派发时决定 agent(默认任务大脑,或 `任务 <agent 名>:正文` 指定);每条任务一个 session key `task-<编号>@<userId>`;\n * - 任务结束(`结束任务`)把连接**还回进程池**(同 agent 的下个任务直接复用),而不是杀掉;\n * - 任务表写入磁盘(tasks.json):daemon 重启后未完成的标「已中断」并在「任务列表」可见,回「继续」/`#n 继续` 才拉进程续跑;\n * - 一轮失败在**模型/账号侧**(余额、密钥、计费、模型不存在,见 `providerIssue()`)也直接转「已中断」:这类错误重试不会好,而停在「等你回复」会一直抢用户的无编号消息;\n * - 编号跨重启不归零(nextId 存在表里,只增不减)。\n *\n * 依赖注入(push/typing/stateSink)保持不变:task-manager 不直接依赖发送层与 account,保持可测。\n */\n\nconst log = (...a: unknown[]) => console.error(\"[wemind/wechat-daemon/task]\", ...a);\n\nexport type TaskStatus = StoredTaskStatus;\n\nexport type Task = {\n id: number;\n userId: string;\n /** agent 名(写入磁盘这个;恢复时按名字重新解析,可能已经换了二进制或不可用)。 */\n agent: string;\n /** 会话键:`task-<编号>@<userId>`(worker-sessions.json 里 `<agent>:<key>`)。 */\n key: string;\n status: TaskStatus;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n cwd?: string;\n /** 本次运行解析出的 agent(interrupted 任务在恢复前没有)。 */\n ag?: ResolvedAgent;\n conn?: WorkerConn;\n /** 本地命令(`wemind task`)等着的答复编号:起进程有结果了才回,见 `setTaskAckSink`。 */\n ackId?: string;\n /** 答复里要不要带编号(带 `--agent`/`--cwd` 或已有别的任务时带)。 */\n numbered?: boolean;\n /** 下一轮是「从中断处续跑」:会话恢复失败要诚实报错,不能静默换空会话。 */\n strictResume?: boolean;\n pendingInjects?: string[];\n injectResolver?: (text: string | null) => void;\n};\n\n/** 依赖注入:把「推送文本给用户」交给 index 提供(内部走 sendOrdered + 最新 token)。 */\ntype PushFn = (to: string, text: string) => void;\nlet push: PushFn = () => {};\nexport function setTaskPush(fn: PushFn): void {\n push = fn;\n}\n\n/** 依赖注入:发「正在输入中」(typing 不占连续发送上限),让用户知道任务在干活。 */\nlet typing: (to: string) => void = () => {};\nexport function setTaskTyping(fn: (to: string) => void): void {\n typing = fn;\n}\n\n/**\n * 依赖注入:答复出口(CLI 写 `task-out/<id>.json`)。\n *\n * 只在「本地命令问结果」时用到:`wemind task` 这类命令丢完命令就只能猜,\n * 猜出来的「已派发」在达上限被拒时会变成假消息。派发的答复**必须等起进程有结果**再回——\n * 达上限/agent 起不来都是 acquire 阶段才知道的(dispatchTask 当场返回时还不知道)。\n */\nlet ack: (id: string, text: string) => void = () => {};\nexport function setTaskAckSink(fn: (id: string, text: string) => void): void {\n ack = fn;\n}\n/**\n * 发一条答复(没有 id / 没有出口就当没这回事)。\n *\n * **空文案也照发**:它是「命令已送达、没有额外要说的」这个信号——CLI 收到它就能立刻用自己那句话,\n * 而不是干等到超时才敢打印(超时是留给「老版 daemon 不认这条通道」的)。\n */\nfunction ackIf(id: string | undefined, text: string): void {\n if (!id) return;\n try {\n ack(id, text);\n } catch {\n /* ignore */\n }\n}\n\n/** 任务状态记录(写入磁盘 task-state.json 供本地 `task-status` 读;顶层字段复制「最近一条」,旧版 CLI 仍能显示)。 */\nexport type TaskStateSnapshot = {\n active: boolean;\n status: TaskStatus | \"idle\";\n to?: string;\n startedAt?: number;\n updatedAt: number;\n turns: number;\n lastOutput?: string;\n tasks: Array<{\n id: number;\n userId: string;\n agent: string;\n status: TaskStatus;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n }>;\n};\nlet stateSink: (s: TaskStateSnapshot) => void = () => {};\nexport function setTaskStateSink(fn: (s: TaskStateSnapshot) => void): void {\n stateSink = fn;\n}\n\nconst tasks = new Map<number, Task>();\nlet store: TaskStore = emptyTaskStore();\nlet storeDir = DATA_DIR;\n\n/** 写入磁盘时机:派发/每轮结束/waiting/停止(表很小,写多了也没关系;丢了才会真的影响续跑)。 */\nfunction persist(): void {\n store.tasks = [...tasks.values()].map((t) => {\n const s: StoredTask = {\n id: t.id,\n userId: t.userId,\n agent: t.agent,\n key: t.key,\n status: t.status,\n turns: t.turns,\n startedAt: t.startedAt,\n };\n if (t.lastOutput !== undefined) s.lastOutput = t.lastOutput;\n if (t.cwd) s.cwd = t.cwd;\n return s;\n });\n saveTaskStore(store, storeDir);\n}\n\nfunction emitState(): void {\n const live = [...tasks.values()].filter((t) => t.status !== \"interrupted\");\n const primary = live.sort((a, b) => b.startedAt - a.startedAt)[0];\n const snap: TaskStateSnapshot = {\n active: !!primary,\n status: primary?.status ?? \"idle\",\n updatedAt: Date.now(),\n turns: primary?.turns ?? 0,\n tasks: [...tasks.values()]\n .sort((a, b) => a.id - b.id)\n .map((t) => {\n const e: TaskStateSnapshot[\"tasks\"][number] = {\n id: t.id,\n userId: t.userId,\n agent: t.agent,\n status: t.status,\n turns: t.turns,\n startedAt: t.startedAt,\n };\n if (t.lastOutput !== undefined) e.lastOutput = t.lastOutput;\n return e;\n }),\n };\n if (primary) {\n snap.to = primary.userId;\n snap.startedAt = primary.startedAt;\n if (primary.lastOutput !== undefined) snap.lastOutput = primary.lastOutput;\n }\n try {\n stateSink(snap);\n } catch {\n /* best-effort */\n }\n}\n\nconst PROGRESS = (process.env.WEMIND_WORKER_PROGRESS ?? \"0\") === \"1\"; // 默认安静:只在轮次结束/中断推送\n\n/** 启动时读回任务表:未完成的标「已中断」(**不自动恢复**),编号接着排。 */\nexport function initTasks(dir: string = DATA_DIR): { interrupted: number[] } {\n storeDir = dir;\n store = loadTaskStore(dir);\n const hit = markInterrupted(store);\n saveTaskStore(store, dir);\n tasks.clear();\n for (const t of store.tasks) tasks.set(t.id, { ...t });\n if (hit.length)\n log(\n `重启:${hit.length} 条未完成任务标记为「已中断」(编号 ${hit.map((t) => t.id).join(\"、\")};回「继续」可续跑)`,\n );\n emitState();\n return { interrupted: hit.map((t) => t.id) };\n}\n\n// ---- 查询 ----\nconst isLive = (t: Task): boolean => t.status === \"running\" || t.status === \"waiting\";\nexport const liveTasks = (userId: string): Task[] =>\n [...tasks.values()].filter((t) => t.userId === userId && isLive(t)).sort((a, b) => a.id - b.id);\nexport const awaitingTasks = (userId: string): Task[] =>\n liveTasks(userId).filter((t) => t.status === \"waiting\");\nexport const interruptedTasks = (userId: string): Task[] =>\n [...tasks.values()]\n .filter((t) => t.userId === userId && t.status === \"interrupted\")\n .sort((a, b) => a.id - b.id);\nexport const listTasks = (userId: string): Task[] =>\n [...tasks.values()].filter((t) => t.userId === userId).sort((a, b) => a.id - b.id);\n\n/**\n * 像不像「继续跑」这句话。\n *\n * 只在一处用到:**「已中断」的任务**(daemon 重启留下的,或一轮失败在 provider 侧——见 `providerIssue()`)在没有活跃任务时要把无前缀消息放行进任务表(`injectTask` → `resumeTask`)。\n * 关键字这层闸不能省:中断任务会一直躺在表里,若见了任何消息都放行,一句「帮我查一下 X」会把陈旧的它唤醒。\n * 单条任务时 `#n 继续` 也走这条路(显式编号那条分支在解析层就分流了)。\n */\nexport function looksLikeResume(text: string | undefined): boolean {\n return /^(?:继续|接着|接着跑|继续跑|续跑|继续吧|continue|go\\s*on)[。.!!]?$/i.test(\n (text ?? \"\").trim(),\n );\n}\n/** 是否存在活跃任务(可选限定某用户)。 */\nexport function hasActiveTask(to?: string): boolean {\n return to === undefined ? [...tasks.values()].some(isLive) : liveTasks(to).length > 0;\n}\n/** 池状态摘要(`AI`/`wemind list` 展示用)。 */\nexport const taskPoolSummary = poolSummary;\n\nfunction since(ms: number): string {\n const s = Math.max(0, Math.round(ms / 1000));\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n if (m < 60) return `${m}m`;\n return `${Math.floor(m / 60)}h${m % 60}m`;\n}\n\nconst STATUS_LABEL: Record<TaskStatus, string> = {\n running: \"运行中\",\n waiting: \"等你回复\",\n interrupted: \"已中断\",\n};\n\n/** 任务标签:多条任务时带编号(单任务免编号,延续改造前的顺手)。 */\nfunction labelOf(task: Task): string {\n return listTasks(task.userId).length > 1 || liveTasks(task.userId).length > 1\n ? `任务${task.id}`\n : \"任务\";\n}\n\n/** `任务列表`:编号 / agent / 状态 / 轮次 / 已运行 / 最近输出摘要。 */\nexport function renderTaskList(userId: string): string {\n const all = listTasks(userId);\n if (!all.length)\n return \"📋 当前没有任务。派发:直接发「任务:正文」或「任务 <agent 名>:正文」。\";\n const lines = [`📋 任务(${all.length} 条)`];\n for (const t of all) {\n const age = t.startedAt ? since(Date.now() - t.startedAt) : \"?\";\n lines.push(\n `#${t.id} [${t.agent}] ${STATUS_LABEL[t.status]} · ${t.turns} 轮 · ${age}${t.cwd ? ` · cwd=${t.cwd}` : \"\"}`,\n );\n if (t.lastOutput) lines.push(` 最近输出:${t.lastOutput.replace(/\\n/g, \" \").slice(0, 120)}`);\n }\n const live = all.filter(isLive);\n if (live.length > 1)\n lines.push(`多条任务并行:回话用「#<编号> 正文」,结束用「结束任务 <编号>」。`);\n const intr = all.filter((t) => t.status === \"interrupted\");\n if (intr.length)\n lines.push(\n `已中断的任务回「继续」(或「#${intr[0].id} 继续」)可续跑——注意上一步可能已经改过文件,核对后再继续。`,\n );\n return lines.join(\"\\n\");\n}\n\n// ---- 派发与续跑 ----\n/**\n * 答复文案:单任务(且没指定 agent、没指定 cwd)沿用改造前那句话——最常见路径不该变得啰嗦;\n * 其余情况(指定了 agent / 已经有多条任务 / 本地派发)带上编号,因为这时编号才有用。\n */\nfunction ackFor(task: Task, numbered: boolean): string {\n if (!numbered)\n return \"✅ 已派给工作机执行(任务运行中)。需要你决策或中断时我会主动找你;随时可回复「结束任务」终止。\";\n return `✅ 已派发任务 #${task.id}(${task.agent} 执行)。看进展发「任务列表」,回话发「#${task.id} 正文」,收掉发「结束任务 ${task.id}」。`;\n}\n\n/** 解析「本任务用哪个 agent」:显式 agent 名优先,否则默认任务大脑。 */\nfunction resolveTaskAgent(\n agentKey?: string,\n): { ok: true; ag: ResolvedAgent } | { ok: false; problem: string } {\n if (!agentKey) return { ok: true, ag: getAgent(\"worker\") };\n const cfg = loadBrainConfig(storeDir);\n const r = resolvePresetByName(agentKey, agentConfigFor(cfg, \"worker\") ?? cfg);\n return r.ok ? { ok: true, ag: r.agent } : { ok: false, problem: r.problem };\n}\n\n/**\n * 派发新任务(后台异步执行,立即返回答复文案,不阻塞调用方)。\n * `agentKey`:`任务 <agent 名>:正文` 指定的 agent;`cwd`:本地派发指定的任务工作目录。\n * `ackId` 给了就等起进程有结果再答复(成功=真答复文案,达上限/起不来=那句警告)。\n */\nexport function dispatchTask(\n to: string,\n instruction: string,\n opts: { cwd?: string; agentKey?: string; ackId?: string } = {},\n): string {\n const text = (instruction ?? \"\").trim();\n if (!text) {\n const r = \"要派发的任务正文是空的。\";\n ackIf(opts.ackId, r);\n return r;\n }\n const got = resolveTaskAgent(opts.agentKey);\n if (!got.ok) {\n const r = `❌ 不能用「${opts.agentKey}」跑任务:${got.problem}`;\n ackIf(opts.ackId, r);\n return r;\n }\n const numbered = !!opts.agentKey || !!opts.cwd || listTasks(to).length > 0;\n const id = takeNextId(store, to);\n const task: Task = {\n id,\n userId: to,\n agent: got.ag.name,\n key: taskKeyOf(id, to),\n status: \"running\",\n turns: 0,\n startedAt: 0, // 真正起进程时再定(起进程可能失败)\n ag: got.ag,\n numbered,\n };\n if (opts.cwd) task.cwd = opts.cwd;\n if (opts.ackId) task.ackId = opts.ackId;\n tasks.set(id, task);\n persist();\n emitState();\n void runTask(task, text);\n return ackFor(task, numbered);\n}\n\n/** 会话键:带用户,避免多用户的编号相撞。 */\nexport function taskKeyOf(id: number, userId: string): string {\n return `task-${id}@${userId}`;\n}\n\nfunction waitForInject(task: Task): Promise<string | null> {\n if (task.pendingInjects?.length) {\n const t = task.pendingInjects.join(\"\\n\"); // 多条暂存按序合并喂下一轮,一条不丢\n task.pendingInjects = undefined;\n return Promise.resolve(t);\n }\n return new Promise((resolve) => {\n task.injectResolver = resolve;\n });\n}\n\n/** 任务循环:每轮 ask → 推送本轮输出 → 等用户回话 → 续跑。绝不在 workerLoop 里 await 它。 */\nasync function runTask(task: Task, firstInstruction: string): Promise<void> {\n const ag = task.ag;\n if (!ag) {\n log(`task ${task.id} 没有 agent(内部错误)`);\n return;\n }\n task.status = \"running\";\n emitState();\n const got = await acquire(ag, task.id);\n if (!got.ok) {\n const msg = `⚠️ 任务 #${task.id} 没起得来:${got.problem}`;\n // 续跑(`strictResume`)起不来 ≠ 新派发起不来:新任务从没跑过,删掉是对;续跑的记录必须留着——\n // 用户是照着「修好后回「继续」」的提示来的,删掉记录等于把编号和会话键一起丢了,还得重派一条。\n if (task.strictResume === true) {\n task.strictResume = false;\n task.status = \"interrupted\";\n task.lastOutput = msg;\n persist();\n emitState();\n push(task.userId, `${msg}(任务记录还在,处理好后再回「继续」)`);\n ackIf(task.ackId, msg);\n return;\n }\n tasks.delete(task.id);\n persist();\n emitState();\n push(task.userId, msg);\n ackIf(task.ackId, msg); // 本地命令要的是「到底派上了没有」——这里才是答案\n return;\n }\n const conn = got.conn;\n task.conn = conn;\n conn.setCwd(task.cwd);\n if (!task.startedAt) task.startedAt = Date.now();\n persist();\n emitState();\n ackIf(task.ackId, ackFor(task, task.numbered === true)); // 起进程成功:这时才敢说「已派发」\n task.ackId = undefined;\n log(`task ${task.id} start agent=${ag.name} conn=${conn.id} cwd=${conn.effectiveCwd()}`);\n\n let instruction: string | null = firstInstruction;\n while (tasks.get(task.id) === task && instruction !== null) {\n typing(task.userId); // 立即一次「正在输入」\n const ti = setInterval(() => typing(task.userId), 5000); // 每 5s 续命(小于客户端 typing 自动过期,防闪烁;不占连发上限)\n let wasResume = false;\n try {\n const onProgress = PROGRESS\n ? (ev: WorkerProgressEvent) => {\n if (ev.type === \"tool\") push(task.userId, `🔧 ${labelOf(task)} 正在调用工具…`);\n }\n : undefined;\n wasResume = task.strictResume === true;\n task.strictResume = false;\n const out = await conn.ask(instruction, task.key, onProgress, { strictResume: wasResume });\n if (tasks.get(task.id) !== task) break; // 期间被 stop\n const label = labelOf(task);\n const body = out || \"(本轮无输出,可回复「继续」推进或「结束任务」终止)\";\n task.turns += 1;\n task.lastOutput = `🤖【${label}】${body}`;\n push(task.userId, task.lastOutput);\n } catch (e) {\n if (tasks.get(task.id) !== task) break; // 被 stop 导致的 reject,不算中断\n const why = String(e instanceof Error ? e.message : e);\n if (wasResume && why.includes(\"接不上\")) {\n // 续跑失败(原会话接不上):回到「已中断」并把话说清楚,而不是静默换一个空会话假装续上了。\n // 当前会话记录**保留**(收尾处按「任务是否还在表里」决定要不要清);但别再劝「重述一遍」——重述也是走 strictResume,接不上就是接不上,只能力尽于此。\n task.status = \"interrupted\";\n task.lastOutput = `⚠️ #${task.id} 号任务这次接不上:${why}(这条任务的记录还在;要重来就「结束任务 ${task.id}」后重新派一条。)`;\n persist();\n emitState();\n log(`task ${task.id} resume failed(接不上,已转「已中断」):`, why);\n push(task.userId, task.lastOutput);\n break;\n }\n const label = labelOf(task);\n const resumeHint = label === \"任务\" ? \"回复「继续」\" : `回复「#${task.id} 继续」`;\n const stopHint = `或「结束任务${label === \"任务\" ? \"\" : ` ${task.id}`}」终止。`;\n log(`task ${task.id} turn failed:`, why);\n const prov = providerIssue(e);\n if (prov) {\n // 模型/账号侧的问题(余额、密钥、计费、模型不存在):重试也不会好,而且这类任务若停在「等你回复」,用户下一条无编号消息会被它吞掉、只是又白失败一轮(真机踩过:一条余额不足的任务把「在吗」吃掉了)。转「已中断」——不在 liveTasks 里,就不再抢消息;用户修好账号后回「继续」/`#n 继续` 才续跑。\n task.status = \"interrupted\";\n task.lastOutput = `⚠️【${label}】中断了(${prov})。这类错误重试也不会好——修好后${resumeHint}我从中断处恢复,${stopHint}`;\n persist();\n emitState();\n log(`task ${task.id} provider issue(${prov})→ 已转「已中断」,不再接管无编号消息`);\n push(task.userId, task.lastOutput);\n break;\n }\n task.lastOutput = `⚠️【${label}】中断了(${reason(e)})。${resumeHint}我从中断处恢复,${stopHint}`;\n push(task.userId, task.lastOutput);\n } finally {\n clearInterval(ti); // 本轮结束停 typing\n }\n if (tasks.get(task.id) !== task) break;\n task.status = \"waiting\";\n persist();\n emitState(); // → waiting(task-list 可见「等你回复」)\n instruction = await waitForInject(task); // null = stop\n if (tasks.get(task.id) === task) {\n task.status = \"running\";\n persist();\n emitState();\n }\n }\n // 收尾:连接还给进程池(活着才有复用价值;被 stop 时可能已经死了,release 会就地清掉空壳)。\n // 会话当前会话记录只在「任务真的结束」(已被 stop、不在表里)时清掉:回到「已中断」的任务要留着它,用户重述进展后还能再试。\n if (task.conn === conn) {\n if (tasks.get(task.id) !== task) conn.dropSession(task.key);\n release(conn);\n task.conn = undefined;\n }\n emitState();\n log(`task ${task.id} loop ended`);\n}\n\nfunction reason(e: unknown): string {\n const s = String(e instanceof Error ? e.message : e);\n if (/no-first-frame|idle|cap|timeout/i.test(s)) return \"超时/卡住\";\n return \"执行出错\";\n}\n\n/**\n * 像不像「等一会儿重试就会好」的限流/过载。**先判它**,像就一律不归类为账号问题。\n *\n * 为什么必须先判:Google 的**按分钟**限流原文是「Quota exceeded for quota metric '…requests per minute'…」(HTTP 429),只按关键字会把限流误判成额度问题、把任务提前转「已中断」——而文档口径是「限流不算」。这里宁可漏判一个限流(退回老行为:停在等你回复),也不让限流误触发。\n * 判的是**文本里有没有限流语义**,**故意不看裸的状态码 `429`**:各家把 HTTP 状态塞进错文本的形态不一,而 OpenAI 的余额耗尽(「You exceeded your current quota, please check your plan and billing details」)本身就是 HTTP 429——按状态码判会把真正的账号问题放过。`overloaded` 是 Anthropic 的 529(过载,可重试)。\n * `-32603` 本身**不**在这里判——只有它包着的 provider 文案算(DSH 的余额不足就是这样包出来的),所以本函数只看文本、不看 code。\n */\nconst RATE_LIMIT_LIKE =\n /rate limit|rate_limit|ratelimit|too many requests|requests per minute|per minute|resource_exhausted|overloaded/i;\n\n/**\n * 这一轮失败是不是「模型/账号侧」的问题(额度、余额、密钥、鉴权、计费、模型不存在)——这类错误重试也不会好,得等用户把账号修好。\n *\n * 为什么单独认这一类:**停在「等你回复」的任务会抢用户下一条无编号消息**(「谁在等就喂谁」)。一条余额不足的任务因此会不断吞掉日常消息、每次都白失败一轮(真机踩过:它把「在吗」吃掉了)。认出来就转「已中断」,用户修好账号后回「继续」/`#n 继续` 再续跑。\n *\n * 用关键字判(错误文本来自各 agent 的 provider,措辞不统一),**认不出的一律按老规矩停在「等你回复」**——宁可多抢一次,也不要把偶发失败误判成账号问题。限流(429/rate limit)不算(见上面的 `RATE_LIMIT_LIKE`):等一会儿重试就会好,仍走老路。\n */\nexport function providerIssue(e: unknown): string | null {\n const s = String(e instanceof Error ? e.message : e);\n if (RATE_LIMIT_LIKE.test(s)) return null;\n if (\n /insufficient balance|balance is insufficient|insufficient[ _]funds|insufficient_quota|insufficient credits|credit balance|requires more credits|quota exceeded|exceeded your current quota|out of credits|余额不足|额度不足|欠费|请充值/i.test(\n s,\n )\n )\n return \"额度/余额不足\";\n if (\n /no api key|invalid api key|incorrect api key|api key not valid|invalid x-api-key|authentication required|unauthorized|not authenticated/i.test(\n s,\n )\n )\n return \"密钥/鉴权有问题\";\n if (/payment required|\"code\"\\s*:\\s*402|billing/i.test(s)) return \"计费问题\";\n // 「模型不存在」各家的写法都不同:OpenAI 的 model_not_found、Anthropic 的 no such model、\n // DeepSeek 的 Model Not Exist、Gemini 的 `models/<名> is not found`、还有「The model '…' does not exist」。\n // 中间那段用 [^\\n]{0,80} 兜住带引号/带点的模型名(gpt-3.5-turbo 里有点,不能用 [^.])。\n if (\n /model_not_found|no such model|unknown model|model[ _]?not[ _]?(?:found|exist)|model\\S*\\b[^\\n]{0,80}\\b(?:not found|does not exist)/i.test(\n s,\n )\n )\n return \"模型不可用\";\n return null;\n}\n\n/** 续跑一条「已中断」的任务:按原 agent 拉进程 + session/load 恢复上下文,然后照常跑循环。 */\nfunction resumeTask(task: Task, instruction: string): string {\n const got = resolveTaskAgent(task.agent);\n if (!got.ok) {\n // 返回而不是 push:微信侧一样会看到(injectTask 的返回值由调用方当答复发出去),CLI 侧则能拿到真话,\n // 否则「接不上」只进 daemon 日志、CLI 却报「已发送」。\n return `⚠️ 接不上 #${task.id} 号任务(原 agent「${task.agent}」现在用不了):${got.problem}`;\n }\n task.ag = got.ag;\n task.strictResume = true; // 续跑要求「上下文真的接上」:恢复失败要报错,不能静默换空会话\n void runTask(\n task,\n `(这条任务上一轮没跑完就中断了,现在续跑;上下文按会话恢复。动手前先核对上一步是否已经产生副作用,避免重复。)\\n${instruction}`,\n );\n return `⏳ 正在按 #${task.id} 号任务续跑(${got.ag.name})。上一步可能已经改过文件或发过请求,核对时注意别重复动作。`;\n}\n\n/**\n * 把用户消息送给任务。`id` 给定时只作用于那条任务;不给时:唯一一条 → 它;多条 → 回编号提示(不猜)。\n * 返回要额外回复的文案(空字符串=只推给了任务,不需要额外答复)。\n * `ackId`(本地命令):非空返回文案也回一份给 CLI——「没有 #9 号任务」这种话不能只进 daemon 日志。\n */\nexport function injectTask(to: string, text: string, id?: number, ackId?: string): string {\n const done = (r: string): string => {\n ackIf(ackId, r);\n return r;\n };\n const live = liveTasks(to);\n const intr = interruptedTasks(to);\n const body = (text ?? \"\").trim();\n if (!body) return done(\"要回给任务的内容是空的。\");\n\n let target: Task | undefined;\n if (id !== undefined) {\n target = tasks.get(id);\n if (!target || target.userId !== to)\n return done(`没有 #${id} 号任务。发「任务列表」看现有编号。`);\n } else {\n const awaiting = live.filter((t) => t.status === \"waiting\");\n // 「谁在等就喂谁」优先于「只有一条就喂它」:一条 running + 一条 waiting 时,无前缀消息显然是回给在等的那条\n if (awaiting.length === 1) target = awaiting[0];\n else if (live.length === 1) target = live[0];\n else if (live.length === 0) {\n if (intr.length === 1) return done(resumeTask(intr[0], body));\n if (intr.length > 1)\n return done(\n `有 ${intr.length} 条已中断的任务,请指明编号续跑:${intr.map((t) => `#${t.id}`).join(\"、\")}(写法:\\`#${intr[0].id} 继续\\`)。`,\n );\n return done(\"\");\n } else {\n const rows = live.map((t) => `#${t.id} [${t.agent}] ${STATUS_LABEL[t.status]}`).join(\" \");\n return done(`有 ${live.length} 条任务在跑,请指明要回给哪条:「#<编号> 正文」。\\n${rows}`);\n }\n }\n\n if (target.status === \"interrupted\") return done(resumeTask(target, body));\n if (target.status === \"waiting\" && target.injectResolver) {\n const r = target.injectResolver;\n target.injectResolver = undefined;\n r(body);\n return done(\"\");\n }\n (target.pendingInjects ??= []).push(body); // running 期间:按序累积,等这一步跑完一并处理(不覆盖前一条)\n push(to, `📝 已记下给 #${target.id} 的补充,等当前这步跑完就处理。`);\n return done(\"\");\n}\n\n/** 结束任务:停 worker、清状态、唤醒退出循环。`id` 缺省=唯一那条;`\"all\"`=全部。 */\nexport function stopTask(to: string, id?: number | \"all\", ackId?: string): string {\n const done = (r: string): string => {\n ackIf(ackId, r);\n return r;\n };\n const live = liveTasks(to);\n const intr = interruptedTasks(to);\n if (id === undefined && live.length === 0) {\n // daemon 重启后内存无任务,但 task-state.json 可能残留 active/waiting(幽灵任务)→ 仍要写入磁盘 idle\n persist();\n emitState();\n const hint = intr.length\n ? `有 ${intr.length} 条已中断的任务记录(回「继续」可续跑,或「结束任务 <编号>」清掉记录)。`\n : \"\";\n return done(`当前没有正在运行的任务。${hint}`);\n }\n if (id === undefined && live.length > 1) {\n const rows = live.map((t) => `#${t.id} [${t.agent}] ${STATUS_LABEL[t.status]}`).join(\" \");\n return done(\n `有 ${live.length} 条任务在跑,请指明编号(「结束任务 <编号>」),或「结束全部任务」。\\n${rows}`,\n );\n }\n\n const targets =\n id === \"all\" ? live : id === undefined ? live.slice(0, 1) : live.filter((t) => t.id === id);\n if (!targets.length) {\n if (id === undefined) return done(\"当前没有正在运行的任务。\");\n if (id === \"all\") return done(\"当前没有正在运行的任务。\");\n const t = tasks.get(id);\n if (t && t.userId === to && t.status === \"interrupted\") {\n tasks.delete(id);\n removeStoredTask(store, id);\n persist();\n emitState();\n return done(`已清掉 #${id} 号已中断任务的记录。`);\n }\n return done(`没有 #${id} 号在跑的任务。发「任务列表」看现有编号。`);\n }\n\n for (const t of targets) {\n const wasWaiting = t.status === \"waiting\";\n tasks.delete(t.id); // 先删:循环里的 `tasks.get(id) !== task` 据此感知被停\n if (t.injectResolver) {\n const r = t.injectResolver;\n t.injectResolver = undefined;\n r(null);\n }\n const conn = t.conn;\n if (conn) {\n conn.dropSession(t.key);\n if (wasWaiting)\n release(conn); // 等回复的任务进程是干净的 → 留着复用\n else drop(conn); // 正在跑的那条:回合没法「优雅取消」,只能杀掉(正在做的动作就此作罢)\n t.conn = undefined;\n }\n }\n persist();\n emitState();\n if (id === \"all\") return done(`🛑 已结束全部任务(${targets.length} 条)。`);\n const one = targets[0];\n return done(targets.length === 1 ? `🛑 任务 #${one.id} 已结束。` : \"🛑 任务已结束。\");\n}\n\n/** daemon 退出 / `wemind stop`:停掉一切(任务与池)。 */\nexport function shutdownTasks(): void {\n for (const t of tasks.values()) {\n t.injectResolver?.(null);\n t.injectResolver = undefined;\n }\n killAll();\n persist(); // 表留着(下次启动会标「已中断」),进程全清\n emitState();\n}\n\n/** 测试用:清空内存任务表与写入磁盘路径(不写入磁盘、不杀进程由调用方决定)。 */\nexport function resetTasksForTest(dir?: string): void {\n tasks.clear();\n store = emptyTaskStore();\n if (dir) storeDir = dir;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * 任务收件箱的文件协议(CLI ↔ daemon):**收命令通道**(`task-inbox/`)与**回结果通道**(`task-out/`)。\n *\n * 为什么要有回结果通道:`wemind task` / `task-reply` / `task-stop` 原先只是把命令丢进收件箱就返回,\n * 于是它们只能靠轮询 `task-state.json` 猜结果——「达上限被拒绝派发」会被报成「已派发」,\n * 甚至报成**上一条**任务的编号(`active` 本来就是 true),「回复不存在的编号」也会被报成「已发送」。\n * 回结果通道让 daemon 把**真实结果**写回来,CLI 打印的就是 daemon 的那句话。\n *\n * 兼容:老 daemon 不认这条通道(不会写答复),CLI 等不到就退回原来的「已发送」措辞并说明一句,\n * 而不是把「没等到」当成「成功了」。\n */\n\nexport const TASK_INBOX_DIRNAME = \"task-inbox\";\nexport const TASK_OUT_DIRNAME = \"task-out\";\n\nexport type TaskAck = { id: string; at: number; ok: boolean; text: string };\n\nexport const taskInboxDir = (base: string): string => path.join(base, TASK_INBOX_DIRNAME);\nexport const taskOutDir = (base: string): string => path.join(base, TASK_OUT_DIRNAME);\n\n/** 原子写(临时文件 + rename):daemon 扫目录时不会读到半截 JSON。 */\nfunction atomicWriteJson(fp: string, obj: unknown): void {\n fs.mkdirSync(path.dirname(fp), { recursive: true });\n const tmp = `${fp}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(obj, null, 2), \"utf-8\");\n fs.renameSync(tmp, fp);\n}\n\n/**\n * 写一条命令进收件箱,返回它的答复编号(同时写进 `ack` 字段;老 daemon 忽略这个字段)。\n * 文件名用同一个 id:带时间戳前缀,daemon 按名字排序即按时间排序。\n */\nexport function writeTaskCmd(base: string, obj: Record<string, unknown>): string {\n // id 与 ts 取同一次 Date.now():分两次取时一旦跨毫秒边界,id 的时间戳前缀就与 ts 对不上(「按名排序即按时间排序」依赖它)。\n const ts = Date.now();\n const id = `${ts}-${Math.random().toString(36).slice(2, 8)}`;\n atomicWriteJson(path.join(taskInboxDir(base), `${id}.json`), { ...obj, ack: id, ts });\n return id;\n}\n\n/** daemon 侧:把一条命令的结果写回去(CLI 正在短轮询它)。写失败不影响命令本身。 */\nexport function writeTaskAck(base: string, id: string | undefined, text: string): void {\n if (!id) return;\n try {\n atomicWriteJson(path.join(taskOutDir(base), `${id}.json`), {\n id,\n at: Date.now(),\n ok: !text.startsWith(\"❌\"),\n text,\n });\n } catch {\n /* 答复写不进去不该影响命令本身(CLI 会回退到「已发送」措辞) */\n }\n}\n\n/** CLI 侧:等这次命令的答复(读到即删,目录不留垃圾)。超时返回 null。 */\nexport async function awaitTaskAck(\n base: string,\n id: string,\n timeoutMs = 8000,\n pollMs = 150,\n): Promise<TaskAck | null> {\n const fp = path.join(taskOutDir(base), `${id}.json`);\n const t0 = Date.now();\n for (;;) {\n try {\n const a = JSON.parse(fs.readFileSync(fp, \"utf-8\")) as TaskAck;\n fs.unlinkSync(fp);\n if (a?.id === id && typeof a.text === \"string\") return a;\n } catch {\n /* 还没到 */\n }\n if (Date.now() - t0 >= timeoutMs) return null;\n await new Promise((r) => setTimeout(r, pollMs));\n }\n}\n\n/** 启动 / 收尾清理:作废上一条命令的残留(防重放),两个通道一起清。 */\nexport function clearTaskDirs(base: string): void {\n for (const d of [taskInboxDir(base), taskOutDir(base)]) {\n try {\n fs.rmSync(d, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n }\n try {\n fs.mkdirSync(taskInboxDir(base), { recursive: true });\n } catch {\n /* ignore */\n }\n}\n","#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n getUpdates,\n sendMessage,\n sendTyping,\n getConfig,\n notifyStart,\n generateId,\n SESSION_EXPIRED_ERRCODE,\n isSessionPaused,\n pauseSession,\n getRemainingPauseMs,\n loadAccount,\n loadState,\n saveState,\n DATA_DIR,\n MessageItemType,\n MessageState,\n MessageType,\n downloadImage,\n sendImage,\n sendFile,\n ILinkError,\n classifyTransportError,\n isAllowed,\n addAllowFrom,\n readAllowFrom,\n} from \"@wemind/wechat-ilink-core\";\nimport type {\n AccountData,\n ServerState,\n WeixinMessage,\n SendMessageReq,\n ImageItem,\n} from \"@wemind/wechat-ilink-core\";\nimport {\n askBrain,\n newSession,\n restart,\n resumeSession,\n supportsImages,\n heartbeatMs,\n} from \"./acp-brain.js\";\nimport {\n agentProblem,\n authHint,\n BRAIN_CONFIG_EXIT_CODE,\n presetAvailable,\n presetNames,\n resolvePresetByName,\n} from \"./brain-agents.js\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\nimport { agentConfigFor, loadBrainConfig } from \"./brain-config.js\";\nimport { getAgent, seedSlots, setAgent } from \"./agent-slot.js\";\nimport { preflightSwitch, switchBrain, switchInFlightTarget } from \"./brain-switch.js\";\nimport { writeBrainState } from \"./brain-state.js\";\nimport {\n isOwnerOnly,\n renderHelp,\n parseChatCommand,\n parseTaskCommand,\n type ChatCommand,\n type TaskCommand,\n} from \"./chat-commands.js\";\nimport { ensureBrainHome, addMcp, removeMcp, listMcp } from \"./mcp-sandbox.js\";\nimport {\n deleteSessionCmd,\n newSessionCmd,\n renameSessionCmd,\n resumeSessionCmd,\n sessionsCmd,\n type SessionCtx,\n} from \"./session-manager.js\";\nimport { syncFromPointerMap, currentSessionIdOf } from \"./session-registry.js\";\nimport {\n clearControlDirs,\n controlInboxDir,\n markBridgeReady,\n readControlInbox,\n writeControlResponse,\n type ControlRequest,\n} from \"./control-bridge.js\";\nimport {\n HANDOFF_PROMPT,\n HANDOFF_TTL_MS,\n HANDOFF_VERSION,\n clearHandoff,\n handoffPrefix,\n loadHandoff,\n saveHandoff,\n takeHandoffFor,\n} from \"./handoff.js\";\nimport { extractImages, resolveImageBytes } from \"./image-reply.js\";\nimport { LONG_REPLY_THRESHOLD, buildMdReply } from \"./md-reply.js\";\nimport { PENDING_PUSH_MAX_TRIES, PendingPushes } from \"./pending-push.js\";\nimport {\n dispatchTask,\n injectTask,\n stopTask,\n hasActiveTask,\n awaitingTasks,\n interruptedTasks,\n looksLikeResume,\n renderTaskList,\n initTasks,\n setTaskPush,\n setTaskTyping,\n setTaskStateSink,\n setTaskAckSink,\n taskPoolSummary,\n providerIssue,\n type TaskStateSnapshot,\n} from \"./task-manager.js\";\nimport { clearTaskDirs, taskInboxDir, writeTaskAck } from \"./task-inbox.js\";\nimport {\n killAll as killWorkerPool,\n killIdle as killIdleConns,\n sweep as sweepWorkerPool,\n} from \"./worker-pool.js\";\nimport { ensureWorkerHome } from \"./worker-sandbox.js\";\n\nconst log = (...a: unknown[]) => console.error(\"[wemind/wechat-daemon]\", ...a);\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/** 分类大脑错误:backend=模型后端瞬时错误(可多重试);timeout=超时/卡死;other=其它。 */\nfunction classifyBrainError(e: unknown): \"timeout\" | \"backend\" | \"other\" {\n const s = String(e instanceof Error ? e.message : e);\n if (\n /-32603|internal error|please try again|response stream|overloaded|rate.?limit|\\b(429|503)\\b/i.test(\n s,\n )\n )\n return \"backend\";\n if (/idle timeout|no-first-frame|cap timeout|timeout/i.test(s)) return \"timeout\";\n return \"other\";\n}\n/**\n * 一次大脑失败该怎么处置:provider 侧(余额/额度、密钥鉴权、计费、模型不存在)是**永久**错误——\n * 重试只是又白失败一轮,何况每次重试都要先冷启动一个 agent 进程;这类只试 1 次,直接把原因告诉用户。\n * 其余沿用老规矩:backend(`-32603`/限流等瞬时错误)3 次,timeout/其它 2 次。\n */\nexport function brainFailurePlan(e: unknown): { attemptLimit: number; category: string } {\n const prov = providerIssue(e);\n if (prov) return { attemptLimit: 1, category: `provider:${prov}` };\n const cat = classifyBrainError(e);\n return { attemptLimit: cat === \"backend\" ? 3 : 2, category: cat };\n}\n/** 按错误类型给用户的失败提示。`agentName` 用于给特定 agent 补专属建议(如 Cline 的自定义 provider 要设环境变量)。 */\nexport function brainErrorMessage(e: unknown, agentName?: string): string {\n const prov = providerIssue(e);\n if (prov) {\n // 余额/计费在 provider 侧处理完,重发消息即可(daemon 不用重启);\n // 密钥/模型类常常是刚在本机改过登录态或 brain.json——运行中的 daemon 不重读配置、也不重新同步凭据(槽位启动时种入),\n // 必须重启一次才生效,所以这句提示要说全,否则用户会陷入「改了、重发、还是错」。\n // Cline 是例外:自定义 provider 只认启动环境里的 CLINE_PROVIDER/CLINE_API_KEY,重启不带变量照样失败——\n // 用 agent 专属提示顶掉通用那句(`authHint`)。\n const auth = agentName ? authHint(agentName, String(e instanceof Error ? e.message : e)) : \"\";\n const hint = auth\n ? ` ${auth}`\n : prov === \"密钥/鉴权有问题\" || prov === \"模型不可用\"\n ? \"若刚在本机改过登录态或模型配置,还要重启一次 daemon(wemind stop && wemind start)才会读到。\"\n : \"\";\n return `(这次没答上来:${prov}。这类问题重试不会好——把账号/模型处理好后把消息再发一次就行。${hint})`;\n }\n switch (classifyBrainError(e)) {\n case \"backend\":\n return \"(模型后端临时出错了,自动重试多次仍失败,请稍后把消息再发一次)\";\n case \"timeout\":\n return \"(这次处理超时/卡住被中断了,重试也没成功——可把任务拆小一点再发)\";\n default:\n return \"(处理出错了,请把消息再发一次试试)\";\n }\n}\n\nfunction extractText(msg: WeixinMessage): string {\n return (msg.item_list ?? [])\n .map((it) => {\n if (it.type === MessageItemType.TEXT) return it.text_item?.text ?? \"\";\n if (it.type === MessageItemType.VOICE) return it.voice_item?.text ?? \"\"; // 微信侧语音转文字(省事路)\n return \"\";\n })\n .filter((s) => s)\n .join(\"\\n\")\n .trim();\n}\n\n/** 工具标题中文化(无 purpose 时的兜底)。 */\nfunction zhTool(t: string): string {\n return t\n .replace(/^Searching the web for:?\\s*/i, \"联网搜索:\")\n .replace(/^Running:?\\s*/i, \"执行:\")\n .replace(/^Reading\\s+/i, \"读取 \")\n .replace(/^Writing\\s+/i, \"写入 \")\n .replace(/^Finding\\s+/i, \"查找 \");\n}\n\n/** 按工具类型给图标。 */\nfunction toolIcon(kind?: string): string {\n switch (kind) {\n case \"read\":\n return \"📄\";\n case \"search\":\n return \"🔍\";\n case \"execute\":\n return \"⌨️\";\n case \"fetch\":\n return \"🌐\";\n case \"edit\":\n case \"write\":\n return \"✏️\";\n case \"delete\":\n return \"🗑️\";\n default:\n return \"🔧\";\n }\n}\n\n/**\n * 解析控制指令(命中则由 daemon 直接处理,不转发给 brain)。\n * 表与解析逻辑都在 chat-commands.ts(`manage.ts` 也要用它渲染 `wemind help`,故不能留在这里)。\n */\nexport function parseControl(text: string): ChatCommand | null {\n return parseChatCommand(text);\n}\n\n/** 解析「明确发给 worker」指令:任务 [agent:]正文 / >xxx / #编号 正文 / 任务列表 / 结束任务 [编号]。 */\nexport function parseTaskControl(text: string): TaskCommand | null {\n return parseTaskCommand(text);\n}\n\n/** 解析「明确问大脑」逃逸前缀:问大脑 xxx / 问大脑:xxx。命中返回去前缀后的文本(强制走 brain,即使 worker 在等你)。 */\nexport function parseBrainEscape(text: string): string | null {\n const m = text.trim().match(/^问大脑[::]?\\s*([\\s\\S]+)$/);\n return m ? m[1].trim() : null;\n}\n\n/** 切脑结果/来源的中文说法(`AI` 总览与答复共用一套词,避免两处说法不一致)。 */\nconst SOURCE_LABEL: Record<ResolvedAgent[\"source\"], string> = {\n env: \"环境变量\",\n file: \"brain.json\",\n auto: \"自动探测\",\n};\n\nfunction agentLine(ag: ResolvedAgent): string {\n return `${ag.name}(来源:${SOURCE_LABEL[ag.source]})`;\n}\n\n/** `AI` / `/ai`:可选 agent(带「装了没」)+ 当前对话大脑/默认任务大脑 + 池中进程。 */\nfunction agentOverview(): string {\n const cfg = loadBrainConfig();\n const brain = getAgent(\"brain\");\n const worker = getAgent(\"worker\");\n const list = presetNames()\n .map((k) => `${k}${presetAvailable(k, cfg) ? \"✅\" : \"❌\"}`)\n .join(\"、\");\n const pool = taskPoolSummary();\n const lines = [\n \"🤖 AI 配置\",\n `对话大脑:${agentLine(brain)}`,\n `默认任务大脑:${agentLine(worker)}${worker.name === brain.name ? \"(随对话大脑)\" : \"\"}`,\n pool.length\n ? `池中 worker:${pool.map((p) => `${p.agent} 忙${p.busy}${p.taskIds.length ? `(#${p.taskIds.join(\"/\")})` : \"\"}/闲${p.idle}`).join(\" | \")}`\n : undefined,\n `可选 agent:${list}、auto`,\n \"切换:`切换大脑 <agent 名>` | `切换任务大脑 <agent 名>`\",\n ].filter((x): x is string => x !== undefined);\n return lines.join(\"\\n\");\n}\n\n/**\n * 切对话大脑:先回 ack,启动新大脑在后台跑、结果补发(决策⑦:切换可能要几十秒,不能让用户干等等不到一句回话)。\n * 这里**不** await 整段切换——启动新大脑期间的入站消息会自己在 acp-brain 门口等新大脑就绪(有界),不会双起进程。\n */\nasync function switchBrainAck(target: string, userId: string, ctx: ControlCtx): Promise<string> {\n const running = switchInFlightTarget();\n if (running) return `正在切到「${running}」,等它完成后再发一次。`;\n const pre = preflightSwitch(target, DATA_DIR);\n if (!pre.ok) return `❌ ${pre.problem}`;\n if (pre.already) {\n // 已经是它(只是把选择落到 brain.json):亚毫秒级,直接等它,不用 ack/补发两段\n const out = await switchBrain(target, { userId, dir: DATA_DIR });\n return out.ok ? out.notice : `❌ ${out.problem}`;\n }\n void switchBrain(target, { userId, dir: DATA_DIR })\n .then((out) => (out.ok ? out.notice : `❌ ${out.problem}`))\n .catch((e) => `❌ 切换出错:${String(e)}`)\n .then(async (r) => {\n log(\"brain switch:\", r.replace(/\\n/g, \" | \"));\n await ctx.notify(r).catch((e) => log(\"switch notice failed:\", String(e)));\n });\n return `⏳ 正在切到「${pre.agent.name}」…(正在启动新大脑,成功或失败稍后补发消息;这期间发来的消息会等新大脑就绪)`;\n}\n\n/** `切换任务大脑`:只改本进程的默认任务大脑(本次运行内一直生效,不写 brain.json)。 */\nfunction setTaskBrain(target: string): string {\n const key = target.trim().toLowerCase();\n const cfg = loadBrainConfig(DATA_DIR);\n const r = resolvePresetByName(key, agentConfigFor(cfg, \"worker\"));\n if (!r.ok) return `❌ ${r.problem}`;\n const cur = getAgent(\"worker\");\n // 与 brain-switch 的 sameAgent 同一条判据(name/bin/args/model):任一项不同都算一次真切换,\n // 否则「改完再切」会被判成「已经是它」,新任务继续用旧二进制/旧参数/旧模型。\n if (\n cur.name === r.agent.name &&\n cur.bin === r.agent.bin &&\n cur.args.join(\"\\u0000\") === r.agent.args.join(\"\\u0000\") &&\n cur.model === r.agent.model\n )\n return `默认任务大脑已经是「${r.agent.name}」,无需切换。`;\n setAgent(\"worker\", r.agent);\n const busy = hasActiveTask();\n killIdleConns(); // 池里的空闲连接属于旧默认:留着没用(下次按新 agent 取),有任务在跑的那些不动\n const note = \"(不写 brain.json,重启后回到配置里的默认)\";\n return busy\n ? `✅ 默认任务大脑:${cur.name} → ${r.agent.name}${note}。\\n在跑的任务继续用它自己起的 agent 跑完;**之后的新任务**用新大脑。`\n : `✅ 默认任务大脑:${cur.name} → ${r.agent.name}${note}。`;\n}\n\ntype ControlCtx = {\n /** 登录者 id(帮助文案据此裁剪)。 */\n owner: string;\n /** 补发一条「必须送达」的消息(用于切脑的 ack/结果两段答复)。 */\n notify: (text: string) => Promise<void>;\n /** 补发一份长内容(交接摘要这类「要给用户留着」的文本走 .md 文件,不刷屏)。 */\n notifyFile?: (text: string) => Promise<void>;\n};\n\n/**\n * `交接`:让当前大脑把上下文写成摘要,写入磁盘 + 回复给用户(长则另发 .md)。\n * 先回一条「在做」的提示:写摘要要走一整个模型回合(可能一两分钟),没有进度提示会让人以为卡死。\n */\nasync function runHandoff(userId: string, ctx: ControlCtx): Promise<string> {\n const from = getAgent(\"brain\").name;\n if (!currentSessionIdOf(from, userId, DATA_DIR))\n return \"ℹ️ 当前大脑还没有和你的对话记录,没什么可交接的。先聊几句再来交接。\";\n await ctx\n .notify(`⏳ 正在让「${from}」把当前上下文写成交接摘要…(可能要一两分钟)`)\n .catch(() => {});\n let text: string;\n try {\n text = (await askBrain(userId, HANDOFF_PROMPT)).trim();\n } catch (e) {\n return `❌ 生成交接摘要失败:${String(e)}`;\n }\n if (!text) return \"❌ 大脑这次没给出摘要内容(空回复),没有保存。可以再试一次。\";\n const now = Date.now();\n saveHandoff(\n {\n version: HANDOFF_VERSION,\n fromAgent: from,\n userId,\n text,\n createdAt: now,\n expiresAt: now + HANDOFF_TTL_MS,\n },\n DATA_DIR,\n );\n const head =\n `✅ 交接摘要已保存(${text.length} 字,来自「${from}」)。\\n` +\n `切到别的大脑后,你发的第一条消息会自动带上它(答完即用掉,只带一次;切回「${from}」不带上)。`;\n if (text.length > LONG_REPLY_THRESHOLD && ctx.notifyFile) {\n await ctx.notifyFile(text).catch((e) => log(\"handoff md send failed:\", String(e)));\n return `${head}\\n(摘要较长,已另发 .md 文件。)`;\n }\n return `${head}\\n\\n${text}`;\n}\n\nasync function handleControl(c: ChatCommand, userId: string, ctx: ControlCtx): Promise<string> {\n if (c.kind === \"list\") return listMcp();\n // 会话类指令的共用上下文:谁、当前对话大脑、数据目录(登记表按 agent 分开存,切换后自然按新大脑列)\n const sc = (): SessionCtx => ({ agent: getAgent(\"brain\").name, userId, dir: DATA_DIR });\n if (c.kind === \"new\") return await newSessionCmd({ ...sc(), name: c.name, create: newSession });\n if (c.kind === \"sessions\") return sessionsCmd({ ...sc(), page: c.page });\n if (c.kind === \"resume\") return await resumeSessionCmd({ ...sc(), resume: resumeSession }, c.ref);\n if (c.kind === \"rename\") return renameSessionCmd(sc(), c.ref, c.name);\n if (c.kind === \"delete\") return deleteSessionCmd(sc(), c.ref);\n if (c.kind === \"add\") {\n const r = addMcp(c.name);\n restart(); // 重启 acp 进程,使其按新沙箱 mcp.json 重新加载\n return r;\n }\n if (c.kind === \"remove\") {\n const r = removeMcp(c.name);\n restart();\n return r;\n }\n if (c.kind === \"ai\") return agentOverview();\n if (c.kind === \"brain\")\n return c.target\n ? await switchBrainAck(c.target, userId, ctx)\n : `当前对话大脑:${agentLine(getAgent(\"brain\"))}。切换用 \\`切换大脑 <agent 名>\\`(可选:${presetNames().join(\"、\")}、auto)。`;\n if (c.kind === \"workerBrain\")\n return c.target\n ? setTaskBrain(c.target)\n : `当前默认任务大脑:${agentLine(getAgent(\"worker\"))}。切换用 \\`切换任务大脑 <agent 名>\\`。`;\n if (c.kind === \"help\") return renderHelp(userId === ctx.owner ? \"owner\" : \"user\", \"wechat\");\n if (c.kind === \"handoff\") return await runHandoff(userId, ctx);\n // 兜底:指令表新增了 kind 但这里还没接通时(正常情况下 TS 会先把这里判成不可达),别静默吞掉\n return `「${(c as { kind: string }).kind}」这条指令还没接通(当前版本请先用 CLI 对应命令)。`;\n}\n\nasync function setTyping(\n account: AccountData,\n to: string,\n ctx: string | undefined,\n status: 1 | 2,\n): Promise<void> {\n if (!ctx) return;\n try {\n const cfg = await getConfig({\n baseUrl: account.baseUrl,\n token: account.botToken,\n ilinkUserId: to,\n contextToken: ctx,\n });\n await sendTyping({\n baseUrl: account.baseUrl,\n token: account.botToken,\n body: { ilink_user_id: to, typing_ticket: cfg.typing_ticket, status },\n });\n } catch (e) {\n // typing 是体验增强(best-effort 不阻断),但失败要留痕:区分「偶发网络失败」与「ticket 拿不到」\n log(`setTyping(${status}) failed:`, String(e));\n }\n}\n\nasync function reply(\n account: AccountData,\n to: string,\n ctx: string | undefined,\n text: string,\n state: number = MessageState.FINISH,\n fallbackToken?: string,\n): Promise<void> {\n const send = (token: string | undefined) => {\n const req: SendMessageReq = {\n msg: {\n from_user_id: \"\",\n to_user_id: to,\n client_id: generateId(\"wemind/wechat-daemon\"),\n message_type: MessageType.BOT,\n message_state: state,\n item_list: [{ type: MessageItemType.TEXT, text_item: { text } }],\n context_token: token,\n },\n };\n return sendMessage({ baseUrl: account.baseUrl, token: account.botToken, body: req });\n };\n try {\n await send(ctx);\n } catch (e) {\n // ret=-2 既可能是「限流」(rapid sends)也可能是「context_token 失效/不匹配」(参数错误)。\n // 两者都靠:退避 ~1.5s 后重试一次——限流靠等待恢复,失效 token 则换用该用户最新缓存 token。\n if (e instanceof ILinkError && e.kind === \"param_error\") {\n log(\"send -2(限流或token失效) → 退避 1.5s 后重试\");\n await new Promise((r) => setTimeout(r, 1500));\n await send(fallbackToken && fallbackToken !== ctx ? fallbackToken : ctx);\n return;\n }\n // 传输层错误(网络抖动 fetch failed/ECONNRESET/超时等):可重试类退避 1.5s 后重试一次\n if (!(e instanceof ILinkError)) {\n const t = classifyTransportError(e);\n if (t.retryable) {\n log(`send 传输错误(${t.kind}) → 退避 1.5s 后重试`);\n await new Promise((r) => setTimeout(r, 1500));\n await send(ctx);\n return;\n }\n }\n throw e;\n }\n}\n\n/** 统一的发送错误处理:按错误类型分类记录;会话过期则暂停账号。 */\nfunction handleSendError(e: unknown, account: AccountData): void {\n if (e instanceof ILinkError) {\n log(\"send error:\", e.message);\n if (e.kind === \"session_expired\") {\n pauseSession(account.botId);\n log(\"会话过期(-14) on send → 该账号暂停 1h,待重新登录\");\n }\n } else {\n const t = classifyTransportError(e);\n log(`send error(传输/${t.kind}${t.retryable ? \",可重试\" : \"\"}):`, t.message);\n }\n}\n\n// 串行发送队列:保证多条消息(进度/答案)按入队顺序送达,不并发乱序\nlet replyChain: Promise<void> = Promise.resolve();\nlet lastSendAt = 0;\n// 两条发送之间的最小间隔:iLink 对 bot 发送有速率限流(rapid sends → ret=-2 \"rate limited\",导致消息丢失,见 NousResearch/hermes-agent#21011)。全局节流主动规避。\nconst MIN_SEND_GAP_MS = Number(process.env.WECHAT_MIN_SEND_GAP_MS) || 1500;\n\n// iLink 在用户回复前能连续发送的消息数上限(实测约 10,可调)。\nconst CONSEC_SEND_LIMIT = Number(process.env.WECHAT_CONSEC_SEND_LIMIT) || 10;\n// 静默期心跳默认 15s(配置可放宽,如 DSH 的 60s);间隔在每回合开头算,见 handleMessage 的 heartbeatMsValue。\nconst HEARTBEAT_DEFAULT_MS = 15_000;\n// 「距该用户上一条消息以来」bot 已连续发送的条数 + 是否处于\"等待继续\"段。按用户(to)维护、**跨回合持续**,仅在该用户发来任何消息时重置(照抄服务端\"任何用户消息都重置连续发送配额\"的规则)。\n// 这样即使 A 任务期间你发了 B、A 又继续发了几条,B 任务接手时计数是延续的,不会误以为\"还能再发10条\"而撞上限。\ntype SendBudget = { count: number; awaiting: boolean };\nconst sendBudget = new Map<string, SendBudget>();\nfunction budgetOf(to: string): SendBudget {\n let b = sendBudget.get(to);\n if (!b) {\n b = { count: 0, awaiting: false };\n sendBudget.set(to, b);\n }\n return b;\n}\n// 重启后旧 token 期间「必须送达」外发的挂起队列:任务结果、CLI 指令答复在用户发来下一条消息之前必然 -2(重试也还是旧 token),消息直接丢。先挂起,新 token 一到按序补发(见 poller 登记最新 token 的地方)。\nconst pendingPushes = new PendingPushes();\n// 本进程**收到过**该用户消息 = 手里是这一进程拿到的 token。只有在这之前的 -2 才是「旧 token」;之后的 -2 属于限流,不该挂起(挂起只会把过时的答复拖到用户下一条消息,反而更乱)。\nconst freshTokenUsers = new Set<string>();\n// 已入 replyChain、还没发完的外发条数(按收件人)。用户发来消息会重置连发计数,但这些「排在消息之后才上线路」的发送仍占服务端的额度,重置时不能一并清零(否则本回合会误以为额度还满,把最终的答案挤到限流上)。\nconst inflightSends = new Map<string, number>();\nfunction trackInflight(to: string, delta: number): void {\n const n = (inflightSends.get(to) ?? 0) + delta;\n if (n > 0) inflightSends.set(to, n);\n else inflightSends.delete(to);\n}\n// 一次补发最多几条:连发额度约 10 条,补发太多会把它吃光、把用户这条消息的正常答复挤到限流上;剩下的留给后续消息继续补。\nconst PENDING_PUSH_BATCH = 5;\n\n/** -2(param_error):既可能是限流、也可能是 token 还是旧的——两者都值得「等该用户下一条消息再试」。 */\nconst isParamError = (e: unknown): boolean => e instanceof ILinkError && e.kind === \"param_error\";\n\n/** 一条外发失败 → 若是「重启后旧 token」造成的 -2(该用户还没在本进程发过消息),挂起等新 token 补发;限流与其它错误照旧只记日志。 */\nfunction holdUndelivered(to: string, text: string, e: unknown): void {\n if (!isParamError(e) || freshTokenUsers.has(to)) return;\n const n = pendingPushes.push(to, text);\n log(`send -2(旧 token)→ 挂起,累计 ${n} 条,等该用户下一条消息补发(${to})`);\n}\n\n/** 补发又失败:重新挂起,等该用户下一条消息再试(传输错误也重挂——这条还没送达);试够 PENDING_PUSH_MAX_TRIES 次就放弃——防一条永远被服务端拒绝的内容每次发消息都拖着 replyChain。 */\nfunction requeueHeld(to: string, text: string, tries: number, e: unknown): void {\n const n = tries + 1;\n if (n >= PENDING_PUSH_MAX_TRIES) {\n log(\n `补发 ${n} 次仍失败 → 放弃这条挂起的推送(${to}):${text.slice(0, 40)}(原因:${e instanceof Error ? e.message : String(e)})`,\n );\n return;\n }\n const size = pendingPushes.push(to, text, n);\n log(`补发 ${n} 次仍失败 → 重新挂起,累计 ${size} 条(${to})`);\n}\n\n/** 新 token 到了:把该收件人挂起的文本按序补发;剩下的留给后续消息。补发量受两重限制:单轮 PENDING_PUSH_BATCH 条、且不越过本回合留给答复的连发额度(否则补发自己就会撞限流、还会把用户的答案挤掉)。 */\nfunction flushHeldPushes(account: AccountData, to: string, token: string): void {\n // 给最终答案留最后两条额度(sendProgress 在 count ≥ 上限-2 时才发「接近上限」提示并停下,答案再占一条)。\n const room = Math.max(0, CONSEC_SEND_LIMIT - 2 - budgetOf(to).count);\n if (room === 0) {\n log(`挂起 ${pendingPushes.size(to)} 条,但本回合连发额度已不够 → 留给后续消息补发(${to})`);\n return;\n }\n const held = pendingPushes.take(to, Math.min(PENDING_PUSH_BATCH, room));\n if (held.length === 0) return;\n const rest = pendingPushes.size(to);\n log(\n `新 token 已到 → 补发挂起消息 ${held.length} 条(${to})${rest > 0 ? `,还有 ${rest} 条排在后续消息` : \"\"}`,\n );\n for (const item of held) {\n budgetOf(to).count += 1;\n void sendOrdered(account, to, token, item.text, MessageState.FINISH, token, (e) =>\n requeueHeld(to, item.text, item.tries, e),\n );\n }\n}\n// 当前进行中回合(worker 串行,只有一个):记录发往用户与所用 token(token 可被「继续」更新)。\nlet activeTurn: { from: string; token: string | undefined } | null = null;\n// onError:「必须送达」的调用方(任务推送、指令答复)用它接管 -2 —— 没有更新的 token 时挂起,等用户下一条消息补发;不传则维持原来的「记日志就丢」。\nfunction sendOrdered(\n account: AccountData,\n to: string,\n ctx: string | undefined,\n text: string,\n state: number = MessageState.FINISH,\n fallbackToken?: string,\n onError?: (e: unknown) => void,\n): Promise<void> {\n trackInflight(to, 1);\n replyChain = replyChain\n .then(async () => {\n const wait = MIN_SEND_GAP_MS - (Date.now() - lastSendAt);\n if (wait > 0) await sleep(wait);\n await reply(account, to, ctx, text, state, fallbackToken).catch((e) => {\n handleSendError(e, account);\n onError?.(e);\n });\n lastSendAt = Date.now();\n })\n .finally(() => trackInflight(to, -1));\n return replyChain;\n}\n\n/**\n * 发一条「必须送达」的答复(控制指令答复、任务答复、任务结束确认、出错提示)。\n *\n * 与 sendOrdered 的唯一差别:**先计入连发额度**。服务端按「距用户上一条消息」计连续发送数,这类答复确实占额度;只发不计数会让 brain 回合的 budgetOf 低估真实值 → 误判「还能发」→ 撞 ret=-2 限流、结果丢失。计数后仍走同一条 replyChain,保证与进度/答案不乱序、遵守最小间隔。\n * 与 `setTaskPush` 同一套语义:task 推送也是「必须送达、只计数不被 awaiting 暂停」。\n */\nfunction sendNotice(\n account: AccountData,\n to: string,\n ctx: string | undefined,\n text: string,\n fallbackToken?: string,\n): Promise<void> {\n budgetOf(to).count += 1;\n return sendOrdered(account, to, ctx, text, MessageState.FINISH, fallbackToken, (e) =>\n holdUndelivered(to, text, e),\n );\n}\n\n/** 把长回复作为 .md 文件发送(走同一串行链+节流);发文件失败则兜底回退为文本。 */\nfunction sendMdOrdered(\n account: AccountData,\n to: string,\n ctx: string | undefined,\n finalText: string,\n fallbackToken?: string,\n): Promise<void> {\n trackInflight(to, 1);\n replyChain = replyChain\n .then(async () => {\n const wait = MIN_SEND_GAP_MS - (Date.now() - lastSendAt);\n if (wait > 0) await sleep(wait);\n const { fileName, data } = buildMdReply(finalText);\n try {\n await sendFile({\n baseUrl: account.baseUrl,\n token: account.botToken,\n to,\n data,\n fileName,\n kind: \"file\",\n contextToken: ctx,\n });\n log(`final as md file: ${fileName} (${finalText.length} chars)`);\n } catch (e) {\n log(\"send md file failed, fallback to text:\", String(e));\n await reply(account, to, ctx, finalText, MessageState.FINISH, fallbackToken).catch((er) =>\n handleSendError(er, account),\n );\n }\n lastSendAt = Date.now();\n })\n .finally(() => trackInflight(to, -1));\n return replyChain;\n}\n\n/** 从解密后的字节嗅探图片 MIME。 */\nfunction sniffMime(b: Buffer): string {\n const h = b.subarray(0, 4).toString(\"hex\");\n if (h.startsWith(\"89504e47\")) return \"image/png\";\n if (h.startsWith(\"47494638\")) return \"image/gif\";\n if (h.startsWith(\"52494646\")) return \"image/webp\";\n return \"image/jpeg\";\n}\n\n/**\n * 处理一条发给大脑的消息。返回**是否真的答成了**(`brainErrorMessage` 兜底算没答成)——\n * 交接摘要的消费就靠这个返回值:只有答成了才把摘要删掉(失败留着,下次还能用)。\n */\nasync function handleMessage(\n account: AccountData,\n ctx: string | undefined,\n to: string,\n text: string,\n imageItems: ImageItem[],\n createTimeMs?: number,\n latestToken?: () => string | undefined,\n): Promise<boolean> {\n const msgAge = () => (createTimeMs ? `${Math.round((Date.now() - createTimeMs) / 1000)}s` : \"?\"); // 这条消息已存在多久(诊断用)\n // activeTurn 只存当前回合的 from/token;连续发送计数用按用户、跨回合的 budgetOf(to)(不在回合开始清零)。\n activeTurn = { from: to, token: ctx };\n const cx = () => activeTurn?.token ?? ctx;\n await setTyping(account, to, cx(), 1);\n // 处理期间每 5s 续发\"正在输入\"(sendtyping 不计入连续消息上限),避免长任务时指示消失、用户以为卡了。\n // 间隔需小于客户端对 typing 的自动过期(约 5–8s),否则中间会出现指示闪烁的空洞。\n const keepAlive = setInterval(() => {\n void setTyping(account, to, cx(), 1).catch(() => {});\n }, 5000);\n\n const t0 = Date.now();\n let lastProgressAt = Date.now();\n // 心跳间隔**每回合**取一次:热切换大脑后新 agent 的心跳覆盖要立刻生效(改造前是 import 期定死的常量,\n // 切到 DSH(heartbeatMs=60s)却还按旧大脑的 15s 发心跳,白占微信连发额度)。\n const heartbeatMsValue =\n Number(process.env.WECHAT_BRAIN_HEARTBEAT_MS) || heartbeatMs() || HEARTBEAT_DEFAULT_MS;\n // iLink 限制 bot 在用户回复前连续发送的消息数(实测约 10,超出被 ret=-2 拒)。计数按用户跨回合维护(budgetOf)。\n // 当已发到「上限-2」(默认第8条)后,下一条进度改为「即将达上限」提示并进入等待,把最后一条额度留给最终答案;用户回复「继续」(或发任何消息)会重置计数恢复发送(见 poller)。\n const sendProgress = (msg: string) => {\n lastProgressAt = Date.now();\n const b = budgetOf(to);\n if (b.awaiting) return; // 等待「继续」期间不发任何进度\n // 发出一条文本会把客户端刚显示的「正在输入」顶掉 → 消息送达后立刻补发一次 typing,否则快回合(回复 < 旧 8s 续发间隔)全程看不到指示。\n const sendWithTyping = (text: string) => {\n void sendOrdered(account, to, cx(), text, MessageState.GENERATING)\n .then(() => setTyping(account, to, cx(), 1))\n .catch(() => {});\n };\n if (b.count >= CONSEC_SEND_LIMIT - 2) {\n b.count += 1;\n b.awaiting = true;\n sendWithTyping(\n \"⚠️ 接近微信连续消息上限,我先暂停进度提示、继续在后台处理;完成后会把结果直接发给你。想立刻恢复进度可回复「继续」。\",\n );\n return;\n }\n b.count += 1;\n sendWithTyping(msg);\n };\n // 即时答复:收到即给一条友好提示,保证用户看到的第一条文本不是 15s 兜底心跳。\n // (有图片时,下面的\"🖼️ 收到 N 张图片…\"已充当即时答复,不再重复发)\n if (imageItems.length === 0) {\n const t = cx();\n log(`turn start to=${to} msgAge=${msgAge()} usingCtx=…${t ? t.slice(-12) : \"NONE\"}`);\n sendProgress(\"🤖 收到,正在处理…\");\n }\n // 心跳:仅当 brain 真正静默(无任何帧)超过心跳间隔才提示——首条用友好措辞,后续才带计时\n let hbCount = 0;\n let answerNoticeSent = false; // \"正在整理回复\"只发一次\n const heartbeat = setInterval(() => {\n if (Date.now() - lastProgressAt >= heartbeatMsValue) {\n hbCount += 1;\n sendProgress(\n hbCount === 1\n ? \"🤔 还在思考/检索中…\"\n : `⏳ 仍在处理中…(已 ${Math.round((Date.now() - t0) / 1000)} 秒)`,\n );\n }\n }, heartbeatMsValue);\n\n // 收图:下载并解密(失败的跳过)\n const images: Array<{ data: string; mimeType: string }> = [];\n if (imageItems.length > 0) {\n sendProgress(`🖼️ 收到 ${imageItems.length} 张图片,下载识别中…`);\n for (const im of imageItems) {\n try {\n const buf = await downloadImage(im);\n images.push({ data: buf.toString(\"base64\"), mimeType: sniffMime(buf) });\n } catch (e) {\n log(\"image download failed:\", String(e));\n }\n }\n }\n\n // 当前大脑不接受图片(如 DSH 0.2.1-rc.2 image:false):不要让它以 -32602 失败重试\n if (images.length > 0 && !supportsImages()) {\n clearInterval(keepAlive);\n clearInterval(heartbeat);\n await setTyping(account, to, cx(), 2);\n budgetOf(to).count += 1;\n try {\n await sendOrdered(\n account,\n to,\n cx(),\n \"🖼️ 当前大脑暂不支持看图,请改用文字描述,或切换到支持图片的大脑。\",\n MessageState.FINISH,\n );\n log(\"image unsupported by current brain; rejected gracefully\");\n } finally {\n activeTurn = null;\n }\n return false; // 没有答成:交接摘要不能被消费掉\n }\n\n // 一次尝试:带进度(步骤编号每次尝试重置)。进度消息节流:避免一回合刷十几条触发 iLink 发送限流(疑似 ret=-2 主因)\n const attempt = (promptText: string, imgs: typeof images): Promise<string> => {\n let step = 0;\n let lastToolProgressAt = 0;\n return askBrain(\n to,\n promptText,\n (ev) => {\n if (ev.type === \"tool\") {\n lastProgressAt = Date.now(); // 工具活动刷新心跳基线\n // 首条立即发(让你知道开始干活了),之后每 15s 最多一条,避免刷屏 + 触发限流\n if (Date.now() - lastToolProgressAt >= 15000) {\n lastToolProgressAt = Date.now();\n step += 1; // 只在真正发出时递增,保证步骤号连续(不因节流跳号)\n const desc = ev.purpose || (ev.title ? zhTool(ev.title) : \"\") || \"调用工具\";\n sendProgress(`${toolIcon(ev.kind)} 步骤 ${step}:${desc}`);\n }\n } else if (ev.type === \"thinking\") {\n lastProgressAt = Date.now();\n } else if (ev.type === \"answer\") {\n if (!answerNoticeSent) {\n answerNoticeSent = true;\n sendProgress(\"✍️ 正在整理回复…\");\n }\n } else {\n // activity:brain 产出任意帧 → 静默刷新心跳基线,避免流式输出期间误报\"仍在处理\"\n lastProgressAt = Date.now();\n }\n },\n imgs,\n );\n };\n\n // 带分类的重试:provider 侧永久错误只试 1 次;backend(模型后端瞬时错误 -32603 等)最多 3 次;timeout/other 最多 2 次\n const runWithRetry = async (promptText: string, imgs: typeof images): Promise<string> => {\n let n = 0;\n while (true) {\n n++;\n try {\n return await attempt(promptText, imgs);\n } catch (e) {\n const plan = brainFailurePlan(e);\n log(`brain error (try ${n}, ${plan.category}): ${String(e)}`);\n if (n < plan.attemptLimit) {\n sendProgress(\n plan.category === \"backend\"\n ? \"⚠️ 模型后端临时出错,正在重试…\"\n : \"⏳ 响应中断了,正在自动重试…\",\n );\n continue;\n }\n throw e;\n }\n }\n };\n\n let answer: string;\n let ok = true;\n try {\n answer = await runWithRetry(text, images);\n } catch (e2) {\n ok = false; // 重试也失败 → 只是把错误提示当回复发出去,不算「答成」(交接摘要据此决定留不留)\n answer = brainErrorMessage(e2, getAgent(\"brain\").name);\n } finally {\n clearInterval(keepAlive);\n clearInterval(heartbeat);\n }\n await setTyping(account, to, cx(), 2);\n // 发图:从回复里提取图片引用(markdown  或裸图片 URL),逐个作为图片消息发出,再发剩余文本\n const { images: replyImages, text: cleaned } = extractImages(answer || \"\");\n const failedImgs: string[] = [];\n for (const im of replyImages) {\n try {\n const buf = await resolveImageBytes(im.src);\n await sendImage({\n baseUrl: account.baseUrl,\n token: account.botToken,\n to,\n image: buf,\n contextToken: cx(),\n });\n log(`sent image: ${im.src.slice(0, 80)}`);\n } catch (e) {\n log(\"send image failed:\", im.src, String(e));\n failedImgs.push(im.src);\n }\n }\n let finalText = cleaned;\n if (failedImgs.length) {\n const note = `(有 ${failedImgs.length} 张图片我没能下载下来,附原链接:\\n${failedImgs.join(\"\\n\")})`;\n finalText = finalText ? `${finalText}\\n\\n${note}` : note;\n }\n if (!finalText && !replyImages.length) finalText = \"(没有内容)\";\n if (finalText) {\n budgetOf(to).count += 1; // 最终答案占一条额度(等待段预留的最后一条)\n log(`final send: msgAge=${msgAge()} sent=${budgetOf(to).count} len=${finalText.length}`);\n const ft = latestToken ? latestToken() : undefined;\n if (finalText.length > LONG_REPLY_THRESHOLD) {\n await sendMdOrdered(account, to, cx(), finalText, ft); // 长回复 → .md 文件(一条消息)\n } else {\n await sendOrdered(account, to, cx(), finalText, MessageState.FINISH, ft);\n }\n }\n log(`replied to ${to} (msgAge=${msgAge()}): ${(answer || \"\").slice(0, 60)}`);\n activeTurn = null; // 回合结束(计数 budgetOf 不清零,留待用户下条消息重置)\n return ok;\n}\n\n// ===== 解耦:拉取器(poller) 与 处理器(worker) 通过队列协作 =====\n// 大脑(ACP session)严格串行,但「拉取消息/抓 token」无需串行。把两者解耦:\n// poller 持续 getUpdates、实时更新 token、把消息入队,永不被大脑阻塞;worker 串行从队列取消息喂大脑(尊重 kiro-cli 串行约束),发送时取最新 token,规避 ret=-2。\ntype QueuedMsg = {\n from: string;\n text: string;\n imageItems: ImageItem[];\n createTimeMs?: number;\n contextToken?: string;\n};\nconst msgQueue: QueuedMsg[] = [];\nlet wakeWorker: (() => void) | null = null;\nfunction notifyWorker(): void {\n const w = wakeWorker;\n wakeWorker = null;\n if (w) w();\n}\n\n/** 发件人鉴权:登录者(owner,始终允许)或在登录者白名单 allowFrom 中——兜底校验(微信平台只发送登录者本人的消息,这道闸防的是发送范围变化/别的入口)。 */\nfunction isAuthorized(from: string, owner: string): boolean {\n return !!from && (from === owner || isAllowed(from));\n}\n\nasync function pollerLoop(account: AccountData, owner: string, state: ServerState): Promise<void> {\n while (true) {\n if (isSessionPaused(account.botId)) {\n await sleep(Math.min(getRemainingPauseMs(account.botId), 60_000) || 60_000);\n continue;\n }\n let resp;\n try {\n resp = await getUpdates({\n baseUrl: account.baseUrl,\n token: account.botToken,\n updatesBuf: state.updatesBuf,\n timeoutMs: 25_000,\n });\n } catch (e) {\n log(\"getUpdates error:\", String(e));\n await sleep(2000);\n continue;\n }\n try {\n if (resp.errcode === SESSION_EXPIRED_ERRCODE) {\n log(\"session expired (-14); pausing 1h. Send a WeChat message to revive after re-login.\");\n pauseSession(account.botId);\n continue;\n }\n if (resp.errcode) {\n log(\"server error:\", resp.errcode, resp.errmsg);\n await sleep(2000);\n continue;\n }\n if (resp.get_updates_buf) state.updatesBuf = resp.get_updates_buf;\n\n const news = (resp.msgs ?? []).filter(\n (m) =>\n m.message_type === MessageType.USER && m.message_id && m.message_id > state.lastMessageId,\n );\n // 先登记本批所有最新 token(worker 发送时取最新)\n for (const m of news) {\n const f = m.from_user_id ?? \"\";\n if (isAuthorized(f, owner))\n log(\n `recv msg id=${m.message_id} ctx=…${m.context_token ? m.context_token.slice(-12) : \"NONE\"} age=${m.create_time_ms ? Math.round((Date.now() - m.create_time_ms) / 1000) + \"s\" : \"?\"}`,\n );\n if (m.context_token) {\n state.contextTokens[`${account.botId}:${f}`] = m.context_token;\n freshTokenUsers.add(f); // 本进程拿到的 token——此后该用户的 -2 不再当「旧 token」挂起\n }\n }\n let enq = false;\n for (const msg of news) {\n state.lastMessageId = Math.max(state.lastMessageId, msg.message_id ?? 0);\n const from = msg.from_user_id ?? \"\";\n if (!isAuthorized(from, owner)) {\n log(\"ignore unauthorized sender:\", from);\n continue;\n }\n const text = extractText(msg);\n const voiceItem = (msg.item_list ?? []).find(\n (it) => it.type === MessageItemType.VOICE,\n )?.voice_item;\n if (voiceItem)\n log(\n `recv voice: text=${voiceItem.text ? `\"${voiceItem.text.slice(0, 30)}\"` : \"(空,无微信转写)\"} playtime=${voiceItem.playtime ?? \"?\"}`,\n );\n const imageItems = (msg.item_list ?? [])\n .filter((it) => it.type === MessageItemType.IMAGE && it.image_item)\n .map((it) => it.image_item as ImageItem);\n if (!text && imageItems.length === 0) {\n log(\"skip empty/unsupported message\");\n continue;\n }\n // 服务端按「距用户上一条消息」计连续发送数 → 任何来自 owner 的消息都重置该用户计数 + 退出等待段。但已经进了 replyChain、会在本条消息之后才上线路的发送(补发、上一回合没发完的)仍占新窗口的额度,所以不能直接清零,而是把它们算进来。\n const b = budgetOf(from);\n const wasAwaiting = b.awaiting;\n b.count = inflightSends.get(from) ?? 0;\n b.awaiting = false;\n // 「继续」且当前回合正处于「等待继续」段:换用本消息新 token 恢复,且仅作恢复信号、不入队\n if (text.trim() === \"继续\" && activeTurn && activeTurn.from === from && wasAwaiting) {\n activeTurn.token = msg.context_token ?? activeTurn.token;\n log(\"收到「继续」(等待中) → 恢复进度、重置计数、换新 token\");\n continue;\n }\n msgQueue.push({\n from,\n text,\n imageItems,\n createTimeMs: msg.create_time_ms,\n contextToken: msg.context_token,\n });\n enq = true;\n }\n // 新 token 已到 → 补发旧 token 期间挂起的推送。等本批所有消息的计数重置都做完再补发(否则同一批里后一条消息的 reset 会把补发的计数抹掉);每个发件人一次,用 state 里该用户最新的 token。补发先进 replyChain,排在本批消息触发的进度/答案之前(该循环全程同步,worker 来不及插进来)。\n const flushTo = new Set<string>();\n for (const msg of news) {\n const from = msg.from_user_id ?? \"\";\n if (!msg.context_token || flushTo.has(from) || !isAuthorized(from, owner)) continue;\n flushTo.add(from);\n const fresh = state.contextTokens[`${account.botId}:${from}`];\n if (fresh) flushHeldPushes(account, from, fresh);\n }\n saveState(state);\n if (enq) notifyWorker();\n } catch (e) {\n log(\"poller error:\", e instanceof Error ? e.stack || e.message : String(e));\n }\n }\n}\n\nasync function workerLoop(account: AccountData, state: ServerState, owner: string): Promise<void> {\n while (true) {\n if (isSessionPaused(account.botId)) {\n await sleep(Math.min(getRemainingPauseMs(account.botId), 30_000) || 30_000);\n continue;\n }\n if (msgQueue.length === 0) {\n await new Promise<void>((r) => {\n wakeWorker = r;\n if (msgQueue.length > 0) {\n wakeWorker = null;\n r();\n }\n });\n continue;\n }\n const item = msgQueue.shift() as QueuedMsg;\n const from = item.from;\n const token = item.contextToken; // 用这条消息自己带的 context_token 回复(每条消息各有各的 token)\n const latestToken = () => state.contextTokens[`${account.botId}:${from}`]; // 该用户最新缓存 token(仅作 -2 参数错误时的兜底)\n\n // 控制指令:单独处理\n const ctrl = item.text ? parseControl(item.text) : null;\n if (ctrl) {\n // owner-only(切脑/交接):非本人 → 短提示拒绝,**不**转发给大脑(否则大脑可能替用户执行越权动作)\n if (isOwnerOnly(ctrl) && from !== owner) {\n const r = \"这条指令仅登录者可用。发送「帮助」看你自己的指令。\";\n log(\"control: denied(not owner)\", JSON.stringify(ctrl));\n await sendNotice(account, from, token, r, latestToken());\n continue;\n }\n // 补发用**最新**token(切换可能几十秒,期间用户又发过消息;旧 token 可能已失效)\n const notify = (text: string) =>\n sendNotice(account, from, latestToken() ?? token, text, latestToken());\n const notifyFile = (text: string) => {\n budgetOf(from).count += 1; // .md 也是一条消息,占同一份额度\n return sendMdOrdered(account, from, latestToken() ?? token, text, latestToken());\n };\n let r: string;\n try {\n r = await handleControl(ctrl, from, { owner, notify, notifyFile });\n } catch (e) {\n r = \"处理指令出错:\" + String(e);\n }\n log(\"control:\", JSON.stringify(ctrl), \"->\", r.replace(/\\n/g, \" | \"));\n await sendNotice(account, from, token, r, latestToken());\n continue;\n }\n\n // ── 任务路由 ──\n // 1) 结束任务(可带编号/全部)\n const tcmd = item.text ? parseTaskControl(item.text) : null;\n if (tcmd?.kind === \"stop\") {\n const r = stopTask(from, tcmd.id);\n log(\"task: stop ->\", r.replace(/\\n/g, \" | \"));\n await sendNotice(account, from, token, r, latestToken());\n continue;\n }\n // 2) 任务列表\n if (tcmd?.kind === \"tasks\") {\n await sendNotice(account, from, token, renderTaskList(from), latestToken());\n continue;\n }\n // 3) 给指定编号的任务回话\n if (tcmd?.kind === \"reply\") {\n const r = injectTask(from, tcmd.text, tcmd.id);\n log(`task: inject(#${tcmd.id}):`, tcmd.text.slice(0, 40));\n if (r) await sendNotice(account, from, token, r, latestToken());\n continue;\n }\n // 4) 派发:带 agent 名 = 指定 agent 起一条**新任务**(多任务并行);不带 = 沿用「有任务就发给它,没有就派发」\n if (tcmd?.kind === \"toWorker\" && tcmd.agentToken) {\n // 指定的 agent **只对这条任务生效**,不改默认任务大脑:那是账号级状态(`切换任务大脑` 是 owner-only),顺手改会让「派一条任务」变成一次隐形的全局设置变更,也破坏微信与 CLI(`--agent` 只影响那一条任务)两边的对称。\n const r = dispatchTask(from, tcmd.text, { agentKey: tcmd.agentToken });\n log(`task: dispatch(agent=${tcmd.agentToken}):`, r.replace(/\\n/g, \" | \"));\n await sendNotice(account, from, token, r, latestToken());\n continue;\n }\n if (tcmd?.kind === \"toWorker\") {\n // 明确发给 worker:有活跃任务 → 发给它/追加(唯一一条喂下一轮、running 暂存、多条则回编号提示);\n // 没有活跃任务 → 派发新任务(不「续跑」已中断的:那是「继续」/`#n 继续` 的事,明确派发就该是新的活)。\n if (hasActiveTask(from)) {\n const r = injectTask(from, tcmd.text);\n log(\"task: inject(explicit):\", tcmd.text.slice(0, 40), \"->\", r.replace(/\\n/g, \" | \"));\n if (r) await sendNotice(account, from, token, r, latestToken());\n } else {\n const r = dispatchTask(from, tcmd.text);\n log(\"task: dispatch:\", r.replace(/\\n/g, \" | \"));\n await sendNotice(account, from, token, r, latestToken());\n }\n continue;\n }\n // 2) 明确「问大脑」逃逸前缀 → 去前缀后强制走 brain(即使 worker 在等你)\n const brainEscape = item.text ? parseBrainEscape(item.text) : null;\n // 3) 无前缀 + worker 正等你回 + 非图片 → 默认发给 worker(唯一一条 waiting 直接喂;多条 waiting 回编号提示);\n // 其余(running / 无任务 / 逃逸 / 图片)→ brain\n if (!brainEscape && item.imageItems.length === 0 && awaitingTasks(from).length > 0) {\n const r = injectTask(from, item.text);\n log(\"task: inject(awaiting):\", item.text.slice(0, 40), \"->\", r.replace(/\\n/g, \" | \"));\n if (r) await sendNotice(account, from, token, r, latestToken());\n continue;\n }\n // 已中断的任务(daemon 重启留下的):没有活跃任务时**只有「继续」这类短句**才放行进任务表续跑。\n // 为什么还要关键字这层闸:一条陈旧的中断任务会一直躺在表里,若见了消息就放行,「帮我查一下 X」这类日常提问会被它吞掉。\n if (\n !brainEscape &&\n item.imageItems.length === 0 &&\n !hasActiveTask(from) &&\n interruptedTasks(from).length > 0 &&\n looksLikeResume(item.text)\n ) {\n const r = injectTask(from, item.text);\n log(\"task: resume(interrupted):\", item.text.slice(0, 40), \"->\", r.replace(/\\n/g, \" | \"));\n if (r) await sendNotice(account, from, token, r, latestToken());\n continue;\n }\n\n const brainText = brainEscape ?? item.text;\n // 交接注入:切换大脑后,发给**新**大脑的第一条消息带上前一个大脑留下的摘要(切回来源大脑、别人、\n // 过期都不带上,规则见 handoff.ts)。作为前缀带上 = 不额外占一个回合、不多一次模型调用。\n // 切换进行中时按**目标**大脑判断(这条消息会在门口等新大脑就绪,实际答话的是它)。\n const pending = loadHandoff(DATA_DIR);\n const goal = switchInFlightTarget() ?? getAgent(\"brain\").name;\n const htext = takeHandoffFor(pending, goal, from);\n const withHandoff =\n pending && htext ? handoffPrefix(pending.fromAgent, htext) + brainText : brainText;\n if (htext) log(`handoff injected from=${pending?.fromAgent} to=${goal} chars=${htext.length}`);\n try {\n const answered = await handleMessage(\n account,\n token,\n from,\n withHandoff,\n item.imageItems,\n item.createTimeMs,\n latestToken,\n );\n // 答成才消费:失败/被图片挡下就留着,下次还能用。\n // 切回来答话(回滚到来源大脑,或切换目标就是它)也不消费——那是它自己写的摘要,留着下次切走时用。\n const answeredBy = getAgent(\"brain\").name;\n if (htext && answered && answeredBy !== pending?.fromAgent && clearHandoff(DATA_DIR))\n log(`handoff consumed by ${answeredBy}`);\n } catch (e) {\n log(\"handleMessage error:\", e instanceof Error ? e.stack || e.message : String(e));\n await sendNotice(\n account,\n from,\n token,\n \"处理这条消息时出错了,请再发一次试试。\",\n latestToken(),\n );\n }\n }\n}\n\n// ===== 本地命令入口(文件桥):CLI 写 task-inbox/*.json,daemon 扫描分发到 task-manager,结果写回 task-out/*.json =====\nconst TASK_INBOX_DIR = taskInboxDir(DATA_DIR);\nconst TASK_STATE_FILE = path.join(DATA_DIR, \"task-state.json\");\nconst TASK_SCAN_MS = Number(process.env.WEMIND_TASK_SCAN_MS) || 1000;\n\n/** 扫描 task-inbox:按文件名(时间戳)升序逐个读、分发到 task-manager(dispatch/reply/stop)、删文件。本地命令默认作用于 owner。 */\nfunction scanTaskInbox(owner: string): void {\n let files: string[];\n try {\n files = fs.readdirSync(TASK_INBOX_DIR).filter((f) => f.endsWith(\".json\"));\n } catch {\n return;\n } // 目录不存在/读失败:跳过\n if (!files.length) return;\n files.sort(); // 文件名带时间戳前缀,字典序即时间序\n for (const f of files) {\n const fp = path.join(TASK_INBOX_DIR, f);\n let cmd: {\n kind?: string;\n text?: string;\n cwd?: string;\n agent?: string;\n id?: number | string;\n ack?: string;\n };\n try {\n cmd = JSON.parse(fs.readFileSync(fp, \"utf-8\"));\n } catch {\n try {\n fs.unlinkSync(fp);\n } catch {\n /* ignore */\n }\n continue;\n } // 坏文件丢弃\n try {\n fs.unlinkSync(fp);\n } catch {\n /* ignore */\n } // 先删,避免重复处理\n try {\n if (cmd.kind === \"dispatch\") {\n // 答复由 task-manager 在**起进程有结果之后**发(达上限/起不来都是那时才知道),这里只把 ack id 递进去\n log(\n \"local task dispatch:\",\n dispatchTask(owner, String(cmd.text ?? \"\"), {\n ...(cmd.cwd ? { cwd: String(cmd.cwd) } : {}),\n ...(cmd.agent ? { agentKey: String(cmd.agent) } : {}),\n ...(cmd.ack ? { ackId: String(cmd.ack) } : {}),\n }).slice(0, 60),\n );\n } else if (cmd.kind === \"reply\") {\n const id = Number(cmd.id) > 0 ? Number(cmd.id) : undefined;\n const ackId = cmd.ack === undefined ? undefined : String(cmd.ack);\n const r = injectTask(owner, String(cmd.text ?? \"\"), id, ackId);\n log(\n \"local task reply:\",\n id ? `#${id}` : \"-\",\n String(cmd.text ?? \"\").slice(0, 40),\n r.slice(0, 40),\n );\n } else if (cmd.kind === \"stop\") {\n log(\n \"local task stop:\",\n stopTask(\n owner,\n cmd.id === \"all\" ? \"all\" : Number(cmd.id) > 0 ? Number(cmd.id) : undefined,\n cmd.ack === undefined ? undefined : String(cmd.ack),\n ),\n );\n } else {\n log(\"unknown task-inbox cmd:\", cmd.kind);\n writeTaskAck(\n DATA_DIR,\n cmd.ack === undefined ? undefined : String(cmd.ack),\n `❌ 不认识的任务命令:${cmd.kind}`,\n );\n }\n } catch (e) {\n log(\"task-inbox dispatch error:\", String(e));\n writeTaskAck(\n DATA_DIR,\n cmd.ack === undefined ? undefined : String(cmd.ack),\n `❌ 命令执行出错:${String(e)}`,\n );\n }\n }\n}\n\n/**\n * 执行一条 CLI 桥请求,返回要打印给用户的那句话。\n * 与微信指令**共用同一批实现函数**(switchBrain / session-manager / task-manager),\n * 所以「CLI 与微信行为一致」不是靠约定,而是同一段代码。\n * 这里的 `owner` 就是登录者:本地命令没有「谁发的」的概念,一律作用于本人。\n */\nasync function handleControlRequest(req: ControlRequest, owner: string): Promise<string> {\n const sc = (): SessionCtx => ({ agent: getAgent(\"brain\").name, userId: owner, dir: DATA_DIR });\n switch (req.kind) {\n case \"brain.switch\": {\n if (!req.target) return \"❌ 用法:brain switch <agent 名|auto>\";\n const pre = preflightSwitch(req.target, DATA_DIR);\n if (!pre.ok) return `❌ ${pre.problem}`;\n // 注意:这里**不用** ack/补发两段(那是微信侧的写法)。CLI 在等应答,直接等完整结果更实在。\n const out = await switchBrain(req.target, { userId: owner, dir: DATA_DIR });\n return out.ok ? out.notice : `❌ ${out.problem}`;\n }\n case \"brain.worker\":\n if (!req.target)\n return `当前默认任务大脑:${agentLine(getAgent(\"worker\"))}。切换用 \\`wemind brain switch <agent 名> --worker\\`。`;\n return setTaskBrain(req.target);\n case \"brain.handoff\":\n return await runHandoff(owner, { owner, notify: async () => {} });\n case \"session.new\":\n return await newSessionCmd({ ...sc(), name: req.name, create: newSession });\n case \"session.list\":\n return sessionsCmd({ ...sc(), page: req.page });\n case \"session.resume\":\n if (!req.ref) return \"❌ 用法:session resume <序号|名字|id前缀>\";\n return await resumeSessionCmd({ ...sc(), resume: resumeSession }, req.ref);\n case \"session.rename\":\n if (!req.ref || !req.name) return \"❌ 用法:session rename <序号|名字> <新名字>\";\n return renameSessionCmd(sc(), req.ref, req.name);\n case \"session.delete\":\n if (!req.ref) return \"❌ 用法:session delete <序号|名字|id前缀>\";\n return deleteSessionCmd(sc(), req.ref);\n case \"task.list\":\n return renderTaskList(owner);\n case \"task.reply\": {\n if (!req.text) return '❌ 用法:task-reply [#编号] \"内容\"';\n const r = injectTask(\n owner,\n req.text,\n typeof req.taskId === \"number\" ? req.taskId : undefined,\n );\n return r || \"已把这句话发给任务(它继续跑,结果会发到微信)。\";\n }\n case \"task.stop\":\n return stopTask(owner, req.taskId);\n }\n}\n\n// ===== 控制桥(CLI ↔ daemon):CLI 写 control-inbox,这里执行 + 写 control-out =====\nlet controlBusy = false; // 同时只处理一条:切换/会话恢复本来就要串行(它们争的是同一个 ACP 连接)\n\n/** 扫描控制桥收件箱。异步:brain.switch 要等启动新大脑(最长 ~2 分钟),期间不能再处理下一条。 */\nasync function scanControlInbox(owner: string): Promise<void> {\n if (controlBusy) return;\n const items = readControlInbox(DATA_DIR);\n if (!items.length) return;\n controlBusy = true;\n try {\n for (const { file, req } of items) {\n try {\n fs.unlinkSync(path.join(controlInboxDir(DATA_DIR), file));\n } catch {\n /* ignore */\n } // 先删,避免重复处理\n if (!req) {\n log(\"control: 非法请求(未知 kind / 坏 JSON),已丢弃:\", file);\n continue;\n }\n log(\"control(cli):\", req.kind, JSON.stringify({ target: req.target, ref: req.ref }));\n let text: string;\n try {\n text = await handleControlRequest(req, owner);\n } catch (e) {\n text = `❌ 处理出错:${String(e)}`;\n }\n // 约定:❌ 开头即失败(CLI 据此定退出码)。应答一定写——不写的话 CLI 会白等到超时。\n writeControlResponse(\n { id: req.id, at: Date.now(), ok: !text.startsWith(\"❌\"), text },\n DATA_DIR,\n );\n }\n } finally {\n controlBusy = false;\n }\n}\n\nexport async function runDaemon(): Promise<void> {\n // 启动即校验大脑:早失败、说人话——避免「进程看着正常、直到第一条消息才炸」(历史上表现为反复「⏳ 响应中断了,正在自动重试…」,实为默认大脑的二进制没装)。\n // 先种槽位再校验:此后运行期一律从槽位取 agent(热切换会更新它,见 agent-slot.ts),启动期与运行期同一个来源。\n seedSlots(loadBrainConfig());\n const agent = getAgent(\"brain\");\n const brainProblem = agentProblem(agent);\n if (brainProblem) {\n log(`FATAL 大脑不可用:${brainProblem}`);\n process.exit(BRAIN_CONFIG_EXIT_CODE); // 后台守护进程据此停止自动重启(配置错误重拉无益)\n }\n log(\n `brain=${agent.name} bin=${agent.bin} args=[${agent.args.join(\" \")}] shell=${agent.shell} (source: ${agent.source})`,\n );\n // 运行时身份:list 在 daemon 活着时据此显示「实际生效的大脑」(brain.pid 要等 ACP 懒启动才有)\n writeBrainState(DATA_DIR, agent);\n const wagent = getAgent(\"worker\");\n if (wagent.name !== agent.name)\n log(\n `worker=${wagent.name} bin=${wagent.bin} args=[${wagent.args.join(\" \")}] (source: ${wagent.source})`,\n );\n // 任务大脑不可用只警告、不退出:只聊天的用户不受影响,但派发任务会失败——早说清楚比任务静默失败好。\n const workerProblem = agentProblem(wagent);\n if (workerProblem) log(`WARN 任务大脑不可用(派发任务会失败):${workerProblem}`);\n const account = loadAccount();\n if (!account?.botToken) {\n log(\"not logged in; run the MCP login_qrcode flow first.\");\n process.exit(1);\n }\n const owner = process.env.WECHAT_OWNER_ID?.trim() || account.userId; // 登录者:始终授权(兜底校验)\n if (owner) addAllowFrom(owner, DATA_DIR); // 种入登录者白名单(自动迁移老账号);新登录在 finalize 已配对\n // 写 PID 文件,供管理命令(logout/delete)精确定位并停止本账号的 daemon\n const pidFile = path.join(DATA_DIR, \"daemon.pid\");\n try {\n fs.writeFileSync(pidFile, String(process.pid), \"utf-8\");\n } catch {\n /* best-effort */\n }\n const cleanupPid = () => {\n try {\n fs.unlinkSync(pidFile);\n } catch {\n /* ignore */\n }\n };\n // 退出时连带收掉池里的全部 worker:Windows 上子进程不会随父进程退出,漏掉就是一堆孤儿 agent 进程。\n // (任务表不在这里改状态:下次启动统一标「已中断」——那才是它真实的处境。)\n const cleanupAll = () => {\n cleanupPid();\n killWorkerPool();\n };\n process.on(\"exit\", cleanupAll);\n process.on(\"SIGINT\", () => {\n cleanupAll();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n cleanupAll();\n process.exit(0);\n });\n // 防御:任何未捕获的 promise 拒绝/异常(多为瞬时网络 fetch failed 或回合内 send 被拒)只记录、不退出,避免 daemon 崩溃丢掉在途回合、用户卡在心跳。此前的 code=1 崩溃即源于此类未捕获拒绝。\n process.on(\"unhandledRejection\", (reason) => {\n log(\n \"unhandledRejection:\",\n reason instanceof Error ? reason.stack || reason.message : String(reason),\n );\n });\n process.on(\"uncaughtException\", (err) => {\n log(\"uncaughtException:\", err?.stack || String(err));\n });\n ensureBrainHome(); // 准备 brain 沙箱(默认空 MCP)\n ensureWorkerHome(); // 准备 worker 沙箱(继承全量 MCP 除 wechat + 记忆/技能/settings)\n log(`up. bot=${account.botId} owner=${owner} allowFrom=${readAllowFrom(DATA_DIR).length}`);\n await notifyStart({ baseUrl: account.baseUrl, token: account.botToken }).catch(() => {});\n\n const state: ServerState = loadState();\n // worker 任务推送:用该用户最新 token 经串行发送层发出(与 brain 共用 sendOrdered,保证不乱序)\n setTaskPush((to, text) => {\n const tok = state.contextTokens[`${account.botId}:${to}`];\n // task 推送也占用连续发送额度(与 brain 回合共用 budgetOf):task 在后台运行,可能与 brain 回合并发,若不计数,本地 budget 会低估服务端真实连续发送数 → brain 的 sendProgress 误判「还能发」→ 撞 ret=-2 限流、结果丢失。\n // 注:task 推送是「本轮结果」(必须送达),故只累加计数、不被 awaiting 暂停(暂停只作用于可丢弃的 brain 进度提示)。\n budgetOf(to).count += 1;\n void sendOrdered(account, to, tok, text, MessageState.FINISH, tok, (e) =>\n holdUndelivered(to, text, e),\n );\n });\n // worker 跑一轮期间持续「正在输入中」(typing 不占连续发送上限),避免长任务静默让用户以为卡了\n setTaskTyping((to) => {\n void setTyping(account, to, state.contextTokens[`${account.botId}:${to}`], 1);\n });\n // 任务状态写入磁盘:worker 状态记录原子写 task-state.json,供本地 `manage.js task-status` 读\n setTaskStateSink((snap: TaskStateSnapshot) => {\n try {\n const tmp = `${TASK_STATE_FILE}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(snap, null, 2), \"utf-8\");\n fs.renameSync(tmp, TASK_STATE_FILE);\n } catch {\n /* best-effort */\n }\n });\n setTaskAckSink((id, text) => writeTaskAck(DATA_DIR, id, text)); // 本地命令的回结果通道(`wemind task` 等着的「到底派上了没有」)\n // 本地命令入口:启动清空收件箱与答复目录(作废重启前残留命令、防重放),再起独立定时器扫描分发(不挂 poller,避免被 25s 长轮询阻塞)\n clearTaskDirs(DATA_DIR);\n setInterval(() => scanTaskInbox(owner), TASK_SCAN_MS);\n // 控制桥:同样「启动清空 + 独立定时器扫描」(作废重启前的残留命令,防重放)\n clearControlDirs(DATA_DIR);\n markBridgeReady(DATA_DIR, process.pid); // 凭据:CLI 据此判断「在跑的这个 daemon 认不认识控制桥」\n setInterval(() => void scanControlInbox(owner), TASK_SCAN_MS);\n // 任务表:读回上次的(未完成的标「已中断」,不自动恢复);编号接着排\n const restored = initTasks(DATA_DIR);\n if (restored.interrupted.length)\n log(`restored tasks: interrupted=[${restored.interrupted.join(\",\")}](回「继续」可续跑)`);\n // 会话登记表:把当前会话表里有、登记表里还没有的旧会话补进去(功能上线前的老账号,一次性、幂等)\n const backfilled = syncFromPointerMap(DATA_DIR);\n if (backfilled) log(`session registry backfilled: ${backfilled} 条(来自当前会话表)`);\n // 进程池清扫:空闲超时的 worker 回收(默认 5 分钟)\n const poolSweep = setInterval(() => sweepWorkerPool(), 30_000);\n poolSweep.unref?.();\n // 解耦运行:拉取器持续抓消息(永不被大脑阻塞),处理器串行喂大脑\n void pollerLoop(account, owner, state);\n await workerLoop(account, state, owner);\n}\n\n// daemon 本体由 cli.ts 的 `wemind daemon` 调用 runDaemon() 启动,此处不再顶层自启。\n","// pm2 封装:`wemind start --runner pm2` / `stop` 委托 pm2 做常驻(后台守护进程 / 开机自启 / 多账号)。\n// 默认 runner 是自带后台守护进程(watchdog-runner.ts);本模块只在用户显式选 pm2 时使用。\nimport { spawnSync, type SpawnSyncReturns } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { accountDir } from \"@wemind/wechat-ilink-core\"; // 与 daemon 自身用的是同一个根(尊重 WEMIND_HOME)\n\nimport { BRAIN_CONFIG_EXIT_CODE } from \"./brain-agents.js\";\n\n/*\n * 统一用「单条命令字符串 + shell」调用 pm2,而不是 spawnSync(cmd, args[], { shell: true })。\n * 后者在 Node 20+ 会触发 DEP0190 弃用警告(shell:true 同时传 args 数组、参数仅拼接不转义)。\n * 这里命令中只拼入受控值:固定的 cliPath(import.meta 解析得到)、账号 key 派生的 name 与日志路径,且路径都加引号处理空格——无任何外部不可信输入,无注入风险。\n */\nfunction pm2Run(\n cmd: string,\n opts: { quiet?: boolean; env?: NodeJS.ProcessEnv } = {},\n): SpawnSyncReturns<Buffer> {\n return spawnSync(`pm2 ${cmd}`, {\n shell: true,\n stdio: opts.quiet ? \"ignore\" : \"inherit\",\n env: opts.env ?? process.env,\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n}\n\n/** pm2 是否已全局安装、可用。 */\nexport function pm2Available(): boolean {\n try {\n return pm2Run(\"-v\", { quiet: true }).status === 0;\n } catch {\n return false;\n }\n}\n\n/** 账号的 daemon 日志路径,与 run-daemon.ps1 / ecosystem 方式一致:`~/.wemind/<key>/daemon.log`。 */\nfunction daemonLogPath(key: string): string {\n return path.join(accountDir(key), \"daemon.log\");\n}\n\n/**\n * 生成 pm2 的 app 声明(`{ apps: [decl] }`,写进账号目录的 `pm2.app.json`)。\n *\n * **为什么用 JSON 声明而不是把参数堆在 `pm2 start` 命令行上**:\n * - `restart_delay` / `stop_exit_codes` / `max_restarts` 在命令行上是 `--restart-delay` / `--stop-exit-codes`(**变参**)/ `--max-restarts`,还要和 `-- daemon` 这个「脚本参数分隔符」挤在同一条命令里——解析顺序、变参吞掉后续 token 的风险都只能靠真机试;\n * - JSON 声明没有解析层:键值原样进 `pm2_env`(`stop_exit_codes` 在 `God.handleExit` 里既接受数字数组也接受数字字符串,这里给数字数组),与 `ecosystem.config.cjs` 同源同义;\n * - 路径全走 JSON 转义,不再依赖 shell 引号。\n *\n * 与 `ecosystem.config.cjs` 保持一致的项:app 名 `wemind-<key>`、崩溃 5s 重拉、最多 50 次、退出码 78(大脑配置错误)不重启、日志合并到账号 `daemon.log`。导出供单测直接断言(不需要装 pm2)。\n */\nexport function buildAppDeclaration(\n key: string,\n cliPath: string,\n logPath: string,\n): Record<string, unknown> {\n return {\n name: `wemind-${key}`,\n script: cliPath,\n args: \"daemon\", // 等价于 `cli.js daemon`;不必再依赖 `-- daemon` 分隔符\n interpreter: \"node\",\n cwd: os.homedir(), // 不继承 `pm2 start` 时的 shell 目录(换了目录再 start 行为才一致)\n autorestart: true,\n restart_delay: 5000, // 与 builtin 后台守护进程 / run-daemon.ps1 的 5s 重拉一致\n max_restarts: 50, // 防崩溃风暴\n stop_exit_codes: [BRAIN_CONFIG_EXIT_CODE], // 78 = 大脑配置错误,重拉无益\n out_file: logPath,\n error_file: logPath,\n merge_logs: true,\n watch: false, // daemon 是长轮询常驻进程\n // 只声明账号;其余(DEEPSEEK_API_KEY、WEMIND_HOME 等)由 pm2 从执行 `pm2 start` 的进程环境合并进来(`Common.prepareAppConf`:shell env 打底、声明里的 env 覆盖)——与改动前一致。\n env: { WECHAT_ACCOUNT: key },\n };\n}\n\n/**\n * 把声明写进账号目录,返回文件路径。单独导出便于测试(不触碰 pm2);`dir` 可覆盖目标目录——单测传临时目录,避免动真实 `~/.wemind`。\n */\nexport function writeAppDeclaration(\n key: string,\n cliPath: string,\n logPath: string,\n dir: string = accountDir(key),\n): string {\n fs.mkdirSync(dir, { recursive: true });\n const file = path.join(dir, \"pm2.app.json\");\n const apps = [buildAppDeclaration(key, cliPath, logPath)];\n fs.writeFileSync(file, JSON.stringify({ apps }, null, 2) + \"\\n\", \"utf-8\");\n return file;\n}\n\n/** 用 pm2 启动某账号的 daemon:app 名 `wemind-<key>`,注入 WECHAT_ACCOUNT,日志归一到账号 daemon.log。 */\nexport function pm2Start(key: string, cliPath: string): { ok: boolean; msg: string } {\n if (!pm2Available()) {\n return {\n ok: false,\n msg: \"未检测到 pm2。请先安装:npm i -g pm2,再重试 `wemind start`。\",\n };\n }\n const log = daemonLogPath(key);\n const name = `wemind-${key}`;\n // 已存在同名进程则先删,确保按最新声明重启\n pm2Run(`delete ${name}`, { quiet: true });\n const decl = writeAppDeclaration(key, cliPath, log);\n const r = pm2Run(`start \"${decl}\"`, { env: { ...process.env, WECHAT_ACCOUNT: key } });\n if (r.status !== 0) {\n return { ok: false, msg: `pm2 启动失败(exit=${r.status}),声明文件:${decl}` };\n }\n pm2Run(\"save\", { quiet: true });\n return {\n ok: true,\n msg:\n `已用 pm2 启动 ${name}(日志:${log})。\\n` +\n \" 崩溃 5s 重拉、退出码 78(大脑配置错误)不再重启,与自带后台守护进程一致。\\n\" +\n \" 开机自启(首次):执行 `pm2 startup`——Linux/macOS 按提示操作;Windows 用计划任务跑 `pm2 resurrect`。\",\n };\n}\n\n/** 停止某账号的 daemon(pm2 delete)。 */\nexport function pm2Stop(key: string): { ok: boolean; msg: string } {\n if (!pm2Available()) return { ok: false, msg: \"未检测到 pm2。\" };\n const name = `wemind-${key}`;\n const r = pm2Run(`delete ${name}`, { quiet: true });\n pm2Run(\"save\", { quiet: true });\n return r.status === 0\n ? { ok: true, msg: `已停止 ${name}。` }\n : { ok: false, msg: `${name} 未在运行,或停止失败。` };\n}\n","// 摘掉 VS Code 集成终端注入的调试引导——只用于**脱离式/常驻**的 spawn(见 watchdog-runner.ts)。\n//\n// VS Code 的「智能自动附加」会给终端里每个 node 进程注入两个变量:\n// NODE_OPTIONS=--require <…>/ms-vscode.js-debug/bootloader.js\n// VSCODE_INSPECTOR_OPTIONS={\"deferredMode\":true,\"autoAttachMode\":\"smart\",\"aaPatterns\":[\"<你的仓库>/**\",…],…}\n// 被注入的进程会**等调试器接上才执行主脚本**。终端里前台的命令 VS Code 会接上,所以看着一切正常;但 `wemind start` 脱离出去的后台守护进程/daemon 不会被接——进程活着却一行 JS 都不跑:`daemon.log` 不更新、`wrapper.pid` 不写、daemon 根本起不来,答复却说「已启动」(2026-10-29 实测)。\n//\n// 修法:脱离式 spawn 时把 VS Code 自己注入的那段 `--require` 摘掉,其余 `NODE_OPTIONS` 原样保留(用户可能设了 `--max-old-space-size` 之类);`VSCODE_INSPECTOR_OPTIONS` 一并删——它只对那个 bootloader 有意义。\n\n/** 匹配 VS Code 注入的 `--require <路径>/ms-vscode.js-debug/…/bootloader.js`(兼容 `--require=` 写法与带引号的路径)。 */\nconst VSCODE_REQUIRE =\n /--require(?:=|\\s+)(?:\"[^\"]*ms-vscode\\.js-debug[^\"]*bootloader\\.js\"|'[^']*ms-vscode\\.js-debug[^']*bootloader\\.js'|[^\\s\"']*ms-vscode\\.js-debug[^\\s\"']*bootloader\\.js)/gi;\n\n/** 返回摘掉 VS Code 调试引导的**新**环境对象(不修改入参;没有注入时原样返回一份拷贝)。 */\nexport function stripVscodeDebugEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const out: NodeJS.ProcessEnv = { ...env };\n const opts = out.NODE_OPTIONS;\n if (opts) {\n const next = opts.replace(VSCODE_REQUIRE, \" \");\n if (next !== opts) {\n // 只有真摘过东西才规整空白,其余情况保留用户原文(避免动到带空格的引号参数)。\n const kept = next.trim().replace(/\\s+/g, \" \");\n if (kept) out.NODE_OPTIONS = kept;\n else delete out.NODE_OPTIONS;\n }\n }\n delete out.VSCODE_INSPECTOR_OPTIONS;\n return out;\n}\n","// 自带后台守护进程(方案 b):`wemind start --runner builtin` 用它常驻,不依赖 pm2。\n// - runWatchdog:后台守护进程循环本体(由 `cli.js _watchdog` 调用),单实例守卫 + 循环 spawn daemon、崩溃 5s 重拉。\n// - watchdogStart:从 manage 脱离式启动后台守护进程。\n// 单实例用 wrapper.pid + isOurProcess() 身份校验(不只看 pid 存活,防重启后 pid 复用误判);竞态由「start 前先 stop」进一步压低。\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DATA_DIR, accountDir } from \"@wemind/wechat-ilink-core\";\nimport { BRAIN_CONFIG_EXIT_CODE } from \"./brain-agents.js\";\nimport { stripVscodeDebugEnv } from \"./debug-env.js\";\nimport { CLI_PID_MARKERS, isOurProcess } from \"./proc-identity.js\";\n\nconst RESTART_DELAY_MS = 5000;\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nfunction readPidFile(file: string): number | null {\n try {\n const n = Number.parseInt(fs.readFileSync(file, \"utf-8\").trim(), 10);\n return Number.isInteger(n) && n > 0 ? n : null;\n } catch {\n return null;\n }\n}\n\n/** 单实例守卫:已有存活**且确认是我们**的后台守护进程返回 false;否则写自己 pid、返回 true。 */\nfunction acquireSingleton(wrapperPidFile: string): boolean {\n const old = readPidFile(wrapperPidFile);\n // 身份校验:陈旧 pid + 重启后 pid 复用时会误判「已有实例」→ 后台守护进程直接退出、daemon 静默不起。\n if (old && isOurProcess(old, CLI_PID_MARKERS)) return false;\n fs.writeFileSync(wrapperPidFile, String(process.pid), \"ascii\");\n return true;\n}\n\n/** 后台守护进程循环本体(由 `cli.js _watchdog` 调用,常驻):单实例守卫 → 循环拉起 daemon、退出 5s 重拉。 */\nexport async function runWatchdog(): Promise<void> {\n const dataDir = DATA_DIR; // 本进程已设 WECHAT_ACCOUNT,core 据此解析到账号目录\n fs.mkdirSync(dataDir, { recursive: true });\n const log = path.join(dataDir, \"daemon.log\");\n const wrapperPidFile = path.join(dataDir, \"wrapper.pid\");\n\n if (!acquireSingleton(wrapperPidFile)) {\n fs.appendFileSync(\n log,\n `[wemind/wechat-daemon/wrapper] 已有实例在跑,本次退出。${new Date().toISOString()}\\n`,\n );\n return;\n }\n const cleanup = () => {\n try {\n if (readPidFile(wrapperPidFile) === process.pid) fs.unlinkSync(wrapperPidFile);\n } catch {\n /* ignore */\n }\n };\n process.on(\"exit\", cleanup);\n process.on(\"SIGINT\", () => {\n cleanup();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n cleanup();\n process.exit(0);\n });\n\n const cliPath = process.argv[1]; // 当前 cli.js(本进程即 `node cli.js _watchdog`)\n for (;;) {\n fs.appendFileSync(\n log,\n `[wemind/wechat-daemon/wrapper] start daemon ${new Date().toISOString()}\\n`,\n );\n const logFd = fs.openSync(log, \"a\");\n const child: ChildProcess = spawn(process.execPath, [cliPath, \"daemon\"], {\n // WECHAT_ACCOUNT 已在本进程环境中;同时摘掉 VS Code 终端注入的调试引导(后台守护进程若本身被注入过,daemon 不能再被带坏,见 debug-env.ts)\n env: stripVscodeDebugEnv(process.env),\n stdio: [\"ignore\", logFd, logFd], // stdout/stderr 都并入 daemon.log(与 run-daemon.ps1 一致)\n windowsHide: true, // 不弹出控制台窗口(Windows):日志已重定向到 daemon.log,无需可见窗口\n });\n const code = await new Promise<number>((resolve) => {\n child.on(\"exit\", (c) => resolve(c ?? -1));\n child.on(\"error\", () => resolve(-1));\n });\n try {\n fs.closeSync(logFd);\n } catch {\n /* ignore */\n }\n if (code === BRAIN_CONFIG_EXIT_CODE) {\n // 大脑配置错误:重拉无益(需人工修),停在后台守护进程循环外,避免每 5s 刷一条。\n fs.appendFileSync(\n log,\n `[wemind/wechat-daemon/wrapper] 大脑配置错误(exit ${code}),已停止自动重启。修好后执行:wemind start <账号>\\n`,\n );\n return;\n }\n fs.appendFileSync(\n log,\n `[wemind/wechat-daemon/wrapper] daemon exited (code=${code}), restart in 5s ${new Date().toISOString()}\\n`,\n );\n await sleep(RESTART_DELAY_MS);\n }\n}\n\n/** 从 manage 脱离式启动某账号的后台守护进程(方案 b:builtin runner)。 */\nexport function watchdogStart(key: string, cliPath: string): { ok: boolean; msg: string } {\n const child = spawn(process.execPath, [cliPath, \"_watchdog\"], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: true,\n // 脱离出的后台守护进程拿不到 VS Code 的调试器,带着 bootloader 注入会被「等调试器」卡死 → 摘掉(见 debug-env.ts)\n env: stripVscodeDebugEnv({ ...process.env, WECHAT_ACCOUNT: key }),\n });\n child.unref();\n return {\n ok: true,\n msg: `已启动自带后台守护进程(账号 ${key},pid ${child.pid})。日志:${path.join(accountDir(key), \"daemon.log\")}`,\n };\n}\n","import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { agentEnv, authHint, type ResolvedAgent } from \"./brain-agents.js\";\nimport { copyProfilePatch } from \"./profile-model.js\";\nimport { spawnAgent } from \"./spawn-agent.js\";\n\n/**\n * 「大脑连通性自检」:真正起一个 ACP agent 进程并跑完 initialize / session/new 握手,用来证明「这个大脑不只是找得到,而且能说 ACP」——比单纯「文件存在」更硬的证据。\n *\n * 用**临时沙箱目录**(避免污染真实配置与账号沙箱),结束后杀进程、删临时目录。\n * 供 `wemind brain check` 使用;不影响正在运行的 daemon(各起各的进程)。\n */\nexport type CheckResult = { ok: boolean; detail: string; ms: number };\n\nexport async function checkAgent(ag: ResolvedAgent, timeoutMs = 60_000): Promise<CheckResult> {\n const started = Date.now();\n const home = fs.mkdtempSync(path.join(os.tmpdir(), \"wemind-brain-check-\"));\n const env = agentEnv(ag, home); // 父进程 env → 临时沙箱目录 → 预设 env → 预设 envDefaults(与 daemon 的 spawn 口径一致)\n\n // 需要登录的 agent(DSH / Qoder)缺凭据会在 session/new 报认证失败——把凭据带进临时沙箱,否则自检会把「没登录」误报成「二进制不可用」。标识类文件(如 Qoder 的 machine_id)必须一致。\n const cfgDir = path.join(home, ag.configSubdir);\n for (const f of [...ag.credentialFiles, ...ag.identityFiles]) {\n try {\n const src = path.join(ag.realHome ?? \"\", f);\n if (!ag.realHome || !fs.existsSync(src)) continue;\n fs.mkdirSync(path.dirname(path.join(cfgDir, f)), { recursive: true });\n fs.copyFileSync(src, path.join(cfgDir, f));\n } catch {\n /* best-effort */\n }\n }\n\n // 与 daemon 的大脑沙箱对齐:配了 realProfile 的 agent(DSH)启动时就解析 profiles/<名>/cordis.patch.yml,\n // 语法错误会直接 exit 1(initialize 都不会发生)。不复制的话,坏 profile 上自检仍报 ✅,而 daemon 每条消息都起不来。\n if (ag.inherit && ag.realProfile && ag.realHome) {\n try {\n const rel = path.join(\"profiles\", ag.realProfile, \"cordis.patch.yml\");\n if (fs.existsSync(path.join(ag.realHome, rel)))\n copyProfilePatch(ag.realHome, ag.realProfile, home);\n } catch {\n /* best-effort(与 daemon 的 syncProfilePatch 一致) */\n }\n }\n\n const proc = spawnAgent(ag, { cwd: home, env });\n\n let buf = \"\";\n let seq = 0;\n const pending = new Map<number, (frame: any) => void>();\n const settleAll = (msg: string): void => {\n for (const [id, done] of pending) {\n pending.delete(id);\n done({ id, error: { message: msg } });\n }\n };\n\n proc.stdout!.on(\"data\", (d) => {\n buf += d.toString();\n let i: number;\n while ((i = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, i).trim();\n buf = buf.slice(i + 1);\n if (!line) continue;\n let frame: any;\n try {\n frame = JSON.parse(line);\n } catch {\n continue;\n }\n if (typeof frame.id === \"number\" && pending.has(frame.id)) {\n const done = pending.get(frame.id)!;\n pending.delete(frame.id);\n done(frame);\n }\n }\n });\n const stderrTail: string[] = [];\n proc.stderr!.on(\"data\", (d) => {\n stderrTail.push(d.toString());\n if (stderrTail.length > 20) stderrTail.shift();\n });\n proc.on(\"error\", (e) => settleAll(`无法启动:${String(e)}`));\n proc.on(\"exit\", (code) => settleAll(`进程已退出(code=${code})`));\n\n const req = (method: string, params: unknown): Promise<any> =>\n new Promise((resolve, reject) => {\n const id = ++seq;\n const t = setTimeout(() => {\n pending.delete(id);\n reject(new Error(`${method} 超时(${timeoutMs}ms)`));\n }, timeoutMs);\n pending.set(id, (frame) => {\n clearTimeout(t);\n if (frame?.error)\n reject(\n new Error(typeof frame.error === \"string\" ? frame.error : JSON.stringify(frame.error)),\n );\n else resolve(frame?.result);\n });\n proc.stdin!.write(JSON.stringify({ jsonrpc: \"2.0\", id, method, params }) + \"\\n\");\n });\n\n try {\n await req(\"initialize\", { protocolVersion: 1, clientCapabilities: {} });\n const s = (await req(\"session/new\", { cwd: home, mcpServers: [] })) as { sessionId?: string };\n // 预设声明了 sessionMode(免审批)就一并验证:不发这一步的话,模式不可用(如 claude-code 以 root 跑且没设 IS_SANDBOX=1)在这里永远显示 ✅\n let modeNote = \"\";\n if (ag.sessionMode && s?.sessionId) {\n try {\n await req(\"session/set_mode\", { sessionId: s.sessionId, modeId: ag.sessionMode });\n modeNote = `,mode=${ag.sessionMode}`;\n } catch (e) {\n modeNote = `,但 session/set_mode ${ag.sessionMode} 失败(${e instanceof Error ? e.message : String(e)})——免审批模式可能不可用,回合仍可跑但会按 agent 自己的审批流程走`;\n }\n }\n return {\n ok: true,\n detail: `ACP 握手成功(initialize + session/new,sessionId=${s?.sessionId ?? \"?\"}${modeNote})`,\n ms: Date.now() - started,\n };\n } catch (e) {\n // 只留末尾会把「Error: dsh: failed to parse overlay … YAMLException」这种头行切掉(栈尾看不出原因),\n // 所以同时保留第一行错误信息与末尾上下文。\n const all = stderrTail.join(\"\").trim();\n const lines = all\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter(Boolean);\n const head =\n lines.find((l) => /^(Error|YAMLException)\\b/.test(l)) ??\n lines.find((l) => /failed to parse|YAMLException/i.test(l)) ??\n lines[0];\n const msg = e instanceof Error ? e.message : String(e);\n const hint = authHint(ag.name, msg);\n const tail = all.slice(-400);\n const detail = `${msg}${head ? `\\n stderr: ${head}` : \"\"}${tail && !head?.includes(tail.trim()) ? `\\n …${tail}` : \"\"}${hint ? `\\n 提示:${hint}` : \"\"}`;\n return {\n ok: false,\n detail,\n ms: Date.now() - started,\n };\n } finally {\n try {\n if (process.platform === \"win32\" && proc.pid)\n spawnSync(\"taskkill\", [\"/PID\", String(proc.pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true,\n });\n else proc.kill();\n } catch {\n /* ignore */\n }\n try {\n fs.rmSync(home, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n }\n}\n","import { readOwnVersion } from \"@wemind/wechat-ilink-core\";\n\n/**\n * CLI 版本号与包身份(`wemind version` / `wemind --version` / help 头部)。\n *\n * **版本号唯一来源是 `package.json`**,这里在运行时读它,不再手写常量。\n * 为什么:以前版本号散落在 package.json 和本文件两处,`npm version` 只会改前者,漏改本文件会导致「发了 1.2.0、`wemind version` 还报 1.1.0」这种极难排查的不一致。\n * 现在 `npm run release:patch|minor|major` 一步到位,本文件自动跟着变。\n *\n * 查找逻辑在 `@wemind/wechat-ilink-core` 的 `readOwnVersion()`(与 MCP 包共用一份):\n * 必须把**本文件的** `import.meta.url` 传进去,否则会命中的是 core 自己的 package.json。\n */\n\n/** 期望的 npm 包名。这是**独立常量**,故意不从 package.json 读——它是校验基准。 */\nexport const CLI_PKG_NAME = \"@wemind/cli\";\n\n/**\n * 包版本。正常情况下等于 `package.json` 的 `version`;若查找失败(不该发生),退化为可识别的占位值,单测会把它当失败信号。\n */\nexport const CLI_VERSION = readOwnVersion(import.meta.url, CLI_PKG_NAME) ?? \"0.0.0-unknown\";\n\n/** 单行版本串(供 --version 输出):命令名 + 版本,包名放括号里便于反馈问题时对上 npm 页面。 */\nexport function versionLine(): string {\n return `wemind ${CLI_VERSION} (${CLI_PKG_NAME}, node ${process.versions.node}, ${process.platform}-${process.arch})`;\n}\n","#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n WEMIND_ROOT,\n accountDir,\n deriveAccountKey,\n listAccounts,\n registerAccount,\n unregisterAccount,\n setLabel,\n getDefaultKey,\n setDefaultKey,\n fetchQRCode,\n pollQRStatus,\n saveQrState,\n saveAccountTo,\n loadAccount as readAccount,\n clearSessionPause,\n notifyStart,\n notifyStop,\n addAllowFrom,\n removeAllowFrom,\n readAllowFrom,\n} from \"@wemind/wechat-ilink-core\";\nimport { pm2Start, pm2Stop } from \"./pm2-runner.js\";\nimport { watchdogStart } from \"./watchdog-runner.js\";\nimport {\n CLI_PID_MARKERS,\n brainPidMarkers,\n commandLines,\n isAlive,\n isOurProcess,\n matchesMarkers,\n} from \"./proc-identity.js\";\nimport {\n ACP_AGENT_PRESETS,\n AUTO_PROBE_ORDER,\n resolveAgent,\n agentProblem,\n presetAvailable,\n acpArgsWarning,\n type ResolvedAgent,\n} from \"./brain-agents.js\";\nimport {\n loadBrainConfig,\n updateBrainConfig,\n clearBrainConfig,\n brainConfigPath,\n type BrainConfig,\n} from \"./brain-config.js\";\nimport { checkAgent } from \"./brain-check.js\";\nimport { acpOverrideHint } from \"./profile-model.js\";\nimport { readPidRecords, recordMarkers } from \"./pid-record.js\";\nimport { BRAIN_RUNTIME_FILE, readBrainState, type BrainRuntime } from \"./brain-state.js\";\nimport { CLI_VERSION, versionLine } from \"./version.js\";\nimport { renderHelp } from \"./chat-commands.js\";\nimport {\n awaitControlResponse,\n bridgePidOf,\n controlTimeoutMs,\n newRequestId,\n writeControlRequest,\n type ControlKind,\n type ControlRequest,\n} from \"./control-bridge.js\";\nimport {\n currentSessionIdOf,\n deleteSession,\n listSessions,\n renderSessionList,\n renameSession,\n} from \"./session-registry.js\";\nimport { clearSessionPointer, brainSessionMapPath, sessionKey } from \"./session-map.js\";\nimport { awaitTaskAck, writeTaskCmd as writeTaskCommand } from \"./task-inbox.js\";\n\n/**\n * WeMind 多账号管理 CLI。\n * 账号 key = 由微信 botId 派生的唯一标识;每个账号独立目录 ~/.wemind/<key>。\n * label = 人类可读别名(可改、可重复);管理命令可用 key 或 label 选择账号。\n * 推荐用 `login` 一条命令完成:自刷新二维码 → 扫码 → 自动登记(→ 可选自动启动)。\n */\n\nconst BASE = process.env.WEMIND_ILINK_BASE?.trim() || \"https://ilinkai.weixin.qq.com\";\nconst STAGING = path.join(WEMIND_ROOT, \".staging\");\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nfunction readPid(dir: string, name: string): number | null {\n try {\n const n = parseInt(fs.readFileSync(path.join(dir, name), \"utf-8\").trim(), 10);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * 大脑进程的身份记录(`brain.pid` 现在是 JSON,见 pid-record.ts):首条非 legacy 记录。\n * 记录里存的是**起进程时真实用的** bin/args —— 比「按当前配置猜」可靠:\n * `wemind brain set X` 之后、热切换窗口内、以及 brain≠worker 时,配置值都可能与实际在跑的进程不一致。\n * (导出仅供单测。)\n */\nexport function brainRecord(dir: string): {\n pid: number | null;\n markers: string[][] | null;\n agent: string;\n} {\n const rec = readPidRecords(path.join(dir, \"brain.pid\"))[0];\n if (!rec) return { pid: null, markers: null, agent: \"\" };\n const markers = recordMarkers(rec);\n return { pid: rec.pid, markers: markers.length ? markers : null, agent: rec.agent };\n}\n/**\n * 该 pid 是否「活着**且确实是我们自己的** CLI 进程」。\n * 只判存活不够:pid 会复用(尤其重启后),陈旧 pid 文件可能指向**别人的**进程 → 会误杀/误判。\n */\nfunction aliveOurs(pid: number | null, markers: string[][] = CLI_PID_MARKERS): boolean {\n return isOurProcess(pid, markers);\n}\n\n/** 阻塞式 sleep(CLI 是同步流程;Atomics.wait 不占 CPU、不比 spawn 一个进程贵)。 */\nfunction sleepSync(ms: number): void {\n try {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n } catch {\n /* ignore */\n }\n}\n\n/**\n * 某 pid 的全部后代(POSIX)。Windows 不需要——`taskkill /T` 自己会走进程树。\n * 用 `ps -A -o pid=,ppid=` 一次取全表再在内存里建树,比逐层 `pgrep -P` 少起进程。\n * (导出仅供单测;CLI 内部不对外。)\n */\nexport function descendantPids(pid: number): number[] {\n try {\n // procps(Linux)与 BSD/macOS 的 ps 都认 `-A`;个别精简环境(BusyBox)只有 `-e`,故兜底一次。\n const psArgs = [\n [\"-A\", \"-o\", \"pid=,ppid=\"],\n [\"-e\", \"-o\", \"pid=,ppid=\"],\n ];\n let stdout = \"\";\n for (const args of psArgs) {\n const r = spawnSync(\"ps\", args, { encoding: \"utf8\", timeout: 5000 });\n if (r.status === 0 && r.stdout) {\n stdout = r.stdout;\n break;\n }\n }\n const childrenOf = new Map<number, number[]>();\n for (const line of stdout.split(/\\r?\\n/)) {\n const m = line.trim().match(/^(\\d+)\\s+(\\d+)$/);\n if (!m) continue;\n const child = Number(m[1]);\n const parent = Number(m[2]);\n const bucket = childrenOf.get(parent);\n if (bucket) bucket.push(child);\n else childrenOf.set(parent, [child]);\n }\n const out: number[] = [];\n const walk = (p: number): void => {\n for (const c of childrenOf.get(p) ?? []) {\n out.push(c);\n walk(c);\n }\n };\n walk(pid);\n return out;\n } catch {\n return [];\n }\n}\n\n/**\n * 杀「pid 及其整棵进程树」。\n *\n * Windows:`taskkill /T /F` 本来就连带子树。\n * POSIX:`process.kill(pid)` **只**结束该进程,不动它的子进程——daemon 被停后,它拉起的 ACP 大脑 / worker 会留下来继续占着会话与文件句柄(Windows 的 `taskkill /T` 会连带子树,所以这个缺陷只在 POSIX 暴露)。\n * 所以这里先收集后代、由叶子往根 SIGTERM(daemon 的 SIGTERM 处理器会自己清 pid 文件),等一小会儿再对仍存活的补 SIGKILL。\n */\nfunction killProcessTrees(pids: number[]): void {\n const unique = [...new Set(pids.filter((p) => isAlive(p)))];\n if (!unique.length) return;\n if (process.platform === \"win32\") {\n for (const pid of unique)\n spawnSync(\"taskkill\", [\"/PID\", String(pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n return;\n }\n // 先收集所有后代(按叶子优先排序:后出现的更深,反过来就是叶子先)\n const targets: number[] = [];\n for (const pid of unique) targets.push(...descendantPids(pid).reverse(), pid);\n const all = [...new Set(targets)].filter((p) => isAlive(p));\n for (const pid of all) {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n /* ignore */\n }\n }\n sleepSync(800); // 给 SIGTERM 处理器一点时间(daemon 会清 pid 文件、关子进程)\n for (const pid of all) {\n if (!isAlive(pid)) continue;\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n /* ignore */\n }\n }\n}\nfunction openFile(p: string): void {\n try {\n if (process.platform === \"win32\")\n spawn(\"cmd\", [\"/c\", \"start\", \"\", p], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: true,\n }).unref();\n else\n spawn(process.platform === \"darwin\" ? \"open\" : \"xdg-open\", [p], {\n detached: true,\n stdio: \"ignore\",\n }).unref();\n } catch {\n /* ignore */\n }\n}\n\n/** key 或 label → key(label 唯一匹配才行)。 */\nfunction resolveKey(arg: string): string {\n if (!arg) throw new Error(\"需要 <key 或 label>\");\n const accs = listAccounts();\n if (accs.some((a) => a.key === arg)) return arg;\n const byLabel = accs.filter((a) => a.label === arg);\n if (byLabel.length === 1) return byLabel[0].key;\n if (byLabel.length > 1)\n throw new Error(\n `label「${arg}」对应多个账号,请改用 key:${byLabel.map((a) => a.key).join(\", \")}`,\n );\n throw new Error(`未找到账号:${arg}(既不是 key 也不是 label)`);\n}\n\n/** 启动某账号的 daemon(方案 a:委托 pm2 常驻,跑 `wemind daemon`,注入 WECHAT_ACCOUNT)。 */\nfunction startDaemon(key: string, runner?: string): void {\n // 用 process.argv[1](即正在执行的 cli.js 绝对路径),而不是 dirname(import.meta.url):\n // 发布后 tsdown 把 manage.ts 内联进 dist/cli.js,两者恰好相同;但若将来拆成多 chunk,argv[1] 才是「用户装的那个 bin」,dirname(import.meta.url) 可能是 dist/chunks/ 下。\n const cliPath =\n process.argv[1] || path.join(path.dirname(fileURLToPath(import.meta.url)), \"cli.js\");\n // start 前先停现有任何 runner(pm2 app / builtin 后台守护进程),避免同账号双跑抢 token\n stopDaemon(key);\n const useRunner = (runner || process.env.WEMIND_RUNNER || \"builtin\").toLowerCase();\n console.log((useRunner === \"pm2\" ? pm2Start(key, cliPath) : watchdogStart(key, cliPath)).msg);\n}\n\n/**\n * 停止某账号的常驻:先 pm2 delete(方案 a),再按 pid 兜底杀 wrapper/daemon/brain/worker(兼容直接启动的)。停的是**整条链**:除列出的进程外,还连带它们的子进程树。\n */\nfunction stopDaemon(key: string): string {\n pm2Stop(key); // best-effort:停掉 pm2 托管的 app(若有)\n const dir = accountDir(key);\n const ag = resolveAgent(loadBrainConfig(dir));\n // brain:优先用身份记录里「起进程时真实用的」bin/args 算特征串;没有记录(旧账号/未起过)才回退配置。\n const brec = brainRecord(dir);\n const brainMarkers = brec.markers ?? brainPidMarkers(ag.bin, ag.args);\n const cand: Array<{ pid: number | null; name: string; markers: string[][] }> = [\n { pid: readPid(dir, \"wrapper.pid\"), name: \"wrapper\", markers: CLI_PID_MARKERS },\n { pid: readPid(dir, \"daemon.pid\"), name: \"daemon\", markers: CLI_PID_MARKERS },\n { pid: brec.pid ?? readPid(dir, \"brain.pid\"), name: \"brain\", markers: brainMarkers },\n ];\n // worker:现在可能**多条并存**(多任务/多 agent),身份记录 `workers.pid.json` 里每条都带着起进程时真实用的 bin/args\n // → 逐条按自己的特征串判定。旧账号只有裸整数 `worker.pid`(无 bin/args)→ 回退「按配置算特征串」的老办法。\n const wrecs = readPidRecords(path.join(dir, \"workers.pid.json\"));\n if (wrecs.length) {\n for (const w of wrecs)\n cand.push({\n pid: w.pid,\n name: `worker${w.taskId ? `#${w.taskId}` : \"\"}(${w.agent || \"?\"})`,\n markers: recordMarkers(w),\n });\n } else {\n cand.push({\n pid: readPid(dir, \"worker.pid\"),\n name: \"worker\",\n markers: brainPidMarkers(ag.bin, ag.args),\n });\n }\n // 只杀「活着**且身份确认**」的:pid 会被复用,陈旧 pid 文件可能指向别人的进程 → 绝不误杀。\n // 命令行一次批量查(Windows 下较慢);正常情况(陈旧 pid 早已不存在)根本不会发起查询。\n const lines = commandLines(cand.map((c) => c.pid));\n const stopped: string[] = [];\n const skipped: string[] = [];\n const toKill: number[] = [];\n const seen = new Set<number>();\n for (const c of cand) {\n if (!c.pid || seen.has(c.pid) || !isAlive(c.pid)) continue;\n seen.add(c.pid);\n if (matchesMarkers(lines.get(c.pid), c.markers)) {\n toKill.push(c.pid);\n stopped.push(`${c.name} ${c.pid}`);\n } else {\n skipped.push(`${c.name} ${c.pid}`); // 活着的 pid 但不是我们的(pid 复用)→ 不杀\n }\n }\n // 身份校验都通过了才动手:一次性杀掉这些进程**及其子进程树**(见 killProcessTrees 注释)。\n killProcessTrees(toKill);\n for (const f of [\n \"wrapper.pid\",\n \"daemon.pid\",\n \"brain.pid\",\n \"worker.pid\",\n \"workers.pid.json\",\n BRAIN_RUNTIME_FILE,\n ]) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n const parts: string[] = [];\n if (stopped.length) parts.push(`已停止 ${stopped.join(\" + \")}`);\n if (skipped.length)\n parts.push(`跳过 ${skipped.join(\" + \")}(非本程序,未杀;已清陈旧 pid 文件)`);\n return parts.length ? parts.join(\";\") : \"无运行中的进程\";\n}\n\n/** 把 STAGING 的登录按 botId 收进账号目录 + 登记。 */\nfunction finalizeStaging(label?: string): { key: string; msg: string } {\n const acc = readAccount(STAGING);\n if (!acc?.botId) throw new Error(`staging 无有效登录(${STAGING})`);\n const key = deriveAccountKey(acc.botId);\n const dest = accountDir(key);\n if (fs.existsSync(dest)) {\n fs.rmSync(STAGING, { recursive: true, force: true });\n throw new Error(`账号目录已存在: ${dest}(该微信号此前已登录过;如需重登请先 delete ${key})`);\n }\n fs.renameSync(STAGING, dest);\n for (const f of [\n \"qrcode.png\",\n \"qrcode.txt\",\n \"qr_login.json\",\n \"poll.out\",\n \"poll.err\",\n \"auto.out\",\n \"auto.err\",\n ]) {\n try {\n fs.unlinkSync(path.join(dest, f));\n } catch {\n /* ignore */\n }\n }\n const meta = registerAccount({\n key,\n botId: acc.botId,\n userId: acc.userId,\n baseUrl: acc.baseUrl,\n label: label || key,\n });\n if (acc.userId) addAllowFrom(acc.userId, dest); // 配对:扫码登录者自动授权\n return { key, msg: `已登录:key=${key} label=${meta.label} botId=${acc.botId}\\n目录:${dest}` };\n}\n\n/**\n * 懒加载 `qrcode`:只有扫码登录要画二维码,`list` / `brain` / `task` / `daemon` 全用不上。\n * 顶层 `import` 会让**每次**启动都付约 36ms(实测)解析 qrcode + yargs 依赖树;这里在第一次画码时才加载,之后同进程内复用(登录过程会因过期刷新多次)。\n * 用函数声明而非 `const QRCode = await import(...)`:后者在 ESM 顶层要多一层 TDZ 心智负担。\n */\nlet qrcodeMod: typeof import(\"qrcode\") | undefined;\n\nasync function loadQrCode(): Promise<typeof import(\"qrcode\")> {\n if (!qrcodeMod) {\n try {\n qrcodeMod = await import(\"qrcode\");\n } catch (e) {\n throw new Error(\n `无法加载二维码库 qrcode(依赖缺失或安装损坏):${e instanceof Error ? e.message : String(e)}\\n` +\n \"请重试安装:npm i -g @wemind/cli\",\n );\n }\n }\n return qrcodeMod;\n}\n\n/** 一条命令完成扫码登录:自刷新二维码 + 终端码 + 自动开图 + 自动登记(→ 可选自动启动)。 */\nasync function cmdLogin(\n label: string | undefined,\n autoStart: boolean,\n runner?: string,\n): Promise<void> {\n const QRCode = await loadQrCode(); // 只有登录路径才加载 qrcode\n fs.rmSync(STAGING, { recursive: true, force: true });\n fs.mkdirSync(STAGING, { recursive: true });\n const png = path.join(STAGING, \"qrcode.png\");\n const overall = Date.now() + 15 * 60 * 1000;\n let code = \"\";\n let opened = false;\n\n const refresh = async (): Promise<void> => {\n const qr = await fetchQRCode(BASE);\n code = qr.qrcode;\n saveQrState({ qrcode: qr.qrcode, qrcodeUrl: qr.qrcode_img_content, createdAt: Date.now() });\n await QRCode.toFile(png, qr.qrcode_img_content, { width: 480, margin: 2 });\n const term = await QRCode.toString(qr.qrcode_img_content, { type: \"terminal\", small: true });\n console.log(\"\\n用要登录的微信扫下面的二维码(过期会自动刷新):\\n\");\n console.log(term);\n console.log(\"图片(已自动打开): \" + png);\n console.log(\"链接: \" + qr.qrcode_img_content + \"\\n\");\n if (!opened) {\n openFile(png);\n opened = true;\n }\n };\n\n await refresh();\n console.log(\"等待扫码确认…(最多 15 分钟,Ctrl+C 取消)\");\n while (Date.now() < overall) {\n let s;\n try {\n s = await pollQRStatus(BASE, code);\n } catch {\n await sleep(2000);\n continue;\n }\n if (s.status === \"confirmed\") {\n if (!s.bot_token || !s.ilink_bot_id || !s.ilink_user_id)\n throw new Error(\"confirmed 但数据不全\");\n // 走 core 的 saveAccountTo:POSIX 600 + Windows 一次性明文告警。\n // 之前这里是裸 writeFileSync,导致「凭证受权限保护」的安全承诺在 CLI 登录路径上不成立。\n saveAccountTo(STAGING, {\n botToken: s.bot_token,\n botId: s.ilink_bot_id,\n userId: s.ilink_user_id,\n baseUrl: s.baseurl ?? BASE,\n savedAt: Date.now(),\n });\n clearSessionPause(s.ilink_bot_id);\n await notifyStart({ baseUrl: s.baseurl ?? BASE, token: s.bot_token }).catch(() => {});\n for (const f of [\"qrcode.png\", \"qr_login.json\"]) {\n try {\n fs.unlinkSync(path.join(STAGING, f));\n } catch {\n /* ignore */\n }\n }\n console.log(\"\\n登录成功,正在登记…\");\n const { key, msg } = finalizeStaging(label);\n console.log(msg);\n if (autoStart) {\n startDaemon(key, runner);\n console.log(`已启动 daemon(账号 ${key})。`);\n } else {\n console.log(`启动:wemind start ${key}`);\n }\n return;\n }\n if (s.status === \"expired\") {\n console.log(\"二维码过期,自动刷新…\");\n await refresh();\n continue;\n }\n await sleep(1500);\n }\n throw new Error(\"超时(15 分钟)未完成登录\");\n}\n\nfunction cmdList(): string {\n const def = getDefaultKey();\n const accs = listAccounts();\n if (!accs.length) return \"(无已登记账号)。用 `login` 添加。\";\n // 先收集各账号的 pid + 大脑特征,再**一次性**批量查命令行(Windows 下每次查询较慢);只凭「pid 活着」显示状态会在重启后 pid 复用时骗人,所以要带身份校验。\n const entries = accs.map((a) => {\n const dir = accountDir(a.key);\n const ag = resolveAgent(loadBrainConfig(dir));\n const brec = brainRecord(dir);\n return {\n a,\n dir,\n dpid: readPid(dir, \"daemon.pid\"),\n bpid: brec.pid ?? readPid(dir, \"brain.pid\"),\n // 身份记录里是「起进程时真实用的」bin/args,比配置值可靠(brain set 未重启 / 热切换窗口 / brain≠worker)\n brainMarkers: brec.markers ?? brainPidMarkers(ag.bin, ag.args),\n brainAgent: brec.agent,\n // daemon 启动/热切换后写入的「实际生效大脑」;只在 daemon 活着时采信(下面按 dpid 判)\n brainRuntime: readBrainState(dir),\n // 多 task worker:`workers.pid.json` 是记录数组(每条一个 agent/任务),逐条按自己的身份校验\n workers: readPidRecords(path.join(dir, \"workers.pid.json\")).map((w) => ({\n pid: w.pid,\n agent: w.agent,\n state: w.state,\n taskId: w.taskId,\n markers: recordMarkers(w),\n })),\n };\n });\n const lines = commandLines(\n entries.flatMap((e) => [e.dpid, e.bpid, ...e.workers.map((w) => w.pid)]),\n );\n const aliveOursIn = (pid: number | null, markers: string[][]): boolean =>\n !!pid && matchesMarkers(lines.get(pid), markers);\n return entries\n .map((e) => {\n const runningNow = aliveOursIn(e.dpid, CLI_PID_MARKERS);\n const running = runningNow ? `运行中(pid ${e.dpid})` : \"已停止\";\n const brainPid = aliveOursIn(e.bpid, e.brainMarkers)\n ? ` brain(pid ${e.bpid}${e.brainAgent ? ` ${e.brainAgent}` : \"\"})`\n : \"\";\n const live = e.workers.filter((w) => aliveOursIn(w.pid, w.markers));\n const pool = live.length\n ? ` pool[${live.map((w) => `${w.agent}${w.taskId ? `#${w.taskId}` : \"\"}:${w.state}`).join(\", \")}]`\n : \"\";\n 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}`;\n })\n .join(\"\\n\");\n}\n\n// ===== 大脑(ACP agent)选择:账号级持久配置 brain.json,见 brain-config.ts =====\nconst BRAIN_SRC_LABEL: Record<ResolvedAgent[\"source\"], string> = {\n env: \"环境变量 WEMIND_BRAIN*\",\n file: \"账号 brain.json\",\n auto: \"自动探测(未配置)\",\n};\n\n/** list 用:账号「配置 / 自动探测」得到的大脑标签(不看当前 shell 的 env,避免误导)。 */\nfunction configAgentLabel(dir: string): string {\n const cfg = loadBrainConfig(dir);\n if (cfg?.agent && cfg.agent.toLowerCase() !== \"auto\") return cfg.agent;\n const picked = AUTO_PROBE_ORDER.find((k) => presetAvailable(k));\n return picked ? `${picked}(自动)` : \"(无可用)\";\n}\n\n/**\n * list 用的 agent 列:daemon **在跑**时优先显示它实际生效的大脑(`brain-runtime.json`,\n * 来自 daemon 自己的解析,含 `WEMIND_BRAIN` 覆盖/热切换结果);daemon 没跑时仍只显示配置/自动探测。\n * 运行时与配置不一致时把两者都写出来,避免「看着是 deepseek、实际在跑 cline」。\n */\nexport function agentLabel(dir: string, runtime: BrainRuntime | null = null): string {\n const cfg = configAgentLabel(dir);\n if (!runtime?.agent?.trim()) return cfg;\n const name = runtime.agent.trim();\n const cfgName = cfg.replace(/([^)]*)$/, \"\");\n if (name === cfgName) return cfg;\n return `${name}(运行,来源 ${runtime.source};配置 ${cfg})`;\n}\n\n/** `wemind brain`(show):打印某账号大脑的「配置」与「生效」情况。 */\nfunction describeBrain(key: string): string {\n const dir = accountDir(key);\n const cfg = loadBrainConfig(dir);\n const ag = resolveAgent(cfg);\n const problem = agentProblem(ag);\n const cfgLine = cfg\n ? `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 !== undefined ? ` shell=${cfg.shell}` : \"\"}${cfg.model ? ` model=${cfg.model}` : \"\"}`\n : \"(未设置 brain.json)\";\n const lines = [\n `账号 ${key}`,\n ` 配置:${cfgLine}`,\n ` 请求:${ag.requestedKey} 来源:${BRAIN_SRC_LABEL[ag.source]}`,\n ` 生效: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 ?? \"(无)\"}`,\n ` 可用:${problem ? `❌ ${problem}` : `✅ ${ag.binPath ?? ag.bin}`}`,\n ` 可选 agent:${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}`,\n ` 自动探测顺序:${AUTO_PROBE_ORDER.join(\" → \")}`,\n ];\n // D:整段替换 args 后漏了 ACP 模式参数时提醒(只是提醒;运行时另有握手失败的诊断提示)\n const argsWarn = acpArgsWarning(ag.name, ag.args, ag.binOverridden);\n if (argsWarn) lines.push(` ⚠️ ${argsWarn}`);\n // 用户层 profile 缺 `- id: acp` 时给出「你自己补上」的提示(WeMind 只原样同步配置,不替 agent 合成)\n const hint =\n ag.realProfile && ag.realHome && ag.inherit\n ? acpOverrideHint(ag.realHome, ag.realProfile)\n : null;\n if (hint) lines.push(\"\", hint);\n return lines.join(\"\\n\");\n}\n\n/** `wemind brain set <agent 名|auto>`:把选择按账号写入磁盘(可选 --bin/--args/--extra-args/--shell/--model;--restart 立即重启 daemon)。 */\nasync function cmdBrainSet(\n agentName: string,\n key: string,\n opts: {\n bin?: string;\n args?: string;\n extraArgs?: string;\n shell?: boolean;\n model?: string;\n restart: boolean;\n },\n): Promise<string> {\n const agent = agentName.trim().toLowerCase();\n if (agent !== \"auto\" && !ACP_AGENT_PRESETS[agent])\n throw new Error(\n `未知 agent「${agentName}」。可选:auto、${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}(自定义二进制用 --bin 搭配任一 agent)`,\n );\n if (opts.model !== undefined) {\n if (!opts.model.trim()) throw new Error(\"--model 缺少参数值(清空/跟随默认请写 --model auto)\");\n // auto 只是「自动探测」,解析时会被替换成探测到的具体 agent:写进 presets.auto 永远读不到,\n // 顶层的 model 又会下发给探测出来的任意 agent(把 DSH 的模型值发给 opencode 之类)。\n if (agent === \"auto\")\n throw new Error(\n \"`auto` 是「自动探测」,不绑定具体 agent,不能配 --model;请指定具体 agent(如 brain set deepseek --model …)\",\n );\n }\n const dir = accountDir(key);\n const customized =\n opts.bin !== undefined ||\n opts.args !== undefined ||\n opts.extraArgs !== undefined ||\n opts.shell !== undefined ||\n opts.model !== undefined;\n // 只选 agent(没带自定义启动参数)+ 没要求重启时,写入磁盘和「热切换」其实是同一件事:daemon 在跑且认桥就交给它一次做完——\n // 它先启动新大脑、成功才写 brain.json(失败退回,盘上不留半截结果),这比「写入磁盘 + 等下次重启」对用户更实在(决策⑪)。\n // 带 --bin/--args/--shell/--model 则**不能**当场应用:控制桥只接受 agent 名(见 control-bridge.ts 的安全边界)。\n if (!opts.restart && !customized && bridgeReady(key)) {\n const r = await cmdBrainSwitch(agent, key, false);\n // 热切换失败时**没有写入磁盘**(持久化放最后,见 brain-switch.ts):把这点说清楚,否则用户会以为 brain.json 已经改了。\n return r.startsWith(\"❌\")\n ? `${r}\\n(brain.json **没有被改写**:换大脑失败就不写入磁盘——这是「失败就退回原来那个」的代价。想强制写入再重启:wemind brain set ${agent} --restart)`\n : r;\n }\n const patch: BrainConfig = { agent };\n if (opts.bin) patch.bin = opts.bin;\n if (opts.args !== undefined) patch.args = opts.args.trim() ? opts.args.trim().split(/\\s+/) : [];\n if (opts.extraArgs !== undefined)\n patch.extraArgs = opts.extraArgs.trim() ? opts.extraArgs.trim().split(/\\s+/) : [];\n if (opts.shell !== undefined) patch.shell = opts.shell;\n if (opts.model !== undefined) patch.model = opts.model.trim();\n // 读-改-写:保留 worker 段(任务侧独立选择)与 presets 段(按 agent 名存的启动覆盖),旧 agent 的覆盖自动迁移。\n const before = loadBrainConfig(dir);\n const cfg = updateBrainConfig(patch, dir);\n const lines = [\n `已设置 ${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}` : \"\"}`,\n `配置文件:${brainConfigPath(dir)}`,\n ];\n // D:整段替换 args 时检查 ACP 模式标记(只提醒,不阻止写入)\n if (opts.args !== undefined) {\n const binOverridden =\n opts.bin !== undefined ||\n !!before?.presets?.[agent]?.bin ||\n (before?.agent?.trim().toLowerCase() === agent && !!before?.bin);\n const warn = acpArgsWarning(agent, cfg.args ?? [], binOverridden);\n if (warn) lines.push(`⚠️ ${warn}`);\n }\n if (opts.restart) {\n const dpid = readPid(dir, \"daemon.pid\");\n if (aliveOurs(dpid)) {\n startDaemon(key);\n lines.push(`已重启 daemon(账号 ${key})。`);\n } else {\n lines.push(`daemon 未在运行,未重启(用 wemind start ${key} 启动)。`);\n }\n } else if (customized && bridgeReady(key)) {\n // --bin/--args/--extra-args/--model 的改动其实可以由 `brain switch <同名 agent>` 当场应用(sameAgent 按这几项判「是不是同一个」);--shell 不在判据里,只能重启。\n const viaSwitch =\n opts.bin !== undefined ||\n opts.args !== undefined ||\n opts.extraArgs !== undefined ||\n opts.model !== undefined;\n lines.push(\n `(带 --bin/--args/--extra-args/--shell/--model 的改动不能由 brain set 当场应用:控制桥只认 agent 名。重启后生效——wemind stop ${key} && wemind start ${key}${viaSwitch ? `;不想重启的话,接着执行 wemind brain switch ${agent} 也能当场应用(它按 name/bin/args/model 判断是不是同一个,改过就会真切换)` : \"\"})`,\n );\n } else if (daemonAlive(key)) {\n lines.push(\n `(daemon 在跑但不是当前版本(不认控制桥):盘已写好,重启一次才生效——wemind stop ${key} && wemind start ${key})`,\n );\n } else {\n lines.push(`(daemon 需重启才生效:wemind start ${key})`);\n }\n return lines.join(\"\\n\");\n}\n\n/** `wemind brain unset`:清除账号级配置,回退到自动探测/默认。 */\nfunction cmdBrainUnset(key: string): string {\n const ok = clearBrainConfig(accountDir(key));\n return ok\n ? `${key} 已清除 brain.json(回退到自动探测/默认)。daemon 需重启才生效。`\n : `${key} 未设置 brain.json(无需清除)。`;\n}\n\n/** `wemind brain check`:真起一个进程跑 ACP 握手,验证大脑「不仅找得到、而且能说 ACP」。 */\nasync function cmdBrainCheck(key: string): Promise<string> {\n const ag = resolveAgent(loadBrainConfig(accountDir(key)));\n const problem = agentProblem(ag);\n if (problem) return `❌ ${key} 的大脑不可用:${problem}`;\n const r = await checkAgent(ag);\n return `${r.ok ? \"✅\" : \"❌\"} ${key} 大脑自检(${ag.name},bin=${ag.bin}):${r.detail}(${r.ms}ms)`;\n}\n\n/** brain 子命令派发:show(默认)/ set / unset / check。 */\nasync function cmdBrain(rest: string[]): Promise<string> {\n const argv = [...rest];\n const FLAGS = new Set([\n \"--bin\",\n \"--args\",\n \"--extra-args\",\n \"--shell\",\n \"--no-shell\",\n \"--model\",\n \"--restart\",\n ]);\n const takeFlag = (name: string): string | undefined => {\n // 同时认 `--model X` 与 `--model=X`:以前只按整 token 匹配 `--model`,等号写法会被当成未知开关静默丢掉(命令还报成功)。\n const eq = argv.find((t) => t.startsWith(`${name}=`));\n if (eq !== undefined) {\n const v = eq.slice(name.length + 1);\n if (!v) throw new Error(`${name} 缺少参数值`);\n argv.splice(argv.indexOf(eq), 1);\n return v;\n }\n const i = argv.indexOf(name);\n if (i < 0) return undefined;\n const v = argv[i + 1];\n // `--bin` 后面没值、或紧跟的是另一个开关(`brain set kiro --bin --restart`)时,以前会静默把 `--restart` 当成 bin 的值,看起来「设置成功」但存进去的是错的。\n // 值本身允许以 `--` 开头(如 `--args \"--experimental-acp\"`),所以只对已知开关报错。\n if (v === undefined || FLAGS.has(v)) throw new Error(`${name} 缺少参数值`);\n argv.splice(i, 2);\n return v;\n };\n const bin = takeFlag(\"--bin\");\n const argsOverride = takeFlag(\"--args\");\n const extraArgsOverride = takeFlag(\"--extra-args\");\n const model = takeFlag(\"--model\");\n const restart = argv.includes(\"--restart\");\n if (restart) argv.splice(argv.indexOf(\"--restart\"), 1);\n const shell = argv.includes(\"--shell\") ? true : argv.includes(\"--no-shell\") ? false : undefined;\n for (const f of [\"--shell\", \"--no-shell\"]) {\n const i = argv.indexOf(f);\n if (i >= 0) argv.splice(i, 1);\n }\n while (argv.includes(\"--worker\")) argv.splice(argv.indexOf(\"--worker\"), 1); // 只对 switch 有意义(用 rest 判断),不留成未知开关\n // 未知开关直接报错:以前一律被 pos 的过滤静默丢掉,`--modle` 这种拼错会「看起来设置成功」。\n const unknown = argv.filter((x) => x.startsWith(\"--\"));\n if (unknown.length)\n throw new Error(\n `未知参数:${unknown.join(\" \")}(可用:--bin --args --extra-args --shell --no-shell --model --restart)`,\n );\n const pos = argv.filter((x) => x && !x.startsWith(\"--\"));\n const first = (pos[0] || \"\").toLowerCase();\n const SUBCOMMANDS = new Set([\"show\", \"set\", \"unset\", \"check\", \"switch\", \"handoff\"]);\n if (first && !SUBCOMMANDS.has(first)) {\n // 省略子命令 → 视为 `brain show <key|label>`(如 `wemind brain 工作号`)。\n return describeBrain(pickKey(pos[0]));\n }\n const sub = first || \"show\";\n switch (sub) {\n case \"show\":\n return describeBrain(pickKey(pos[1]));\n case \"set\": {\n if (!pos[1])\n throw new Error(\n '用法: brain set <agent 名|auto> [--bin X] [--args \"…\"] [--extra-args \"…\"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]',\n );\n return await cmdBrainSet(pos[1], pickKey(pos[2]), {\n bin,\n args: argsOverride,\n extraArgs: extraArgsOverride,\n shell,\n model,\n restart,\n });\n }\n case \"switch\": {\n // 桥只接受**agent 名**(安全边界:见 control-bridge.ts)。带 --bin/--args/--shell/--model 就走 set(只写入磁盘;--bin/--args/--model 可随后用同名 switch 当场应用,--shell 只能重启)。\n if (\n bin !== undefined ||\n argsOverride !== undefined ||\n extraArgsOverride !== undefined ||\n shell !== undefined ||\n model !== undefined\n )\n throw new Error(\n \"`brain switch` 只接受 agent 名(换大脑是「换一个已装好的 agent」,不能带 --bin/--args/--extra-args/--model)。\\n\" +\n \"需要自定义二进制用 `brain set <agent 名> --bin …`(那是配置变更,只写入磁盘——之后 `wemind brain switch <同名 agent>` 可当场应用,或重启 daemon);追加参数用 `brain set <agent 名> --extra-args …`;换模型用 `brain set <agent 名> --model …`。\",\n );\n if (!pos[1]) throw new Error(\"用法: brain switch <agent 名|auto> [--worker] [<key|label>]\");\n const worker = rest.includes(\"--worker\");\n return await cmdBrainSwitch(pos[1], pickKey(pos[2]), worker);\n }\n case \"handoff\":\n return await cmdBrainHandoff(pickKey(pos[1]));\n case \"unset\":\n return cmdBrainUnset(pickKey(pos[1]));\n case \"check\":\n return await cmdBrainCheck(pickKey(pos[1]));\n default:\n throw new Error(\n `未知 brain 子命令「${sub}」(可用:show / set / switch / handoff / unset / check)`,\n );\n }\n}\n\nfunction cmdAdopt(key: string, label?: string): string {\n if (!key) throw new Error(\"用法: adopt <key> [--label 名]\");\n const acc = readAccount(accountDir(key));\n if (!acc?.botId) throw new Error(`${accountDir(key)} 无有效 account.json`);\n const meta = registerAccount({\n key,\n botId: acc.botId,\n userId: acc.userId,\n baseUrl: acc.baseUrl,\n label: label || key,\n });\n if (acc.userId) addAllowFrom(acc.userId, accountDir(key)); // 接管时把登录者种入白名单\n return `已登记:key=${key} label=${meta.label} botId=${acc.botId}`;\n}\n\nasync function cmdLogout(sel: string): Promise<string> {\n const key = resolveKey(sel);\n const dir = accountDir(key);\n const acc = readAccount(dir);\n const stopMsg = stopDaemon(key);\n if (acc?.botToken) {\n try {\n await notifyStop({ baseUrl: acc.baseUrl, token: acc.botToken });\n } catch {\n /* ignore */\n }\n }\n for (const f of [\"account.json\", \"state.json\", \"qr_login.json\", \"qrcode.png\", \"qrcode.txt\"]) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n return `账号 ${key} 已登出(${stopMsg})。目录与会话保留,可重新登录。`;\n}\n\nfunction cmdDelete(sel: string): string {\n const key = resolveKey(sel);\n const stopMsg = stopDaemon(key);\n try {\n fs.rmSync(accountDir(key), { recursive: true, force: true });\n } catch (e) {\n throw new Error(`删除目录失败: ${String(e)}`);\n }\n unregisterAccount(key);\n return `账号 ${key} 已删除(${stopMsg},已移除目录与注册)。`;\n}\n\nfunction cmdStart(sel?: string, runner?: string): string {\n const key = pickKey(sel); // 省略账号 = 默认账号\n startDaemon(key, runner);\n return `已启动 daemon(账号 ${key})。用 list 查看运行状态。`;\n}\n\n// ===== 本地任务命令(文件桥):写 task-inbox/*.json,由 daemon 扫描执行;状态读 task-state.json,结果读 task-out/*.json =====\n/** 原子写一个命令文件到收件箱(临时文件 + rename,避免 daemon 读到半截);返回本次命令的答复编号。 */\nfunction writeTaskCmd(key: string, obj: Record<string, unknown>): string {\n return writeTaskCommand(accountDir(key), obj);\n}\n\n/**\n * 等这次命令的答复(daemon 把真实结果写进 `task-out/<id>.json`)。\n * 三种情形分得很清楚:\n * - 有答复、有文案 → 打印 daemon 的那句话(失败原因、「没有 #9 号任务」这类只有它知道的事);\n * - 有答复、文案为空 → 命令成功但没有额外要说的,用调用方自己那句话(**立刻**,不干等);\n * - 没有答复 → 老版 daemon 不认这条通道(或它正在退出):回退到「已发送」措辞并说明一句,\n * 不把「没等到」当成「成功了」——达上限被拒与「派发成功」在旧措辞里长得一模一样。\n */\nasync function taskOutcome(\n key: string,\n ackId: string,\n fallback: string,\n timeoutMs = 5000,\n): Promise<string> {\n const a = await awaitTaskAck(accountDir(key), ackId, timeoutMs);\n if (!a)\n return `${fallback}\\n(没等到 daemon 答复:它可能还在跑旧版本,或刚启动。用 task-list / task-status 核对实际状态。)`;\n return a.text || fallback;\n}\n/**\n * 选账号:显式 key/label 优先,否则用默认账号(`accounts.json` 的 `default`;缺失时回落第一个登记的账号,见 core 的 `getDefaultKey`)。\n * `session`/`task*` 早就是这个口径;`start`/`stop` 也走这里——所以「省略账号」= 默认账号。\n * 默认账号是可见的(`wemind list` 的 `[默认]`)、可改的(`wemind set-default`);`logout`/`delete` 这类破坏性命令仍然必须显式指定,不走这里。\n */\nexport function pickKey(sel?: string): string {\n if (sel) return resolveKey(sel);\n const d = getDefaultKey();\n if (!d) throw new Error(\"无默认账号,请指定 <key|label> 或先 login\");\n return d;\n}\n/** 预检 daemon 存活(本地命令需 daemon 在跑才有人消费收件箱)。 */\nfunction requireDaemonAlive(key: string): void {\n const pid = readPid(accountDir(key), \"daemon.pid\");\n if (!aliveOurs(pid))\n throw new Error(\n `账号 ${key} 的 daemon 未运行。先启动:wemind start ${key}(用 wemind list 查看状态)`,\n );\n}\n\n// ===== 控制桥(CLI → daemon → 回话):让 CLI 与微信指令走同一段代码 =====\n/** daemon 是否活着(且确实是我们自己的进程)。读命令不需要它,写命令优先走它。 */\nfunction daemonAlive(key: string): boolean {\n return aliveOurs(readPid(accountDir(key), \"daemon.pid\"));\n}\n\n/**\n * daemon 活着**且认识控制桥**(升级 CLI 后没重启的旧 daemon 在这里判为不可用)。\n * 决定「走桥还是走文件」都该问这个而不是 daemonAlive:对旧 daemon 发送只会白等到超时。\n */\nfunction bridgeReady(key: string): boolean {\n const dir = accountDir(key);\n return daemonAlive(key) && bridgePidOf(dir) === readPid(dir, \"daemon.pid\");\n}\n\n/**\n * 经控制桥让 daemon 执行一条命令并等回话。\n * 超时**如实**报「没等到回复」——桥的价值就在于「CLI 说的」和「daemon 做的」是同一件事,绝不假装成功。\n */\nasync function viaBridge(key: string, req: Omit<ControlRequest, \"id\" | \"at\">): Promise<string> {\n const dir = accountDir(key);\n // 先验「在跑的这个 daemon 认不认识桥」:升级 CLI 后 daemon 常常还在跑旧版本,径直发送只会干等到超时。\n // 凭据是 daemon 启动时写的 control-out/.bridge(内容 = 它自己的 pid)——pid 对上才算数。\n const dpid = readPid(dir, \"daemon.pid\");\n if (bridgePidOf(dir) !== dpid)\n throw new Error(\n `账号 ${key} 的 daemon 还在跑**旧版本**(不认识控制桥),换大脑/会话命令发送过去只会白等超时。\\n` +\n `重启一次即可:wemind stop ${key} && wemind start ${key}\\n` +\n `(只想改配置不动进程:wemind brain set <agent 名>,但它要等下次启动才生效。)`,\n );\n const full: ControlRequest = { id: newRequestId(), at: Date.now(), ...req };\n writeControlRequest(full, dir);\n const res = await awaitControlResponse(full.id, dir, controlTimeoutMs(req.kind as ControlKind));\n if (!res)\n throw new Error(\n `等 daemon 回复超时(${req.kind})。它可能正忙或已退出,用 wemind list 看状态、daemon.log 看日志。`,\n );\n if (!res.ok) process.exitCode = 1; // ❌ 开头的答复 = 失败(脚本据此判成败)\n return res.text;\n}\n\n/**\n * `brain switch <agent 名|auto>`:**热切换**——daemon 在跑就走控制桥(不重启、不打断在途回合);\n * daemon 没在跑就只写 brain.json(下次启动生效)。\n * `--worker` 设默认任务大脑:那是 daemon 进程内才有的状态(不写入磁盘),没有 daemon 就没有意义,必须先启动。\n */\nasync function cmdBrainSwitch(agentName: string, key: string, worker: boolean): Promise<string> {\n const agent = agentName.trim().toLowerCase();\n if (agent !== \"auto\" && !ACP_AGENT_PRESETS[agent])\n throw new Error(\n `未知 agent「${agentName}」。可选:auto、${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}`,\n );\n const dir = accountDir(key);\n if (worker) {\n requireDaemonAlive(key); // 「默认任务大脑」是进程内状态,不写入磁盘\n return await viaBridge(key, { kind: \"brain.worker\", target: agent });\n }\n if (daemonAlive(key)) return await viaBridge(key, { kind: \"brain.switch\", target: agent });\n updateBrainConfig({ agent }, dir);\n return `daemon 未在运行:已把 ${key} 的大脑写进 brain.json(agent=${agent},下次启动生效)。\\n运行中换大脑:先 wemind start ${key}`;\n}\n\n/** 会话命令归属的人:登录者(与 daemon 里 owner 的定义一致)。 */\nfunction ownerOf(key: string): string {\n return process.env.WECHAT_OWNER_ID?.trim() || readAccount(accountDir(key))?.userId || \"\";\n}\n\n/** daemon 没在跑时用来定会话归属(按 agent 分开存)的大脑名(与 daemon 启动期同一个解析来源:配置 + 环境变量 + 自动探测)。 */\nfunction agentNameOf(key: string): string {\n return resolveAgent(loadBrainConfig(accountDir(key))).name;\n}\n\n/**\n * 末尾那个「长得像账号」的位置参数摘出来当账号选择(`session list 工作号`)。\n * 认不出就当普通参数——session 的名字可能跟账号别名撞脸,宁可把它当名字。\n */\nfunction splitKeyArg(args: string[]): { args: string[]; sel?: string } {\n const last = args[args.length - 1];\n if (args.length && last && listAccounts().some((a) => a.key === last || a.label === last))\n return { args: args.slice(0, -1), sel: last };\n return { args };\n}\n\n/**\n * `wemind session <new|list|resume|rename|delete>`。\n * 读写规则:**读**永远可用(直接读登记表);**写**优先走 daemon(单一写者,且会话当前会话记录/agent 侧状态都是它的活状态)。\n * 例外:`rename`/`delete` 只动登记表这一个文件,daemon 没跑时直接改也是安全的;`resume` 必须真让 agent load,没 daemon 做不了。\n */\nasync function cmdSession(rest: string[]): Promise<string> {\n const argv = [...rest];\n const pi = argv.indexOf(\"--page\");\n let page: number | undefined;\n if (pi >= 0) {\n page = Number(argv[pi + 1]) || undefined;\n argv.splice(pi, 2);\n }\n const pos = argv.filter((x) => x && !x.startsWith(\"--\"));\n const sub = (pos[0] ?? \"\").toLowerCase();\n const { args, sel } = splitKeyArg(pos.slice(1));\n const USAGE =\n \"用法: session list [--page N] | session new [名字] | session resume <序号|名字|id前缀> | \" +\n \"session rename <序号|名字> <新名字> | session delete <序号|名字|id前缀> [<key|label>]\";\n\n if (sub === \"list\") {\n const key = pickKey(sel);\n // 读优先走桥(daemon 里在用的那个大脑才是真的会话归属);桥不可用就退回直接读文件——读永远不该失败。\n if (bridgeReady(key)) return await viaBridge(key, { kind: \"session.list\", page });\n const dir = accountDir(key);\n const agent = agentNameOf(key);\n const userId = ownerOf(key);\n if (!userId) throw new Error(`账号 ${key} 没有 userId(account.json 不全),先重新登录。`);\n const { entries, others, currentSessionId } = listSessions(agent, userId, dir);\n const why = daemonAlive(key)\n ? \"(daemon 是升级前启动的旧版本,没法问它:以上按 brain.json/自动探测的「下次会用的大脑」列出;重启 daemon 后以进程内在用的为准。)\"\n : \"(daemon 未运行:按 brain.json/自动探测得到的「下次会用的大脑」列出。)\";\n return (\n renderSessionList(entries, { agent, page, currentSessionId, others, verbose: true }) +\n \"\\n\" +\n why\n );\n }\n\n if (sub === \"new\") {\n const key = pickKey(sel);\n const name = args.join(\" \").trim() || undefined;\n if (daemonAlive(key))\n return await viaBridge(key, { kind: \"session.new\", ...(name ? { name } : {}) });\n // 没 daemon:能做的只有「丢掉当前会话记录」——下条消息会开新的。名字要等会话真的建好再 rename。\n const dir = accountDir(key);\n const userId = ownerOf(key);\n if (!userId) throw new Error(`账号 ${key} 没有 userId(account.json 不全),先重新登录。`);\n const cleared = clearSessionPointer(\n brainSessionMapPath(dir),\n sessionKey(agentNameOf(key), userId),\n );\n return [\n cleared ? \"已清空当前会话记录\" : \"当前本来就没有会话当前会话记录\",\n \"(daemon 未运行:下次启动后你发的第一条消息会开一个新会话)\",\n name ? `名字等新会话建好后再登记:wemind session rename 1 ${name}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n }\n\n if (sub === \"resume\") {\n const key = pickKey(sel);\n if (!args[0]) throw new Error(USAGE);\n requireDaemonAlive(key); // 恢复要真的让 agent load 那条会话:只有 daemon 里有 ACP 连接,做不了假\n return await viaBridge(key, { kind: \"session.resume\", ref: args.join(\" \") });\n }\n\n if (sub === \"rename\" || sub === \"delete\") {\n const key = pickKey(sel);\n const [ref, ...nameRest] = args;\n if (!ref || (sub === \"rename\" && !nameRest.length)) throw new Error(USAGE);\n if (bridgeReady(key))\n return await viaBridge(\n key,\n sub === \"rename\"\n ? { kind: \"session.rename\", ref, name: nameRest.join(\" \") }\n : { kind: \"session.delete\", ref },\n );\n // 只动登记表(+ 删除时可能清当前会话记录),daemon 不在也不影响它下次读到的结果\n const dir = accountDir(key);\n const userId = ownerOf(key);\n if (!userId) throw new Error(`账号 ${key} 没有 userId(account.json 不全),先重新登录。`);\n const agent = agentNameOf(key);\n if (sub === \"delete\") {\n const r = deleteSession(agent, userId, ref, { dir });\n if (!r.ok) {\n process.exitCode = 1;\n return `❌ ${r.error}`;\n }\n return `已从列表删除:${r.entry.name ?? r.entry.title ?? r.entry.sessionId.slice(0, 8)}。${\n r.clearedPointer ? \"它就是当前会话,下一条消息会开一个新的。\" : \"当前会话没有变。\"\n }`;\n }\n const r = renameSession(agent, userId, ref, nameRest.join(\" \"), dir);\n if (!r.ok) {\n process.exitCode = 1;\n return `❌ ${r.error}`;\n }\n return `已重命名:${r.entry.name}(当前 brain=${agent} 的会话)`;\n }\n\n throw new Error(USAGE);\n}\n\n/** `wemind brain handoff`:让当前大脑写一份交接摘要(与微信「交接」同一段实现)。 */\nasync function cmdBrainHandoff(key: string): Promise<string> {\n requireDaemonAlive(key);\n return await viaBridge(key, { kind: \"brain.handoff\" });\n}\n\n/** 当前会话记录(供 CLI 展示;不常用但排查时值钱)。 */\nfunction cmdSessionCurrent(key: string): string {\n const agent = agentNameOf(key);\n const userId = ownerOf(key);\n const sid = userId ? currentSessionIdOf(agent, userId, accountDir(key)) : undefined;\n return sid\n ? `${key} 当前会话:${sid}(agent=${agent})`\n : `${key} 当前没有会话当前会话记录(下一条消息会开新会话)。agent=${agent}`;\n}\n\nasync function cmdTask(\n text: string,\n cwd: string | undefined,\n sel?: string,\n agentKey?: string,\n): Promise<string> {\n if (!text) throw new Error('用法: task \"<任务描述>\" [--cwd 目录] [--agent <名字>] [<key|label>]');\n const key = pickKey(sel);\n requireDaemonAlive(key);\n const useCwd = cwd || process.cwd();\n // 答复等 daemon:**起进程有结果之后**它才发(达上限/agent 起不来是那时才知道的),\n // 所以这里不能自己编「已派发」——旧写法轮询 task-state.json 的 active,会把「达上限被拒」和\n // 甚至上一条任务的编号报成成功。\n const ackId = writeTaskCmd(key, {\n kind: \"dispatch\",\n text,\n cwd: useCwd,\n ...(agentKey ? { agent: agentKey } : {}),\n });\n return await taskOutcome(\n key,\n ackId,\n `已发送任务(cwd=${useCwd})。看进展用 task-list。`,\n // 派发的答复要等「起进程有结果」:冷启动一个 agent 可能要几十秒,等得久一点好过说假话\n 30_000,\n );\n}\nasync function cmdTaskReply(text: string, sel?: string, id?: number): Promise<string> {\n if (!text) throw new Error('用法: task-reply [\"#编号\"] \"<内容>\" [<key|label>]');\n const key = pickKey(sel);\n requireDaemonAlive(key);\n const ackId = writeTaskCmd(key, { kind: \"reply\", text, ...(id ? { id } : {}) });\n return await taskOutcome(key, ackId, `已发送给任务${id ? ` #${id}` : \"\"}:${text.slice(0, 40)}`);\n}\nasync function cmdTaskStop(sel?: string, id?: number | \"all\"): Promise<string> {\n const key = pickKey(sel);\n requireDaemonAlive(key);\n const ackId = writeTaskCmd(key, { kind: \"stop\", ...(id ? { id } : {}) });\n const fallback =\n id === \"all\" ? \"已发送「结束全部任务」指令。\" : `已发送「结束任务${id ? ` ${id}` : \"\"}」指令。`;\n return await taskOutcome(key, ackId, fallback);\n}\n\ntype WmTaskRow = {\n id: number;\n userId: string;\n agent: string;\n status: string;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n cwd?: string;\n};\nconst STATUS_ZH: Record<string, string> = {\n running: \"运行中\",\n waiting: \"等回复\",\n interrupted: \"已中断\",\n};\n\n/**\n * 读任务表(daemon 没跑也能读:文件就是唯一真相)。\n * 先看 `tasks.json`——那是任务表的**持久**副本(重启后也在,编号跨重启不归零);\n * 老版本只写过 `task-state.json`(状态记录),仍兼容读它。\n */\nfunction readTaskRows(key: string): WmTaskRow[] | null {\n const dir = accountDir(key);\n for (const name of [\"tasks.json\", \"task-state.json\"]) {\n try {\n const s = JSON.parse(fs.readFileSync(path.join(dir, name), \"utf-8\")) as {\n tasks?: WmTaskRow[];\n };\n if (Array.isArray(s.tasks)) return s.tasks;\n } catch {\n /* 换下一个 */\n }\n }\n return null;\n}\n\nfunction cmdTaskList(sel?: string): string {\n const key = pickKey(sel);\n const rows = readTaskRows(key);\n if (!rows)\n return `账号 ${key}:无任务表(tasks.json / task-state.json 都不存在,daemon 未跑过多任务)。`;\n // 任务表是 daemon 的活状态:它没在跑(或还在跑旧版)时,表里的「运行中」只是上次留下的,别当成现在时。\n const stale = daemonAlive(key)\n ? \"\"\n : \"\\n(daemon 未运行:以下状态是它上次留下的记录;未完成的任务会在下次启动时标为「已中断」,回「继续」可续跑。)\";\n if (!rows.length) return `账号 ${key}:当前没有任务。${stale}`;\n const lines = [`账号 ${key} 任务(${rows.length} 条):`];\n for (const t of rows) {\n const age = t.startedAt ? `${Math.round((Date.now() - t.startedAt) / 1000)}s` : \"?\";\n lines.push(\n ` #${t.id} [${t.agent}] ${STATUS_ZH[t.status] ?? t.status} · ${t.turns} 轮 · ${age}${t.cwd ? ` · cwd=${t.cwd}` : \"\"}`,\n );\n if (t.lastOutput) lines.push(` ${t.lastOutput.replace(/\\n/g, \" \").slice(0, 140)}`);\n }\n return lines.join(\"\\n\") + stale;\n}\n\nfunction cmdTaskStatus(sel?: string): string {\n const key = pickKey(sel);\n const rows = readTaskRows(key);\n if (rows && rows.length) return cmdTaskList(sel);\n const stateFile = path.join(accountDir(key), \"task-state.json\");\n let s: {\n active?: boolean;\n status?: string;\n turns?: number;\n startedAt?: number;\n lastOutput?: string;\n };\n try {\n s = JSON.parse(fs.readFileSync(stateFile, \"utf-8\"));\n } catch {\n return `账号 ${key}:无任务状态(task-state.json 不存在,daemon 可能未跑过任务)。`;\n }\n if (!s.active) return `账号 ${key}:当前无活跃任务(idle)。`;\n const since = s.startedAt ? `${Math.round((Date.now() - s.startedAt) / 1000)}s` : \"?\";\n return [\n `账号 ${key} 任务状态:`,\n ` 状态:${s.status} 轮次:${s.turns ?? 0} 已运行:${since}`,\n ` 最近输出:${s.lastOutput ? s.lastOutput.slice(0, 300) : \"(无)\"}`,\n ].join(\"\\n\");\n}\n\nexport async function runManage(argv: string[]): Promise<void> {\n const cmd = argv.shift();\n const rest = [...argv];\n const li = rest.indexOf(\"--label\");\n let label: string | undefined;\n if (li >= 0) {\n label = rest[li + 1];\n rest.splice(li, 2);\n }\n const si = rest.indexOf(\"--start\");\n const autoStart = si >= 0;\n if (autoStart) rest.splice(si, 1);\n const ci = rest.indexOf(\"--cwd\");\n let cwd: string | undefined;\n if (ci >= 0) {\n cwd = rest[ci + 1];\n rest.splice(ci, 2);\n }\n const ri = rest.indexOf(\"--runner\");\n let runner: string | undefined;\n if (ri >= 0) {\n runner = rest[ri + 1];\n rest.splice(ri, 2);\n }\n const ai = rest.indexOf(\"--agent\");\n let agentKey: string | undefined;\n if (ai >= 0) {\n agentKey = rest[ai + 1];\n rest.splice(ai, 2);\n }\n\n switch (cmd) {\n case \"version\":\n case \"--version\":\n case \"-v\":\n console.log(versionLine());\n break;\n case \"login\":\n await cmdLogin(label, autoStart, runner);\n break;\n case \"list\":\n console.log(cmdList());\n break;\n case \"brain\":\n console.log(await cmdBrain(rest));\n break;\n case \"session\": {\n const sub = (rest[0] ?? \"\").toLowerCase();\n if (sub === \"current\") console.log(cmdSessionCurrent(pickKey(rest[1])));\n else console.log(await cmdSession(rest));\n break;\n }\n case \"adopt\":\n console.log(cmdAdopt(rest[0], label));\n break;\n case \"start\":\n console.log(cmdStart(rest[0], runner));\n break;\n case \"stop\": {\n const k = pickKey(rest[0]); // 省略账号 = 默认账号(答复带上解析到的账号,避免「静默换目标」)\n console.log(`账号 ${k}:${stopDaemon(k)}`);\n break;\n }\n case \"task\":\n console.log(await cmdTask(rest[0], cwd, rest[1], agentKey));\n break;\n case \"task-reply\": {\n // 兼容两种写法:`task-reply \"#3 内容\"` 与 `task-reply 3 \"内容\"`\n const first = rest[0] ?? \"\";\n const m = first.match(/^[##](\\d+)\\s*([\\s\\S]*)$/);\n if (m) console.log(await cmdTaskReply(m[2], rest[1], Number(m[1])));\n else if (rest.length >= 2 && /^\\d+$/.test(first))\n console.log(await cmdTaskReply(rest[1], rest[2], Number(first)));\n else console.log(await cmdTaskReply(first, rest[1]));\n break;\n }\n case \"task-list\":\n console.log(cmdTaskList(rest[0]));\n break;\n case \"task-status\":\n console.log(cmdTaskStatus(rest[0]));\n break;\n case \"task-stop\": {\n const a = rest[0];\n const id: number | \"all\" | undefined =\n a === \"all\" || a === \"全部\" ? \"all\" : a && /^\\d+$/.test(a) ? Number(a) : undefined;\n console.log(await cmdTaskStop(id === undefined ? a : rest[1], id));\n break;\n }\n case \"label\": {\n const k = resolveKey(rest[0]);\n setLabel(k, rest.slice(1).join(\" \"));\n console.log(`已设置 ${k} 的别名为「${rest.slice(1).join(\" \")}」`);\n break;\n }\n case \"set-default\": {\n const k = resolveKey(rest[0]);\n setDefaultKey(k);\n console.log(`默认账号 = ${k}`);\n break;\n }\n case \"logout\":\n console.log(await cmdLogout(rest[0]));\n break;\n case \"delete\":\n console.log(cmdDelete(rest[0]));\n break;\n case \"finalize\": {\n const { msg } = finalizeStaging(label);\n console.log(msg);\n break;\n } // 兼容旧流程(staging 已有登录时直接收进账号目录)\n case \"allow\": {\n const k = resolveKey(rest[0]);\n const uid = rest[1];\n if (!uid) throw new Error(\"用法: allow <key|label> <userId>\");\n const list = addAllowFrom(uid, accountDir(k));\n console.log(`已加入白名单 ${uid} → ${k}\\n当前清单(${list.length}):${list.join(\", \")}`);\n break;\n }\n case \"revoke\": {\n const k = resolveKey(rest[0]);\n const uid = rest[1];\n if (!uid) throw new Error(\"用法: revoke <key|label> <userId>\");\n const list = removeAllowFrom(uid, accountDir(k));\n console.log(\n `已从白名单移除 ${uid} → ${k}\\n当前清单(${list.length}):${list.join(\", \") || \"(空)\"}`,\n );\n break;\n }\n case \"allowlist\": {\n const k = resolveKey(rest[0]);\n const list = readAllowFrom(accountDir(k));\n console.log(\n `${k} 登录者白名单(${list.length}):\\n${list.map((u) => \" - \" + u).join(\"\\n\") || \" (空)\"}`,\n );\n break;\n }\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n console.log(helpText());\n break;\n default:\n // 拼错命令不能静默成功:脚本里 `wemind strat x` 以前 exit 0,看起来像成功。\n // 提示与帮助都走 stderr(stdout 保持为空),并以 2 退出(2 = 用法错误,与「运行出错」的 1 区分)。\n console.error(`未知命令:${cmd}`);\n console.error(helpText());\n process.exitCode = 2;\n }\n}\n\nexport function helpText(): string {\n return `WeMind 多账号管理(wemind v${CLI_VERSION})\n用法: wemind <command> 其余:wemind version | help\n\n login [--label 名] [--start] 一条命令完成扫码登录:自刷新二维码 + 自动登记(--start 顺带启动 daemon)\n list 列出账号、别名、运行状态(含 brain 进程与 worker 池)\n start [key|label] 启动某账号的 daemon(省略 = 默认账号)\n stop [key|label] 停止某账号的 daemon(连带 brain 与全部 worker 进程树;省略 = 默认账号)\n brain set <agent 名|auto> [--bin X] [--args \"…\"] [--extra-args \"…\"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]\n 改账号大脑**配置**(写 brain.json;只给 agent 名且 daemon 认桥时当场应用;带 --bin/--args/--extra-args/--shell/--model 只写盘——除 --shell 外可用同名 brain switch 当场应用;--extra-args 追加在预设参数后,不会漏掉 ACP 子命令)\n brain unset|check|show [<key|label>] 清除配置 / 真机 ACP 自检 / 查看配置与生效情况\n logout <key|label> 停 daemon + 通知下线 + 清凭证(保留目录,可重登)\n delete <key|label> 停 daemon + 删除目录 + 移除注册\n label <key|label> <新别名> 改别名\n set-default <key|label> 设默认账号\n adopt <key> [--label 名] 把已存在的账号目录登记进注册表\n allowlist <key|label> 查看该账号的登录者白名单(allowFrom)\n allow <key|label> <userId> (兜底)把某 userId 加进白名单(正常只会有登录者本人)\n revoke <key|label> <userId> (兜底)从白名单里移除某 userId\n\n【与微信等价的操作指令】以下都能带 [<key|label>](省略则用默认账号)。\n运行中的命令经**控制桥**交给 daemon 执行(CLI 与微信走同一段代码,行为一致);daemon 没跑时,\n读命令照常可用,部分写命令会只改文件或提示先启动。\n\n${renderHelp(\"owner\", \"cli\", { sections: [\"brain\", \"session\", \"task\", \"misc\"] })}\n\n说明:key = 由微信 botId 派生的唯一标识(目录名);label = 人类可读别名。多数命令可用 key 或 label。\n省略账号的命令(start/stop 与会话/任务类)用**默认账号**:首个登记的账号自动成为默认,可用 set-default 改、list 里看 [默认]。\nlogout/delete 等破坏性命令仍必须显式指定账号。`;\n}\n// 入口由 cli.ts 调用 runManage(argv);此处不再顶层自启。\n","#!/usr/bin/env node\n/**\n * wemind —— 完整应用的命令行入口(bin)。\n * - 子命令 `daemon`:跑 daemon 本体(长轮询 + 大脑 + worker),通常由 pm2 调用,用户不直接用。\n * - 其余子命令(login/list/start/stop/task…):交给 manage 派发。\n */\nimport { runDaemon } from \"./index.js\";\nimport { runManage } from \"./manage.js\";\nimport { runWatchdog } from \"./watchdog-runner.js\";\n\nconst argv = process.argv.slice(2);\n\nif (argv[0] === \"daemon\") {\n runDaemon().catch((err: unknown) => {\n console.error(\"[wemind/wechat-daemon] fatal:\", err);\n process.exit(1);\n });\n} else if (argv[0] === \"_watchdog\") {\n runWatchdog().catch((err: unknown) => {\n console.error(\"[wemind/wechat-daemon/watchdog] fatal:\", err);\n process.exit(1);\n });\n} else {\n runManage(argv).catch((e: unknown) => {\n console.error(\"error:\", e instanceof Error ? e.message : e);\n process.exit(1);\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":["sleepSync","loadRegistry","saveRegistry","readJson","resolveKey","downloadImage","isObj","catalogPath","mirrorRealKiro","readJson","syncCredentialFile","syncCredentials","applySettingsDefaults","loadCatalog","syncKiroSettings","syncOpencodeBase","settingsBase","syncSettingsBase","syncProfilePatch","syncSettings","isAlive","labelOf","dlog","REQ_TIMEOUT","PROMPT_IDLE_MS","PROMPT_CAP_MS","firstFrameMs","loadSessionMap","saveSessionMap","sleep","readPidFile","isSafeFileId","labelOf","log","log","writeTaskCmd","sleep","downloadImage","sweepWorkerPool","readAccount","readAccount","writeTaskCommand","isAlive","readAccount"],"sources":["../src/cli-dispatch.ts","../../wechat-ilink-core/dist/config.js","../../wechat-ilink-core/dist/pkg-version.js","../../wechat-ilink-core/dist/api/types.js","../../wechat-ilink-core/dist/api/error-codes.js","../../wechat-ilink-core/dist/api/transport-errors.js","../../wechat-ilink-core/dist/api/client.js","../../wechat-ilink-core/dist/api/session-guard.js","../../wechat-ilink-core/dist/store/file-lock.js","../../wechat-ilink-core/dist/store/accounts.js","../../wechat-ilink-core/dist/store/account.js","../../wechat-ilink-core/dist/store/allow-from.js","../../wechat-ilink-core/dist/cdn/image.js","../src/atomic-json.ts","../src/brain-config.ts","../src/cline-credentials.ts","../src/profile-model.ts","../src/brain-agents.ts","../src/agent-slot.ts","../src/agent-mcp.ts","../src/sandbox-inherit.ts","../src/mcp-sandbox.ts","../src/proc-identity.ts","../src/pid-record.ts","../src/debug-env.ts","../src/spawn-agent.ts","../src/session-map.ts","../src/session-registry.ts","../src/handoff.ts","../src/acp-reverse.ts","../src/acp-quiet.ts","../src/acp-connection.ts","../src/line-decoder.ts","../src/acp-brain.ts","../src/brain-state.ts","../src/brain-switch.ts","../src/log-rotate.ts","../src/watchdog-runner.ts","../src/chat-commands.ts","../src/control-bridge.ts","../src/control-inbox-runner.ts","../src/image-url.ts","../src/image-reply.ts","../src/md-reply.ts","../src/access.ts","../src/session-manager.ts","../src/worker-sandbox.ts","../src/worker-acp.ts","../src/worker-pool.ts","../src/task-store.ts","../src/task-manager.ts","../src/daemon-control.ts","../src/pending-push.ts","../src/send-pipeline.ts","../src/poller-batch.ts","../src/task-inbox.ts","../src/task-inbox-runner.ts","../src/daemon-routing.ts","../src/index.ts","../src/pm2-runner.ts","../src/brain-check.ts","../src/cli-context.ts","../src/cli-brain.ts","../src/cli-login.ts","../src/cli-session.ts","../src/cli-task.ts","../src/version.ts","../src/manage.ts","../src/cli.ts"],"sourcesContent":["/** cli.js 的三个入口:daemon 本体 / 后台守护进程 / 管理命令。单独成模块以便单测(cli.ts 导入即执行)。 */\nexport type CliHook = \"daemon\" | \"watchdog\" | \"manage\";\n\nexport function cliHook(argv: readonly string[]): CliHook {\n if (argv[0] === \"daemon\") return \"daemon\";\n if (argv[0] === \"_watchdog\") return \"watchdog\";\n return \"manage\";\n}\n","/**\n * iLink 应用标识。\n * 原 fork 从 package.json 的 ilink_appid 字段读取(向上查找);拆为 workspace 多包后,该耦合不再可靠(mcp-server 打包后会找到错误的 package.json),固化为常量,并允许 env 覆盖。\n */\nexport const ILINK_APP_ID = process.env.ILINK_APP_ID?.trim() || \"bot\";\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n/**\n * 读取「调用方所在包」自己的版本号(从它的 package.json)。\n *\n * ## 为什么放在 core\n * `@wemind/wechat-mcp` 与 `@wemind/cli` 都需要上报自己的版本,两边的「向上查找 package.json」逻辑完全一样。放 core 里只维护一份;两个消费方各留一个 3 行的薄适配层。\n *\n * ## 为什么必须由调用方传入位置(`callerUrl`)\n * `import.meta.url` 指向**当前文件**,而当前文件在 core 里——用它去向上找,命中的会是 core 自己的 `package.json`(或更上层的仓库根),**永远拿不到调用方的版本**。\n * 实测过:在 daemon 语境里直接复用 mcp 的同类实现,得到的是 mcp 的版本(1.0.4)而不是 daemon 的(1.1.0),且不会报错——静默给错值是最坏的一类 bug。\n * 所以这里强制由调用方传 `import.meta.url`,函数从**那个位置**向上找。\n *\n * ## 为什么还要 expectedName\n * 只按「最近的 package.json」查找并不可靠:在 monorepo 里向上找,可能先撞到 core 的、也可能撞到仓库根的。用 `name` 精确匹配,命中不了就返回 `undefined`(安全失败),由调用方决定兜底值,绝不静默返回别人的版本。\n *\n * ## 打包后仍然有效\n * 两个消费方都用 tsdown 把 core **内联**进单文件产物。由于查找起点是调用方的 `import.meta.url`(打包后是 `dist/cli.js` / `dist/index.js`),向上第一个带 `name` 匹配的 `package.json` 正是该消费方自己的——dev(`src/`)与产物(`dist/`)两种布局都成立。\n *\n * @param callerUrl 调用方模块的 `import.meta.url`\n * @param expectedName 期望的包名(即调用方自己 package.json 的 `name`)\n * @returns 匹配到的 `version`;找不到返回 `undefined`\n */\nexport function readOwnVersion(callerUrl, expectedName) {\n if (!callerUrl || !expectedName)\n return undefined;\n let dir;\n try {\n dir = path.dirname(fileURLToPath(callerUrl));\n }\n catch {\n // callerUrl 不是合法 file: URL(如打包器改写了它):安全失败\n return undefined;\n }\n const { root } = path.parse(dir);\n while (dir && dir !== root) {\n const candidate = path.join(dir, \"package.json\");\n try {\n const p = JSON.parse(fs.readFileSync(candidate, \"utf-8\"));\n if (p.name === expectedName && p.version)\n return p.version;\n }\n catch {\n // 文件不存在 / 非法 JSON:都不是目标,继续向上\n }\n dir = path.dirname(dir);\n }\n return undefined;\n}\n","/**\n * iLink Bot API 协议类型(照抄服务端 proto:GetUpdatesReq/Resp、WeixinMessage、SendMessageReq 等)。\n *\n * 来源与依据:\n * - 字段与 proto 含义对照官方 Tencent/openclaw-weixin `src/api/types.ts` 整理(社区/官方最权威的公开实现)。\n * - 错误码(ret / errcode)语义见 `error-codes.ts`。\n * - 标 `[实测]` 的注释为 WeMind 在真机使用中验证/纠正的结论(详见笔记「迭代记录与待办」)。\n *\n * 约定:\n * - 全部走 JSON over HTTP;proto 里的 bytes 字段在 JSON 中是 **base64 字符串**。\n * - 协议字段绝大多数可选(服务端按场景下发),故统一用 `?`;本地自有类型(AccountData/ServerState/QrLoginState)按实际必填。\n */\n/** 消息方向(proto: MessageType)。USER=入站(用户发来),BOT=出站(机器人发出)。 */\nexport const MessageType = {\n NONE: 0,\n USER: 1,\n BOT: 2,\n};\n/** 消息项类型(proto: MessageItemType)。一条消息的 item_list 里每项是其中一种。 */\nexport const MessageItemType = {\n NONE: 0,\n TEXT: 1,\n IMAGE: 2,\n VOICE: 3,\n FILE: 4,\n VIDEO: 5,\n};\n/**\n * 消息状态(proto: MessageState)。\n * - NEW=0:新消息。\n * - GENERATING=1:生成中(流式/进行中)。\n * - FINISH=2:本回合结束。\n * [实测] 关键:发送进度/中间消息用 **GENERATING**,仅最终答案用 **FINISH**——FINISH 会结束当前回合(关闭该 context 的发送窗口),若进度也用 FINISH 会提前关掉会话。\n * 详见笔记里程碑 21/23 与 README「进度提示与连续发送上限」。\n */\nexport const MessageState = {\n NEW: 0,\n GENERATING: 1,\n FINISH: 2,\n};\n/** 打字状态(proto: TypingStatus)。1=显示「正在输入」,2=取消。配合 sendTyping 使用。 */\nexport const TypingStatus = {\n TYPING: 1,\n CANCEL: 2,\n};\n/** 上传媒体类型(proto: UploadMediaType)。getUploadUrl 的 media_type 取值。 */\nexport const UploadMediaType = {\n IMAGE: 1,\n VIDEO: 2,\n FILE: 3,\n VOICE: 4,\n};\n","/**\n * iLink Bot API 错误码定义与分类。\n *\n * 权威来源:逆向自官方 Tencent/openclaw-weixin 的「iLink Bot API 协议参考」(corespeed-io/wechatbot `docs/protocol.md` 的 Error Codes 表,社区逆向、目前最权威的公开文档)。\n *\n * 两个字段,语义不同:\n * - `ret` :发送/上传等 RPC 的返回码(sendmessage / getuploadurl 等响应体)。\n * - `errcode`:长轮询/会话类接口的错误码(getupdates 等)。\n *\n * 已知错误码(截至文档):\n * | 码 | 字段 | 含义 | 处理 |\n * |---------------|----------|------------------------------|----------------------------------------|\n * | 0 | ret | 成功 | — |\n * | -2 | ret | 参数错误 (Parameter error) | 检查请求参数 |\n * | -14 | errcode | 会话过期 (Session expired) | 重新登录(本项目对该账号冷却 1 小时) |\n *\n * ⚠️ 重要澄清(避免重蹈覆辙):\n * - `ret = -2` 官方定义是**参数错误**,**不是**\"context_token 按时间过期\"。\n * 常见诱因:context_token 失效/与当前会话不匹配、必填字段缺失/格式不对。\n * - context_token 官方说明:按 `(accountId, userId)` **缓存、持久化、重用**,**仅在会话过期(-14)/重新登录时清除**——**没有**文档化的时间 TTL。\n * - 真正\"按时间/会话过期\"的是 `errcode = -14`(会话级),与 -2 无关。\n */\n/** 发送/上传 RPC 的 `ret` 码。 */\nexport const ILINK_RET = {\n OK: 0,\n /** 参数错误:检查请求(最常见为 context_token 失效/不匹配;也可能被复用为发送限流——短时连续发送过多,见 README「连续发送上限」)。 */\n PARAM_ERROR: -2,\n};\n/** 长轮询/会话类接口的 `errcode` 码。 */\nexport const ILINK_ERRCODE = {\n /** 会话过期,需重新登录。 */\n SESSION_EXPIRED: -14,\n};\n/** 把一个 ret/errcode 数值分类。 */\nexport function classifyILinkCode(code) {\n if (code === undefined || code === null || code === 0)\n return \"ok\";\n if (code === ILINK_ERRCODE.SESSION_EXPIRED)\n return \"session_expired\";\n if (code === ILINK_RET.PARAM_ERROR)\n return \"param_error\";\n return \"unknown\";\n}\n/** 返回可读标签 + 处理建议,用于日志与提示。 */\nexport function describeILinkCode(code) {\n switch (classifyILinkCode(code)) {\n case \"ok\":\n return { kind: \"ok\", label: `ok(${code ?? 0})`, hint: \"\" };\n case \"session_expired\":\n return { kind: \"session_expired\", label: \"会话过期(-14)\", hint: \"需重新登录\" };\n case \"param_error\":\n return {\n kind: \"param_error\",\n label: \"参数错误(-2)\",\n hint: \"检查请求参数(最常见:context_token 失效/不匹配;或发送限流——短时连续发送过多)\",\n };\n default:\n return { kind: \"unknown\", label: `未知错误码(${code})`, hint: \"对照 iLink 协议文档排查\" };\n }\n}\n/** iLink 接口返回非 0 ret/errcode 时抛出的结构化错误,携带原始码与分类,便于上层按类型处理。 */\nexport class ILinkError extends Error {\n code;\n kind;\n constructor(op, code, errmsg) {\n const { kind, label, hint } = describeILinkCode(code);\n super(`${op} 调用失败:${label}${hint ? ` — ${hint}` : \"\"}${errmsg ? `(${errmsg})` : \"\"}`);\n this.name = \"ILinkError\";\n this.code = code;\n this.kind = kind;\n }\n}\n","/**\n * 传输层错误分类:把 fetch/网络层抛出的错误归类,判定是否可重试。\n * 与 error-codes.ts(iLink 业务 ret/errcode)正交——这里只管「网络传输」层面的失败。\n * 借鉴 wxclawbot-cli(MIT)client.ts 的 classifyTransportError 思路。\n */\n/** HTTP 非 2xx:带上状态码,供调用方区分「限流/服务端故障可重试」与「4xx 请求问题」。 */\nexport class HttpStatusError extends Error {\n status;\n constructor(label, status, body) {\n super(`${label} ${status}: ${body.slice(0, 500)}`);\n this.name = \"HttpStatusError\";\n this.status = status;\n }\n}\n/** 归类一个传输层错误。timeout(AbortError)一律可重试。 */\nexport function classifyTransportError(err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n return { kind: \"timeout\", retryable: true, message: err.message || \"request timeout\" };\n }\n if (err instanceof HttpStatusError) {\n // 429(限流)与 5xx(服务端故障/网关)值得退避重试;其它 4xx 是请求本身的问题,重试不会好。\n return {\n kind: \"http\",\n retryable: err.status === 429 || err.status >= 500,\n message: err.message,\n };\n }\n const message = err instanceof Error ? err.message : String(err);\n const code = err &&\n typeof err === \"object\" &&\n \"code\" in err &&\n typeof err.code === \"string\"\n ? err.code\n : undefined;\n const cause = err instanceof Error && err.cause != null ? String(err.cause) : undefined;\n const s = `${message} ${code ?? \"\"} ${cause ?? \"\"}`.toLowerCase();\n if (s.includes(\"enotfound\") || s.includes(\"dns\"))\n return { kind: \"dns\", retryable: true, message, code };\n if (s.includes(\"econnreset\") || s.includes(\"socket hang up\"))\n return { kind: \"connection\", retryable: true, message, code };\n // TLS/证书错误判定必须排在 \"fetch failed\" 兜底之前:Node fetch 的网络失败顶层恒为 \"TypeError: fetch failed\",真正的 cert/tls 原因在 err.cause 里(已并入 s)。若放在 network 分支之后,会被 \"fetch failed\" 先匹配成可重试 → 此分支成死代码。\n if (s.includes(\"cert\") || s.includes(\"tls\") || s.includes(\"ssl\"))\n return { kind: \"tls\", retryable: false, message, code };\n if (s.includes(\"econnrefused\") ||\n s.includes(\"ehostunreach\") ||\n s.includes(\"enetunreach\") ||\n s.includes(\"etimedout\") ||\n s.includes(\"network\") ||\n s.includes(\"fetch failed\")) {\n return { kind: \"network\", retryable: true, message, code };\n }\n return { kind: \"unknown\", retryable: false, message, code };\n}\n","import crypto from \"node:crypto\";\nimport { ILINK_APP_ID } from \"../config.js\";\nimport { ILinkError } from \"./error-codes.js\";\nimport { HttpStatusError } from \"./transport-errors.js\";\n/** 普通 RPC 的超时(send/typing/config/upload/notify)。 */\nconst DEFAULT_API_TIMEOUT_MS = 15_000;\n/** 消息长轮询(getupdates)超时。 */\nconst DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;\n/** 扫码状态长轮询(get_qrcode_status)超时。与消息长轮询语义不同,故分开命名。 */\nconst QR_LONG_POLL_TIMEOUT_MS = 35_000;\n// 对外声明的 iLink 客户端版本:模仿官方 openclaw-weixin 的版本,避免后端因客户端版本过低而拒绝/快速过期二维码。需随官方更新——故支持 env 覆盖,免改代码。\nconst ILINK_CLIENT_VERSION = process.env.ILINK_CLIENT_VERSION?.trim() || \"2.4.3\";\nconst CHANNEL_VERSION = ILINK_CLIENT_VERSION;\n/**\n * iLink-App-ClientVersion: uint32 编码 0x00MMNNPP(major<<16 | minor<<8 | patch)。\n * 编码方式借鉴自官方 Tencent/openclaw-weixin(MIT)。\n */\nexport function buildClientVersion(version) {\n const [major = 0, minor = 0, patch = 0] = version.split(\".\").map((p) => parseInt(p, 10) || 0);\n return ((major & 0xff) << 16) | ((minor & 0xff) << 8) | (patch & 0xff);\n}\nconst ILINK_APP_CLIENT_VERSION = String(buildClientVersion(ILINK_CLIENT_VERSION));\n/** iLink 公共请求头:所有 GET/POST 都需携带,缺失会导致服务端拒绝连接。 */\nfunction buildCommonHeaders() {\n return {\n \"iLink-App-Id\": ILINK_APP_ID,\n \"iLink-App-ClientVersion\": ILINK_APP_CLIENT_VERSION,\n };\n}\nfunction ensureTrailingSlash(url) {\n return url.endsWith(\"/\") ? url : `${url}/`;\n}\nfunction randomWechatUin() {\n const uint32 = crypto.randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\nfunction buildHeaders(opts) {\n const headers = {\n \"Content-Type\": \"application/json\",\n AuthorizationType: \"ilink_bot_token\",\n \"Content-Length\": String(Buffer.byteLength(opts.body, \"utf-8\")),\n \"X-WECHAT-UIN\": randomWechatUin(),\n ...buildCommonHeaders(),\n };\n if (opts.token?.trim()) {\n headers.Authorization = `Bearer ${opts.token.trim()}`;\n }\n return headers;\n}\n/** POST 一个 iLink CGI,返回原始响应体。HTTP 非 2xx 抛错;响应体是否是 JSON 由调用方决定。 */\nasync function apiFetch(params) {\n const base = ensureTrailingSlash(params.baseUrl);\n const url = new URL(params.endpoint, base);\n const hdrs = buildHeaders({ token: params.token, body: params.body });\n const controller = new AbortController();\n const onAbort = () => controller.abort();\n if (params.signal?.aborted)\n controller.abort();\n else\n params.signal?.addEventListener(\"abort\", onAbort, { once: true });\n const timer = setTimeout(() => controller.abort(), params.timeoutMs);\n try {\n const res = await fetch(url.toString(), {\n method: \"POST\",\n headers: hdrs,\n body: params.body,\n signal: controller.signal,\n });\n // 读完响应体后才算这次请求结束:只清定时器会漏掉「头已到、体卡住」的慢响应,长轮询尤甚——所以清理放在 finally,成功/失败/超时都只写一次。\n const rawText = await res.text();\n if (!res.ok) {\n throw new HttpStatusError(params.label, res.status, rawText);\n }\n return rawText;\n }\n finally {\n clearTimeout(timer);\n params.signal?.removeEventListener(\"abort\", onAbort);\n }\n}\n/** 宽松解析:空响应体或非 JSON 返回 null(部分接口成功时无响应体)。 */\nfunction tryParseJson(raw) {\n if (!raw)\n return null;\n try {\n return JSON.parse(raw);\n }\n catch {\n return null;\n }\n}\n/** 防御性解析 API JSON 响应:失败抛带 label 与响应片段的清晰错误,而非裸 SyntaxError(畸形/代理 HTML/截断响应不致以晦涩错误打断调用方)。 */\nfunction parseJsonResp(raw, label) {\n try {\n return JSON.parse(raw);\n }\n catch {\n throw new Error(`${label}:响应不是合法 JSON(${raw.slice(0, 120)})`);\n }\n}\n/**\n * iLink 即使 HTTP 200 也可能在响应体返回非 0 ret/errcode 表示失败。语义见 error-codes.ts:\n * ret=-2 参数错误(最常见 context_token 失效/不匹配);errcode=-14 会话过期。\n * 不校验会导致「假成功」——消息其实没送达。仅在明确拿到非 0 错误码时抛 ILinkError(携带码+分类,供上层按类型处理)。\n */\nfunction throwIfErrorCode(op, resp) {\n if (!resp)\n return;\n // 两个字段分别判断:`ret ?? errcode` 在 ret=0、errcode=-14 时会取到 0,把会话过期掩盖成成功。\n if (resp.ret !== undefined && resp.ret !== 0)\n throw new ILinkError(op, resp.ret, resp.errmsg);\n if (resp.errcode !== undefined && resp.errcode !== 0)\n throw new ILinkError(op, resp.errcode, resp.errmsg);\n}\nexport function buildBaseInfo() {\n return { channel_version: CHANNEL_VERSION };\n}\nexport async function getUpdates(params) {\n const timeout = params.timeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;\n try {\n const rawText = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getupdates\",\n body: JSON.stringify({\n get_updates_buf: params.updatesBuf ?? \"\",\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: timeout,\n label: \"getUpdates\",\n signal: params.signal,\n });\n const resp = parseJsonResp(rawText, \"getUpdates\");\n // ret 非 0 且没有 errcode 时,调用方(daemon/MCP)只看 errcode 会把它当成「这轮没有新消息」而静默空转。\n // errcode 留给调用方处理(-14 要触发冷却,不能在这里抛成普通失败)。\n if (resp.ret !== undefined && resp.ret !== 0 && !resp.errcode) {\n throw new ILinkError(\"getUpdates\", resp.ret, resp.errmsg);\n }\n return resp;\n }\n catch (err) {\n // 调用方主动取消 ≠ 长轮询超时:不能伪装成「空结果」,否则 MCP 会继续空转。\n if (params.signal?.aborted)\n throw err;\n // 长轮询超时是常态:返回空结果(保留游标),让调用方直接进入下一轮。\n if (err instanceof Error && err.name === \"AbortError\") {\n return { ret: 0, msgs: [], get_updates_buf: params.updatesBuf };\n }\n throw err;\n }\n}\nexport async function sendMessage(params) {\n const raw = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendmessage\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"sendMessage\",\n });\n // 空/非 JSON 响应视为成功(历史上 iLink 成功时可能无响应体);只有明确拿到非 0 错误码才抛。\n throwIfErrorCode(\"sendMessage\", tryParseJson(raw));\n}\n/** 取 CDN 上传地址(发图/发文件用)。返回 upload_full_url 或 upload_param。 */\nexport async function getUploadUrl(params) {\n const raw = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getuploadurl\",\n body: JSON.stringify({\n filekey: params.filekey,\n media_type: params.media_type,\n to_user_id: params.to_user_id,\n rawsize: params.rawsize,\n rawfilemd5: params.rawfilemd5,\n filesize: params.filesize,\n no_need_thumb: params.no_need_thumb,\n aeskey: params.aeskey,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"getUploadUrl\",\n });\n const resp = parseJsonResp(raw, \"getUploadUrl\");\n throwIfErrorCode(\"getUploadUrl\", resp);\n return resp;\n}\nexport async function fetchQRCode(apiBaseUrl) {\n // 对齐官方:get_bot_qrcode 为 POST,body 带 local_token_list,并走全套请求头。\n const rawText = await apiFetch({\n baseUrl: apiBaseUrl,\n endpoint: \"ilink/bot/get_bot_qrcode?bot_type=3\",\n body: JSON.stringify({ local_token_list: [] }),\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: \"fetchQRCode\",\n });\n return parseJsonResp(rawText, \"fetchQRCode\");\n}\nexport async function pollQRStatus(apiBaseUrl, qrcode) {\n const base = ensureTrailingSlash(apiBaseUrl);\n const url = new URL(`ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`, base);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), QR_LONG_POLL_TIMEOUT_MS);\n try {\n const response = await fetch(url.toString(), {\n headers: buildCommonHeaders(),\n signal: controller.signal,\n });\n const text = await response.text();\n if (!response.ok) {\n throw new Error(`pollQRStatus ${response.status} ${response.statusText} ${text.slice(0, 500)}`);\n }\n return parseJsonResp(text, \"pollQRStatus\");\n }\n catch (err) {\n // 服务端会把长轮询 hold 到超时:AbortError 表示「这段时间没人扫码」,不是失败。\n if (err instanceof Error && err.name === \"AbortError\") {\n return { status: \"wait\" };\n }\n throw err;\n }\n finally {\n clearTimeout(timer);\n }\n}\nexport function generateId(prefix) {\n return `${prefix}:${Date.now()}-${crypto.randomBytes(4).toString(\"hex\")}`;\n}\nexport async function getConfig(params) {\n const rawText = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getconfig\",\n body: JSON.stringify({\n ilink_user_id: params.ilinkUserId,\n context_token: params.contextToken,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: \"getConfig\",\n });\n const resp = parseJsonResp(rawText, \"getConfig\");\n throwIfErrorCode(\"getConfig\", resp);\n return resp;\n}\nexport async function sendTyping(params) {\n const raw = await apiFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendtyping\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: \"sendTyping\",\n });\n // 与 sendMessage 同理:HTTP 200 也可能带非 0 ret,不校验会让「打字中」显示成假成功。\n throwIfErrorCode(\"sendTyping\", tryParseJson(raw));\n}\n/**\n * 上线/下线通知的共同实现。\n * best-effort:失败只记日志、绝不抛出——通知失败不应阻断登录/登出主流程。\n * 也正因为要吞错,这里刻意**不校验**响应体的 ret/errcode。\n */\nasync function notifyLifecycle(op, endpoint, params) {\n try {\n await apiFetch({\n baseUrl: params.baseUrl,\n endpoint,\n body: JSON.stringify({ base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: DEFAULT_API_TIMEOUT_MS,\n label: op,\n });\n }\n catch (err) {\n console.error(`[wemind/wechat-ilink-core/ilink] ${op} failed (ignored, best-effort): ${String(err)}`);\n }\n}\n/** 通知服务端该 bot 客户端上线(best-effort,失败不阻断)。 */\nexport async function notifyStart(params) {\n await notifyLifecycle(\"notifyStart\", \"ilink/bot/msg/notifystart\", params);\n}\n/** 通知服务端该 bot 客户端下线(best-effort,失败不阻断)。 */\nexport async function notifyStop(params) {\n await notifyLifecycle(\"notifyStop\", \"ilink/bot/msg/notifystop\", params);\n}\n","/**\n * 会话守卫:处理服务端 errcode -14(bot 会话超时)。\n * 命中后对该账号进入 1 小时冷却,期间快速返回而非反复长轮询空转。\n * 逻辑借鉴自官方 Tencent/openclaw-weixin(MIT),按 MCP 请求驱动场景精简。\n */\n/** 服务端返回的会话超时错误码。 */\nexport const SESSION_EXPIRED_ERRCODE = -14;\nconst PAUSE_MS = 60 * 60 * 1000;\nconst pauseUntil = new Map();\n/** 命中 -14 后暂停该账号一小时。 */\nexport function pauseSession(accountId) {\n pauseUntil.set(accountId, Date.now() + PAUSE_MS);\n}\n/** 距离冷却结束的剩余毫秒数(未暂停时为 0)。 */\nexport function getRemainingPauseMs(accountId) {\n const until = pauseUntil.get(accountId);\n if (until === undefined)\n return 0;\n const remaining = until - Date.now();\n if (remaining <= 0) {\n pauseUntil.delete(accountId);\n return 0;\n }\n return remaining;\n}\nexport function isSessionPaused(accountId) {\n return getRemainingPauseMs(accountId) > 0;\n}\n/** 重新登录成功后清除冷却。 */\nexport function clearSessionPause(accountId) {\n pauseUntil.delete(accountId);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n/** Atomics.wait 同步等待,不占 CPU,也不引入异步时序。 */\nfunction sleepSync(ms) {\n try {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n }\n catch {\n /* ignore */\n }\n}\nexport function withFileLock(lockFile, fn, opts = {}) {\n const waitMs = opts.waitMs ?? 5000;\n const staleMs = opts.staleMs ?? 10_000;\n const subject = opts.subject ?? lockFile;\n fs.mkdirSync(path.dirname(lockFile), { recursive: true });\n const deadline = Date.now() + waitMs;\n for (;;) {\n let fd;\n try {\n fd = fs.openSync(lockFile, \"wx\");\n fs.writeSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));\n fs.closeSync(fd);\n break;\n }\n catch (e) {\n if (fd !== undefined) {\n try {\n fs.closeSync(fd);\n }\n catch {\n /* ignore */\n }\n try {\n fs.unlinkSync(lockFile);\n }\n catch {\n /* ignore */\n }\n }\n if (e?.code !== \"EEXIST\")\n throw e instanceof Error ? e : new Error(String(e));\n try {\n const st = fs.statSync(lockFile);\n if (Date.now() - st.mtimeMs > staleMs) {\n fs.unlinkSync(lockFile);\n continue;\n }\n }\n catch {\n continue; // 锁刚被释放:立即重试\n }\n if (Date.now() >= deadline)\n throw new Error(`${subject}正被另一个 wimind 进程修改(锁文件 ${lockFile});请稍后重试。若确认没有其它 wimind 在跑,可删除该锁文件。`);\n sleepSync(50);\n }\n }\n try {\n return fn();\n }\n finally {\n try {\n fs.unlinkSync(lockFile);\n }\n catch {\n /* 已被清理:忽略 */\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { withFileLock } from \"./file-lock.js\";\n/**\n * 账号注册表:管理 ~/.wemind 下的多账号清单。\n * - 每个账号一个独立子目录(key = 由 botId 派生的唯一标识,见 deriveAccountKey)。\n * - accounts.json 记录 key → 元数据(botId/userId/baseUrl/label/addedAt)与默认账号。\n * label 为人类可读别名(默认等于 key,可改),folder/key 始终唯一且由微信 botId 派生。\n */\n/** WeMind 根目录(所有账号子目录与注册表的父目录)。可用 WEMIND_HOME 覆盖。 */\nexport const WEMIND_ROOT = process.env.WEMIND_HOME?.trim() || path.join(os.homedir(), \".wemind\");\nconst REGISTRY_FILE = path.join(WEMIND_ROOT, \"accounts.json\");\nconst REGISTRY_LOCK = `${REGISTRY_FILE}.lock`;\n/** 把 botId(如 feead781eb44@im.bot)净化成可作目录名的唯一 key(feead781eb44)。 */\nexport function deriveAccountKey(botId) {\n const head = (botId || \"\").split(\"@\")[0] || botId || \"\";\n const cleaned = head.replace(/[^a-zA-Z0-9_-]/g, \"\").trim();\n return cleaned || \"account\";\n}\n/** 某账号的状态目录:~/.wemind/<key>。 */\nexport function accountDir(key) {\n return path.join(WEMIND_ROOT, key);\n}\nexport function loadRegistry() {\n let raw;\n try {\n raw = fs.readFileSync(REGISTRY_FILE, \"utf-8\");\n }\n catch (e) {\n // 只有「不存在」才算首次运行。权限/磁盘/路径错误必须冒出去:把它当空表的话,下一次写会把所有已登记账号抹掉。\n if (e?.code === \"ENOENT\")\n return { accounts: {} };\n throw new Error(`无法读取账号注册表 ${REGISTRY_FILE}(${e?.code ?? \"未知错误\"}):${e instanceof Error ? e.message : String(e)}。为避免覆盖,本次不按空表处理。`);\n }\n try {\n const j = JSON.parse(raw);\n if (j && typeof j === \"object\" && j.accounts)\n return j;\n throw new Error(\"registry shape invalid\");\n }\n catch (e) {\n // 存在但损坏:不要当空注册表直接覆盖(紧接的 save 会抹掉所有已登记账号);备份坏文件 + 记日志\n console.error(`[wemind/wechat-ilink-core/accounts] ${REGISTRY_FILE} parse failed, backing up: ${String(e)}`);\n try {\n fs.renameSync(REGISTRY_FILE, `${REGISTRY_FILE}.corrupt-${Date.now()}`);\n }\n catch {\n /* best-effort */\n }\n return { accounts: {} };\n }\n}\n/** 原子写注册表:临时文件 + rename,避免写途崩溃/并发损坏。 */\nexport function saveRegistry(reg) {\n fs.mkdirSync(WEMIND_ROOT, { recursive: true });\n const tmp = `${REGISTRY_FILE}.tmp-${process.pid}-${Date.now()}`;\n fs.writeFileSync(tmp, JSON.stringify(reg, null, 2), \"utf-8\");\n fs.renameSync(tmp, REGISTRY_FILE);\n}\n/**\n * 注册表的跨进程写锁:`accounts.json` 的「读-改-写」必须串行,否则两个 `wimind login` 并发时后写的那个会用旧快照覆盖前一个账号。\n * 实现见 `file-lock.ts`(与 allowFrom 共用)。\n */\nfunction withRegistryLock(fn) {\n return withFileLock(REGISTRY_LOCK, fn, { subject: \"账号注册表\" });\n}\nexport function listAccounts() {\n const reg = loadRegistry();\n return Object.values(reg.accounts).sort((a, b) => a.addedAt - b.addedAt);\n}\nexport function getDefaultKey() {\n const reg = loadRegistry();\n if (reg.default && reg.accounts[reg.default])\n return reg.default;\n const keys = Object.keys(reg.accounts);\n return keys.length ? keys[0] : undefined;\n}\nexport function setDefaultKey(key) {\n withRegistryLock(() => {\n const reg = loadRegistry();\n if (!reg.accounts[key])\n throw new Error(`account not found: ${key}`);\n reg.default = key;\n saveRegistry(reg);\n });\n}\n/** 登记/更新一个账号;首个账号自动设为默认。 */\nexport function registerAccount(meta) {\n return withRegistryLock(() => {\n const reg = loadRegistry();\n const existing = reg.accounts[meta.key];\n const full = {\n key: meta.key,\n botId: meta.botId,\n userId: meta.userId,\n baseUrl: meta.baseUrl,\n label: meta.label || existing?.label || meta.key,\n addedAt: existing?.addedAt ?? meta.addedAt ?? Date.now(),\n };\n reg.accounts[meta.key] = full;\n if (!reg.default)\n reg.default = meta.key;\n saveRegistry(reg);\n return full;\n });\n}\n/** 移除注册表中的账号条目(不删除磁盘目录,由调用方决定)。 */\nexport function unregisterAccount(key) {\n withRegistryLock(() => {\n const reg = loadRegistry();\n delete reg.accounts[key];\n if (reg.default === key) {\n const rest = Object.keys(reg.accounts);\n reg.default = rest.length ? rest[0] : undefined;\n }\n saveRegistry(reg);\n });\n}\n/** 设置/修改某账号的人类可读别名。 */\nexport function setLabel(key, label) {\n withRegistryLock(() => {\n const reg = loadRegistry();\n if (!reg.accounts[key])\n throw new Error(`account not found: ${key}`);\n reg.accounts[key].label = label;\n saveRegistry(reg);\n });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { WEMIND_ROOT } from \"./accounts.js\";\n/**\n * 数据目录解析(按优先级):\n * 1. WECHAT_DATA_DIR:显式指定完整路径(最高优先级,便于任意自定义/多账号)。\n * 2. 否则 ~/.wemind/<account>,其中 <account> 取自 WECHAT_ACCOUNT,默认 \"default\"。\n * account.json / state.json / brain-sessions.json / brain-mcps.json / brain-home 等都落在此目录下。\n * 多账号即「不同 WECHAT_ACCOUNT(或 WECHAT_DATA_DIR)→ 不同目录 → 互相隔离」。\n */\nexport const ACCOUNT_KEY = process.env.WECHAT_ACCOUNT?.trim() || \"default\";\nexport const DATA_DIR = process.env.WECHAT_DATA_DIR?.trim() || path.join(WEMIND_ROOT, ACCOUNT_KEY);\n/** 持久化文件名:集中在此,避免各处拼字符串写错(load/save/clear 必须指向同一份)。 */\nconst FILES = {\n account: \"account.json\",\n state: \"state.json\",\n qrState: \"qr_login.json\",\n};\nfunction ensureDir(dir) {\n fs.mkdirSync(dir, { recursive: true });\n}\nfunction readJson(filePath) {\n let raw;\n try {\n raw = fs.readFileSync(filePath, \"utf-8\");\n }\n catch (e) {\n // 只有「不存在」才算首次运行;权限/fs 错误必须冒出去,不能静默当成空数据(否则下一步 save 会覆盖掉原文件)\n if (isNotFound(e))\n return null;\n throw e;\n }\n try {\n return JSON.parse(raw);\n }\n catch (e) {\n // 文件存在但解析失败(写途崩溃/外部破坏):不静默清空,备份坏文件 + 记日志,便于排查与人工恢复\n console.error(`[wemind/wechat-ilink-core/store] ${filePath} parse failed, backing up and treating as empty: ${String(e)}`);\n try {\n fs.renameSync(filePath, `${filePath}.corrupt-${Date.now()}`);\n }\n catch {\n /* best-effort */\n }\n return null;\n }\n}\nfunction isNotFound(err) {\n return err?.code === \"ENOENT\";\n}\n/**\n * POSIX 下把文件收成 600。数据目录里的 `account.json` / `state.json`(含 context_token)/ `qr_login.json` 都属于凭证类,\n * 不能按默认 umask 落成 0644。在 rename **之前**收紧临时文件:rename 会保留权限位,也不存在「先 0644 再 chmod」的窗口。\n * Windows 的 chmod 是 no-op,由 `saveAccountTo()` 的明文告警兜底。\n */\nfunction restrictPerms(filePath) {\n if (process.platform === \"win32\")\n return;\n try {\n fs.chmodSync(filePath, 0o600);\n }\n catch {\n /* best-effort */\n }\n}\n/** 原子写:先写临时文件再 rename 覆盖(同盘 rename 原子),避免写途崩溃/并发产生半截损坏文件。 */\nfunction writeJson(filePath, data) {\n ensureDir(path.dirname(filePath));\n const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n try {\n fs.writeFileSync(tmp, JSON.stringify(data, null, 2), \"utf-8\");\n restrictPerms(tmp);\n fs.renameSync(tmp, filePath);\n }\n catch (e) {\n // 写/rename 失败时清掉临时文件,避免数据目录里堆积 *.tmp-* 垃圾\n try {\n fs.rmSync(tmp, { force: true });\n }\n catch {\n /* best-effort */\n }\n throw e;\n }\n}\nexport function loadAccount(dir = DATA_DIR) {\n return readJson(path.join(dir, FILES.account));\n}\nlet warnedWinPerms = false;\n/**\n * 把登录凭证写进**指定**目录的 `account.json`(原子写 + POSIX 600 + Windows 一次性明文告警)。\n *\n * 为什么要有 dir 参数:`wemind login` 是先写到暂存目录 `.staging/`、扫码确认后才 rename 成账号目录。\n * 那条路径以前直接 `fs.writeFileSync`,于是「POSIX 600 / Windows 告警」这两条安全承诺在最推荐的登录入口上不成立(chmod 也漏了)。改成复用本函数,两条路径行为一致。\n * rename 同盘保留权限位,所以先写好再归档不会丢 600。\n */\nexport function saveAccountTo(dir, account) {\n const file = path.join(dir, FILES.account);\n writeJson(file, account); // POSIX 600 由 writeJson 在 rename 前统一收紧\n if (process.platform === \"win32\" && !warnedWinPerms) {\n // Windows 上 fs.chmod 实际是 no-op,无法用 0o600 限制访问。\n // 不做无效操作,改为一次性提醒(详见 README 的安全说明)。\n warnedWinPerms = true;\n console.error(`[wemind/wechat-ilink-core] warning: ${file} stores the bot token in plaintext; ` +\n `Windows cannot restrict it via chmod. Protect ${dir} with folder permissions or disk encryption.`);\n }\n}\nexport function saveAccount(account) {\n saveAccountTo(DATA_DIR, account);\n}\n/**\n * 删除凭证。返回是否真的删掉了(false = 本来就没有)。\n * 删除失败(权限/文件被占用)会**抛错**,不再与「本来就没登录」混成同一个 false——调用方(MCP logout)据此如实报告,而不是谎称「凭证已清除」。\n */\nexport function clearAccount(dir = DATA_DIR) {\n return removeFile(path.join(dir, FILES.account), true);\n}\n/** 空状态:唯一来源,避免 loadState/resetState 各写一份、将来加字段时漏改。 */\nfunction emptyState() {\n return { updatesBuf: \"\", contextTokens: {}, lastMessageId: 0 };\n}\nexport function loadState(dir = DATA_DIR) {\n return readJson(path.join(dir, FILES.state)) ?? emptyState();\n}\nexport function saveState(state, dir = DATA_DIR) {\n writeJson(path.join(dir, FILES.state), state);\n}\nexport function resetState(dir = DATA_DIR) {\n saveState(emptyState(), dir);\n}\nexport function loadQrState(dir = DATA_DIR) {\n return readJson(path.join(dir, FILES.qrState));\n}\nexport function saveQrState(state, dir = DATA_DIR) {\n writeJson(path.join(dir, FILES.qrState), state);\n}\nexport function clearQrState(dir = DATA_DIR) {\n return removeFile(path.join(dir, FILES.qrState));\n}\nfunction removeFile(filePath, strict = false) {\n try {\n fs.unlinkSync(filePath);\n return true;\n }\n catch (e) {\n if (isNotFound(e))\n return false; // 本来就没有:正常\n console.error(`[wemind/wechat-ilink-core/store] failed to remove ${filePath}: ${String(e)}`);\n if (strict)\n throw e instanceof Error ? e : new Error(String(e));\n return false;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DATA_DIR } from \"./account.js\";\nimport { withFileLock } from \"./file-lock.js\";\nfunction filePath(dir = DATA_DIR) {\n return path.join(dir, \"allowFrom.json\");\n}\nexport function readAllowFrom(dir = DATA_DIR) {\n let raw;\n try {\n raw = fs.readFileSync(filePath(dir), \"utf-8\");\n }\n catch {\n return []; // 不存在:正常(daemon 启动会种入本人)\n }\n try {\n const j = JSON.parse(raw);\n return Array.isArray(j.allowFrom)\n ? j.allowFrom.filter((x) => typeof x === \"string\" && !!x)\n : [];\n }\n catch (e) {\n // 存在但损坏:本清单是安全闸,损坏返回 [] 是 fail-closed(全员锁死),必须可见 → 记日志 + 备份坏文件\n console.error(`[wemind/wechat-ilink-core/allow-from] ${filePath(dir)} parse failed, backing up: ${String(e)}`);\n try {\n fs.renameSync(filePath(dir), `${filePath(dir)}.corrupt-${Date.now()}`);\n }\n catch {\n /* best-effort */\n }\n return [];\n }\n}\n/** 原子写:临时文件 + rename,避免写途崩溃/并发损坏这个安全闸文件。 */\nfunction writeAllowFrom(list, dir = DATA_DIR) {\n fs.mkdirSync(dir, { recursive: true });\n const uniq = [...new Set(list.filter((x) => typeof x === \"string\" && !!x))];\n const fp = filePath(dir);\n const tmp = `${fp}.tmp-${process.pid}-${Date.now()}`;\n fs.writeFileSync(tmp, JSON.stringify({ version: 1, allowFrom: uniq }, null, 2), \"utf-8\");\n fs.renameSync(tmp, fp);\n}\n/** 白名单的跨进程写锁:与账号注册表同类的「读-改-写」,两个终端并发 allow/revoke 时后写者会覆盖前者的变更。 */\nfunction withAllowLock(dir, fn) {\n return withFileLock(`${filePath(dir)}.lock`, fn, { subject: \"登录者白名单\" });\n}\n/** 把某个 userId 加入清单(去重)。返回更新后的清单。 */\nexport function addAllowFrom(userId, dir = DATA_DIR) {\n return withAllowLock(dir, () => {\n const list = readAllowFrom(dir);\n if (userId && !list.includes(userId))\n list.push(userId);\n writeAllowFrom(list, dir);\n return list;\n });\n}\n/** 从清单里移除某个 userId。返回更新后的清单。 */\nexport function removeAllowFrom(userId, dir = DATA_DIR) {\n return withAllowLock(dir, () => {\n const list = readAllowFrom(dir).filter((x) => x !== userId);\n writeAllowFrom(list, dir);\n return list;\n });\n}\n/** 该 userId 是否在清单里。 */\nexport function isAllowed(userId, dir = DATA_DIR) {\n return !!userId && readAllowFrom(dir).includes(userId);\n}\n","import { createCipheriv, createDecipheriv, createHash, randomBytes } from \"node:crypto\";\nimport { getUploadUrl, sendMessage, generateId } from \"../api/client.js\";\nimport { MessageType, MessageState, MessageItemType, UploadMediaType, } from \"../api/types.js\";\n/**\n * 收图:从 iLink CDN 下载并 AES-128-ECB 解密图片。\n * 机制借鉴官方 Tencent/openclaw-weixin(MIT)的 cdn/pic-decrypt + cdn-url。\n */\nconst CDN_BASE_URL = process.env.WECHAT_CDN_BASE_URL?.trim() || \"https://novac2c.cdn.weixin.qq.com/c2c\";\nexport function decryptAesEcb(ciphertext, key) {\n const d = createDecipheriv(\"aes-128-ecb\", key, null); // PKCS7 默认\n return Buffer.concat([d.update(ciphertext), d.final()]);\n}\n/** 解析图片 AES key → 16 字节。aeskey 为 hex;media.aes_key 为 base64(可能是 base64(hex 字符串))。 */\nexport function resolveKey(img) {\n if (img.aeskey && /^[0-9a-fA-F]{32}$/.test(img.aeskey))\n return Buffer.from(img.aeskey, \"hex\");\n const b64 = img.media?.aes_key;\n if (b64) {\n const decoded = Buffer.from(b64, \"base64\");\n if (decoded.length === 16)\n return decoded;\n if (decoded.length === 32 && /^[0-9a-fA-F]{32}$/.test(decoded.toString(\"ascii\"))) {\n return Buffer.from(decoded.toString(\"ascii\"), \"hex\");\n }\n }\n return null;\n}\n/** 收图上限:微信压缩后的图片通常远小于此;超过就中止,避免大文件/恶意响应撑爆内存。 */\nexport const MAX_INCOMING_IMAGE_BYTES = 20 * 1024 * 1024;\n/**\n * 读响应体,超过上限立刻取消——不把超大响应整个读进内存。\n * `content-length` 只是提示(可能缺失/撒谎),真正的闸在这里。\n */\nexport async function readBodyWithLimit(body, maxBytes) {\n if (!body)\n return Buffer.alloc(0);\n const reader = body.getReader();\n const chunks = [];\n let total = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n total += value.byteLength;\n if (total > maxBytes) {\n await reader.cancel().catch(() => { });\n throw new Error(`响应体过大 ${total}`);\n }\n chunks.push(Buffer.from(value));\n }\n }\n finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks);\n}\n/** 下载并解密一张图片,返回明文 Buffer(失败抛错)。 */\nexport async function downloadImage(img) {\n const url = img.media?.full_url ||\n (img.media?.encrypt_query_param\n ? `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(img.media.encrypt_query_param)}`\n : \"\");\n if (!url)\n throw new Error(\"image: 无 full_url/encrypt_query_param\");\n const key = resolveKey(img);\n if (!key)\n throw new Error(\"image: 无法解析 aes key\");\n const res = await fetch(url, { signal: AbortSignal.timeout(20000) });\n if (!res.ok)\n throw new Error(`image: CDN 下载失败 ${res.status}`);\n const declared = Number(res.headers.get(\"content-length\"));\n if (Number.isFinite(declared) && declared > MAX_INCOMING_IMAGE_BYTES)\n throw new Error(`image: CDN 图片过大 ${declared}`);\n const ciphertext = await readBodyWithLimit(res.body, MAX_INCOMING_IMAGE_BYTES);\n return decryptAesEcb(ciphertext, key);\n}\n// ===== 发图:AES-128-ECB 加密 + CDN 上传 + 拼 image_item 发送 =====\n// 机制借鉴官方 Tencent/openclaw-weixin(MIT)的 cdn/aes-ecb + cdn/upload + cdn/cdn-upload + messaging/send。\n/** AES-128-ECB 加密(PKCS7 默认)。 */\nexport function encryptAesEcb(plaintext, key) {\n const c = createCipheriv(\"aes-128-ecb\", key, null);\n return Buffer.concat([c.update(plaintext), c.final()]);\n}\n/** AES-128-ECB 密文大小(PKCS7 补齐到 16 字节边界)。 */\nexport function aesEcbPaddedSize(plaintextSize) {\n return Math.ceil((plaintextSize + 1) / 16) * 16;\n}\n/** 加密并上传一段 buffer 到 CDN,返回下载用的 encrypt_query_param(响应头 x-encrypted-param)。 */\nasync function uploadBufferToCdn(params) {\n const { buf, aeskey, filekey, uploadFullUrl, uploadParam } = params;\n const ciphertext = encryptAesEcb(buf, aeskey);\n const url = uploadFullUrl?.trim() ||\n (uploadParam\n ? `${CDN_BASE_URL}/upload?encrypted_query_param=${encodeURIComponent(uploadParam)}&filekey=${encodeURIComponent(filekey)}`\n : \"\");\n if (!url)\n throw new Error(\"upload: 无 upload_full_url/upload_param\");\n let lastErr;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/octet-stream\" },\n body: new Uint8Array(ciphertext),\n signal: AbortSignal.timeout(30000),\n });\n if (res.status >= 400 && res.status < 500)\n throw new Error(`CDN 上传客户端错误 ${res.status}`); // 不重试\n if (res.status !== 200)\n throw new Error(`CDN 上传服务端错误 ${res.status}`);\n const dp = res.headers.get(\"x-encrypted-param\");\n if (!dp)\n throw new Error(\"CDN 上传响应缺 x-encrypted-param\");\n return dp;\n }\n catch (e) {\n lastErr = e;\n if (e instanceof Error && e.message.includes(\"客户端错误\"))\n throw e;\n if (attempt < 3)\n continue;\n }\n }\n throw lastErr instanceof Error ? lastErr : new Error(\"CDN 上传失败\");\n}\n/** getUploadUrl + 加密上传,返回媒体引用所需信息。media_type 见 UploadMediaType。 */\nasync function uploadMediaBytes(params) {\n const { baseUrl, token, to, data, mediaType } = params;\n const rawsize = data.length;\n const rawfilemd5 = createHash(\"md5\").update(data).digest(\"hex\");\n const filesize = aesEcbPaddedSize(rawsize);\n const filekey = randomBytes(16).toString(\"hex\");\n const aeskey = randomBytes(16);\n // 非 0 ret/errcode 已由 getUploadUrl 内部抛 ILinkError,这里直接用\n const up = await getUploadUrl({\n baseUrl,\n token,\n filekey,\n media_type: mediaType,\n to_user_id: to,\n rawsize,\n rawfilemd5,\n filesize,\n no_need_thumb: true,\n aeskey: aeskey.toString(\"hex\"),\n });\n const downloadParam = await uploadBufferToCdn({\n buf: data,\n aeskey,\n filekey,\n uploadFullUrl: up.upload_full_url,\n uploadParam: up.upload_param,\n });\n return { downloadParam, aeskeyHex: aeskey.toString(\"hex\"), filesize, rawsize };\n}\n/** 拼 CDN media 引用(消息里给接收方下载+解密用)。 */\nfunction buildMediaRef(downloadParam, aeskeyHex) {\n return {\n encrypt_query_param: downloadParam,\n aes_key: Buffer.from(aeskeyHex).toString(\"base64\"),\n encrypt_type: 1,\n };\n}\nconst IMAGE_EXTS = new Set([\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".bmp\"]);\nconst VIDEO_EXTS = new Set([\".mp4\", \".mov\", \".webm\", \".mkv\", \".avi\"]);\n/** 按文件名扩展名判定媒体类别(借鉴 wxclawbot media.ts classifyMedia)。其余一律按文件发送。 */\nexport function classifyMediaByName(name) {\n const i = name.lastIndexOf(\".\");\n const ext = i >= 0 ? name.slice(i).toLowerCase() : \"\";\n if (IMAGE_EXTS.has(ext))\n return \"image\";\n if (VIDEO_EXTS.has(ext))\n return \"video\";\n return \"file\";\n}\nconst UPLOAD_TYPE_BY_KIND = {\n image: UploadMediaType.IMAGE,\n video: UploadMediaType.VIDEO,\n file: UploadMediaType.FILE,\n};\n/** 主动发一张图片给用户(需 context_token,与发文本相同)。image 为图片明文 buffer。 */\nexport async function sendImage(params) {\n const { baseUrl, token, to, image, contextToken } = params;\n const { downloadParam, aeskeyHex, filesize } = await uploadMediaBytes({\n baseUrl,\n token,\n to,\n data: image,\n mediaType: UploadMediaType.IMAGE,\n });\n const req = {\n msg: {\n from_user_id: \"\",\n to_user_id: to,\n client_id: generateId(\"img\"),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n item_list: [\n {\n type: MessageItemType.IMAGE,\n image_item: { media: buildMediaRef(downloadParam, aeskeyHex), mid_size: filesize },\n },\n ],\n context_token: contextToken,\n },\n };\n await sendMessage({ baseUrl, token, body: req });\n}\n/**\n * 主动发一个文件/视频/图片给用户(需 context_token)。\n * 类别按 fileName 扩展名自动判定(image/video/file),也可用 kind 显式指定。\n * 借鉴 wxclawbot media.ts 的类型识别与 item 组装;上传链路复用 WeMind 自有实现。\n */\nexport async function sendFile(params) {\n const { baseUrl, token, to, data, fileName, contextToken } = params;\n const kind = params.kind ?? classifyMediaByName(fileName);\n const { downloadParam, aeskeyHex, filesize, rawsize } = await uploadMediaBytes({\n baseUrl,\n token,\n to,\n data,\n mediaType: UPLOAD_TYPE_BY_KIND[kind],\n });\n const media = buildMediaRef(downloadParam, aeskeyHex);\n let item;\n if (kind === \"image\") {\n item = { type: MessageItemType.IMAGE, image_item: { media, mid_size: filesize } };\n }\n else if (kind === \"video\") {\n item = { type: MessageItemType.VIDEO, video_item: { media, video_size: filesize } };\n }\n else {\n item = {\n type: MessageItemType.FILE,\n file_item: { media, file_name: fileName, len: String(rawsize) },\n };\n }\n const req = {\n msg: {\n from_user_id: \"\",\n to_user_id: to,\n client_id: generateId(\"media\"),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n item_list: [item],\n context_token: contextToken,\n },\n };\n await sendMessage({ baseUrl, token, body: req });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * 原子写 JSON:同目录唯一临时文件 → rename 覆盖。\n *\n * 为什么不用固定 `${file}.tmp`:brain.json / session-registry.json 这类文件会有多个写者(CLI 与 daemon、\n * 升级旧版与新版的边界),共用一个临时文件名时两个进程可以互相覆盖对方写到一半的内容——rename 虽是原子的,\n * 但「写 tmp」这一步不是。唯一后缀(pid + 时间 + 随机)保证每个写者各写各的,rename 到同一个目标仍原子。\n */\nexport function writeJsonAtomic(filePath: string, data: unknown): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n try {\n fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, filePath);\n } catch (e) {\n try {\n fs.rmSync(tmp, { force: true });\n } catch {\n /* best-effort */\n }\n throw e;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\nimport { writeJsonAtomic } from \"./atomic-json.js\";\n\n/**\n * 账号级「大脑」持久配置(brain.json)。\n *\n * 背景:此前「用哪个大脑」只存在于进程环境变量 `WEMIND_BRAIN`(见 brain-agents.ts),是个**临时态**——换个入口重启(登录自启、pm2、后台守护进程、`cli.js start`、另一个 shell)就丢,静默退回默认 agent;而默认 agent(kiro)的二进制不一定装了 → 直到第一条消息才报错(历史上表现为「⏳ 响应中断了,正在自动重试…」)。\n *\n * 这里把选择**按账号写入磁盘**,成为单一事实来源:\n * - 任何入口重启都能复现(不依赖启动者的 shell);\n * - 切换是一等命令(`wemind brain set <agent 名>`),而不是「改环境变量 + 记得重开终端」;\n * - 环境变量 `WEMIND_BRAIN*` 仍为**一次性覆盖**(优先级更高),临时试别的 agent 不受影响。\n *\n * 文件形如 `~/.wemind/<账号>/brain.json`:\n * ```json\n * { \"agent\": \"opencode\", \"worker\": { \"agent\": \"kiro\" },\n * \"presets\": { \"deepseek\": { \"bin\": \"C:\\\\…\\\\dsh.cmd\" } } }\n * ```\n *\n * - 顶层 `agent`(可带 `bin`/`args`/`shell`/`homeEnvVar`/`model` 覆盖)=**对话大脑**,历史语义不变;\n * - `worker` =任务 worker 的独立选择(**缺省=跟随对话大脑**,与拆分角色之前完全一致);\n * - `presets` =**按 agent 名存**的启动覆盖:`wemind brain set X --bin …` 写在这里,于是「切到 opencode 再切回 deepseek」不会丢掉 deepseek 的绝对路径(DSH 桌面端必须显式给 `dsh.cmd`)。`--model` 同样存这里。\n * 顶层那些字段仍会同步写一份(兼容旧版本 CLI 读取),切换 agent 时旧 agent 的覆盖会被迁移进 `presets[旧 agent]`。\n */\n\nexport type AgentRole = \"brain\" | \"worker\";\n\n/** 一个角色的 agent 选择与其启动覆盖。 */\nexport type AgentConfig = {\n /** agent 名(见 brain-agents.ts 的 ACP_AGENT_PRESETS),或 `auto`(自动探测)。 */\n agent: string;\n /** 覆盖二进制(绝对路径或 PATH 上的名字)。 */\n bin?: string;\n /** 覆盖 ACP 启动参数。 */\n args?: string[];\n /** 追加到启动参数末尾(保留预设里的 ACP 模式参数;只加开关时用它,避免整段替换漏掉 `acp`)。 */\n extraArgs?: string[];\n /** 覆盖是否经 shell 启动。 */\n shell?: boolean;\n /** 覆盖「配置目录环境变量名」(指向沙箱)。 */\n homeEnvVar?: string;\n /** 覆盖模型值(会话建立后经 `session/set_config_option` 下发一次);`auto` / `default` = 跟随默认(DSH 从真实 profile 读,优先 `- id: acp`、否则 `agent-default-model`;读不到就不下发)。 */\n model?: string;\n};\n\n/** 按 agent 名保存的启动覆盖。 */\nexport type PresetOverride = Omit<AgentConfig, \"agent\">;\n\nexport type BrainConfig = AgentConfig & {\n /** 任务 worker 的独立选择;缺省=跟随对话大脑。 */\n worker?: AgentConfig;\n /** agent 名 → 启动覆盖。 */\n presets?: Record<string, PresetOverride>;\n};\n\n/** brain.json 的完整路径(默认当前账号数据目录)。 */\nexport function brainConfigPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"brain.json\");\n}\n\n/** 宽松归一化「一块 agent 字段」(顶层与 worker 段共用);无有效 agent 返回 null。 */\nfunction normAgentFields(j: any): AgentConfig | null {\n const agent = typeof j?.agent === \"string\" ? j.agent.trim() : \"\";\n if (!agent) return null;\n const cfg: AgentConfig = { agent };\n if (typeof j.bin === \"string\" && j.bin.trim()) cfg.bin = j.bin.trim();\n if (Array.isArray(j.args))\n cfg.args = j.args.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j.args === \"string\" && j.args.trim()) cfg.args = j.args.trim().split(/\\s+/);\n if (Array.isArray(j.extraArgs))\n cfg.extraArgs = j.extraArgs.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j.extraArgs === \"string\" && j.extraArgs.trim())\n cfg.extraArgs = j.extraArgs.trim().split(/\\s+/);\n if (typeof j.shell === \"boolean\") cfg.shell = j.shell;\n if (typeof j.homeEnvVar === \"string\" && j.homeEnvVar.trim()) cfg.homeEnvVar = j.homeEnvVar.trim();\n if (typeof j.model === \"string\" && j.model.trim()) cfg.model = j.model.trim();\n return cfg;\n}\n\n/** 宽松归一化一条配置覆盖;全空返回 null。 */\nfunction normPresetOverride(j: any): PresetOverride | null {\n const ov: PresetOverride = {};\n if (typeof j?.bin === \"string\" && j.bin.trim()) ov.bin = j.bin.trim();\n if (Array.isArray(j?.args))\n ov.args = j.args.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j?.args === \"string\" && j.args.trim()) ov.args = j.args.trim().split(/\\s+/);\n if (Array.isArray(j?.extraArgs))\n ov.extraArgs = j.extraArgs.filter((x: unknown): x is string => typeof x === \"string\");\n else if (typeof j?.extraArgs === \"string\" && j.extraArgs.trim())\n ov.extraArgs = j.extraArgs.trim().split(/\\s+/);\n if (typeof j?.shell === \"boolean\") ov.shell = j.shell;\n if (typeof j?.homeEnvVar === \"string\" && j.homeEnvVar.trim()) ov.homeEnvVar = j.homeEnvVar.trim();\n if (typeof j?.model === \"string\" && j.model.trim()) ov.model = j.model.trim();\n return Object.keys(ov).length ? ov : null;\n}\n\n/**\n * 读取账号的大脑配置;缺失/损坏/无有效 agent 一律返回 null(幂等、不抛)。\n * 字段做了宽松归一化:args 允许字符串(空格分隔)或数组;未知字段忽略。\n */\nexport function loadBrainConfig(dir: string = DATA_DIR): BrainConfig | null {\n let j: any;\n try {\n j = JSON.parse(fs.readFileSync(brainConfigPath(dir), \"utf-8\"));\n } catch {\n return null;\n }\n if (!j || typeof j !== \"object\" || Array.isArray(j)) return null;\n const base = normAgentFields(j);\n if (!base) return null;\n const cfg: BrainConfig = { ...base };\n const worker = normAgentFields(j.worker);\n if (worker) cfg.worker = worker;\n if (j.presets && typeof j.presets === \"object\" && !Array.isArray(j.presets)) {\n const presets: Record<string, PresetOverride> = {};\n for (const [k, v] of Object.entries(j.presets as Record<string, unknown>)) {\n const ov = normPresetOverride(v);\n if (ov) presets[k.trim().toLowerCase()] = ov;\n }\n if (Object.keys(presets).length) cfg.presets = presets;\n }\n return cfg;\n}\n\n/** 原子写入账号的大脑配置(唯一临时文件 + rename,避免读到半截、也不与其它写者抢同一个 tmp)。 */\nexport function saveBrainConfig(cfg: BrainConfig, dir: string = DATA_DIR): void {\n writeJsonAtomic(brainConfigPath(dir), cfg);\n}\n\n/**\n * 取某角色对应的配置块:worker 未单独设置时**回落到顶层**(拆分角色之前的行为完全不变),\n * 顶层为空则返回 null(交给 resolveAgent 走 auto/默认)。\n */\nexport function agentConfigFor(cfg: BrainConfig | null, role: AgentRole): BrainConfig | null {\n if (!cfg) return null;\n if (role === \"brain\") return cfg;\n if (!cfg.worker?.agent) return cfg; // 没有独立 worker 段:worker 沿用顶层整段(含 presets)\n // presets 是**账号级**的「按 agent 名存的启动覆盖」,与「用哪一段选 agent」无关,必须跟着走:\n // 否则 worker 段的存在会让 presets 查不到,`--bin` 之类的覆盖对 worker 静默失效。\n return cfg.presets ? { ...cfg.worker, presets: cfg.presets } : cfg.worker;\n}\n\n/**\n * 合并一次「设配置」(`wemind brain set` / 切换命令写入磁盘):\n * - patch 的顶层字段整体替换顶层;\n * - 保留 existing 的 worker 段(除非 patch 自带)——旧实现整体覆盖写会把 worker 段删掉;\n * - agent 变了 → 把 existing 顶层还带着的启动覆盖(它们属于**旧** agent)迁进 `presets[旧 agent]`,\n * 避免「切到 opencode 再切回 deepseek」丢掉 deepseek 的 `--bin`;agent 没变则沿用未给定的覆盖;\n * - patch 自带的启动覆盖同时写进 `presets[新 agent]`(并被顶层同步一份,兼容旧版本 CLI)。\n */\nexport function mergeBrainConfig(existing: BrainConfig | null, patch: BrainConfig): BrainConfig {\n const prev = existing?.agent?.trim().toLowerCase() ?? \"\";\n const nextAgent = patch.agent.trim().toLowerCase();\n const presets: Record<string, PresetOverride> = { ...existing?.presets };\n\n if (prev && prev !== nextAgent) {\n const legacy: PresetOverride = {};\n if (existing?.bin) legacy.bin = existing.bin;\n if (existing?.args?.length) legacy.args = existing.args;\n if (existing?.extraArgs?.length) legacy.extraArgs = existing.extraArgs;\n if (existing?.shell !== undefined) legacy.shell = existing.shell;\n if (existing?.homeEnvVar) legacy.homeEnvVar = existing.homeEnvVar;\n if (existing?.model) legacy.model = existing.model;\n if (Object.keys(legacy).length) presets[prev] = { ...presets[prev], ...legacy };\n }\n\n const next: BrainConfig = { agent: patch.agent };\n if (patch.bin) next.bin = patch.bin;\n if (patch.args?.length) next.args = patch.args;\n if (patch.extraArgs?.length) next.extraArgs = patch.extraArgs;\n if (patch.shell !== undefined) next.shell = patch.shell;\n if (patch.homeEnvVar) next.homeEnvVar = patch.homeEnvVar;\n if (patch.model) next.model = patch.model;\n // 同一个 agent 再设一次(如只改了 args):没给的启动覆盖沿用原值——它们本来就属于这个 agent,\n // 丢掉会让「切走再切回」丢 --bin 的坑在原地重现。注意 `args: []` 是**显式清空**,不沿用。\n if (prev && prev === nextAgent) {\n if (!patch.bin && existing?.bin) next.bin = existing.bin;\n if (patch.args === undefined && existing?.args?.length) next.args = existing.args;\n if (patch.extraArgs === undefined && existing?.extraArgs?.length)\n next.extraArgs = existing.extraArgs;\n if (patch.shell === undefined && existing?.shell !== undefined) next.shell = existing.shell;\n if (!patch.homeEnvVar && existing?.homeEnvVar) next.homeEnvVar = existing.homeEnvVar;\n if (!patch.model && existing?.model) next.model = existing.model;\n }\n\n // presets 是「按 agent 名保存的启动覆盖」,读配置时优先于顶层;清空 args 必须连它一起清,否则下次启动又被旧值盖回来。\n const ov: PresetOverride = { ...presets[nextAgent] };\n if (next.bin) ov.bin = next.bin;\n if (next.args?.length) ov.args = next.args;\n else if (patch.args !== undefined) delete ov.args;\n if (next.extraArgs?.length) ov.extraArgs = next.extraArgs;\n else if (patch.extraArgs !== undefined) delete ov.extraArgs;\n if (next.shell !== undefined) ov.shell = next.shell;\n if (next.homeEnvVar) ov.homeEnvVar = next.homeEnvVar;\n if (next.model) ov.model = next.model;\n if (Object.keys(ov).length) presets[nextAgent] = ov;\n else delete presets[nextAgent];\n if (Object.keys(presets).length) next.presets = presets;\n\n const worker = patch.worker ?? existing?.worker;\n if (worker?.agent) next.worker = worker;\n return next;\n}\n\n/** 读-改-写一步到位(manage.ts 用):返回写入磁盘后的配置。 */\nexport function updateBrainConfig(patch: BrainConfig, dir: string = DATA_DIR): BrainConfig {\n const next = mergeBrainConfig(loadBrainConfig(dir), patch);\n saveBrainConfig(next, dir);\n return next;\n}\n\n/** 清除账号的大脑配置(回到自动探测/默认);返回是否确有文件被删。 */\nexport function clearBrainConfig(dir: string = DATA_DIR): boolean {\n try {\n fs.unlinkSync(brainConfigPath(dir));\n return true;\n } catch {\n return false;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Cline ACP 的认证桥接。\n *\n * Cline 的 `isSessionReady()` 只自动恢复它自己的 3 种登录(`cline` / `cline-pass` / `openai-codex`),\n * 自定义/API-key provider(如 `openai-compatible`)不在 `tryRestoreAuth()` 的列表里——即使\n * `data/settings/providers.json` 里已经存在 `lastUsedProvider` 和 `apiKey`,`session/new` 也会报\n * `-32000 Authentication required`。Cline 官方给 ACP 的逃生口是 `CLINE_PROVIDER` + `CLINE_API_KEY`。\n *\n * 这里做一层**只读、按需**的桥接:父进程没有显式提供这两个变量时,把 `lastUsedProvider` 对应的\n * `settings.apiKey` 转成这两个环境变量,从而复用用户已经用 `cline auth` / 手写配置好的登录态。\n *\n * 边界(刻意收窄):\n * - **显式环境变量永远优先**:`CLINE_PROVIDER` 或 `CLINE_API_KEY` 只要有一个已存在,就完全不接管\n * (避免把父进程的 provider 和配置文件里的 key 错配);\n * - 只认 `lastUsedProvider`,且该 provider 必须有非空字符串 `apiKey`;OAuth 条目(只有 `auth`、\n * 没有 `apiKey`)不处理,继续走 Cline 自己的 `tryRestoreAuth()`;\n * - 文件不存在 / JSON 坏了 / 结构不对 → 返回空表,不抛异常(登录态问题仍由原来的认证提示暴露);\n * - 只读不写,不打印 key。\n */\nexport function clineCredentialEnv(\n realHome: string | undefined,\n parentEnv: NodeJS.ProcessEnv,\n): Record<string, string> {\n if (parentEnv.CLINE_PROVIDER?.trim() || parentEnv.CLINE_API_KEY?.trim()) return {};\n if (!realHome) return {};\n\n const file = path.join(realHome, \"data\", \"settings\", \"providers.json\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(file, \"utf-8\"));\n } catch {\n return {};\n }\n if (!isObj(parsed)) return {};\n\n const providerId =\n typeof parsed.lastUsedProvider === \"string\" ? parsed.lastUsedProvider.trim() : \"\";\n if (!providerId) return {};\n\n const providers = isObj(parsed.providers) ? parsed.providers : null;\n const entry = providers && isObj(providers[providerId]) ? providers[providerId] : null;\n const settings = entry && isObj(entry.settings) ? entry.settings : null;\n const apiKey = settings && typeof settings.apiKey === \"string\" ? settings.apiKey.trim() : \"\";\n if (!apiKey) return {};\n\n return { CLINE_PROVIDER: providerId, CLINE_API_KEY: apiKey };\n}\n\nfunction isObj(v: unknown): v is Record<string, any> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * 真实 profile 用户层(`<realHome>/profiles/<profile>/cordis.patch.yml`)的三件事:\n * ① 读默认模型(`readProfileDefaultModel`);② 缺 `- id: acp` 时给用户提示(`acpOverrideHint`);\n * ③ 复制进沙箱前剔除「微信 MCP」条目(`stripWechatMcpEntries`,防回环)。\n * 文件是桌面端维护的 YAML 用户层,结构是一个 patch 列表。这里只做定点抽取(块式与 flow 式映射都认),不引入 YAML 依赖。\n */\n\n/**\n * 从 DSH 真实 home 的 profile 用户层读「默认模型」。\n *\n * 为什么需要(2026-10-29 实测,DSH 0.2.1-rc.3,scripts/acp-probe.mjs):`dsh --profile acp` 的 ACP 桥插件默认模型写死在它\n * **自己的 bundle patch** 里(`- id: acp` → `deepseek-official/deepseek-v4-flash`,该路由要 DEEPSEEK_API_KEY),\n * 而 profile 用户层只覆盖 `llm-pi-ai` / `agent-default-model` / `web-search-deepseek`——**`- id: acp` 由用户自己补**\n * (插件 README 原话:a later patch can replace that row's complete config)。\n *\n * 读的顺序(都只是「读用户自己写的配置」,不替 agent 合成):\n * ① 用户层若已有 `- id: acp` 且带 provider/model → 用它的值(这才是 ACP 路径真正生效的模型);\n * ② 否则退回 `- id: agent-default-model` 的 provider/model(桌面端的默认,等于「你日常怎么用」)。\n * 读不到返回 null(调用方回退到内置预设值 / 不下发)。\n */\nexport function readProfileDefaultModel(realHome: string, profile: string): string | null {\n if (!realHome || !profile) return null;\n let text: string;\n try {\n text = fs.readFileSync(path.join(realHome, \"profiles\", profile, \"cordis.patch.yml\"), \"utf-8\");\n } catch {\n return null;\n }\n const acp = entryConfig(text, /^\\s*-\\s+id:\\s*acp\\s*(#.*)?$/);\n if (acp) return acp;\n return entryConfig(text, /^\\s*-\\s+id:\\s*agent-default-model\\s*(#.*)?$/);\n}\n\n/**\n * 复制进沙箱前剔除「微信 MCP」条目(真实文件不动)。DSH 自己的 MCP 也写在 profile 里\n * (`- id: mcp-xxx` + `name: '@deepseek-ai/dsh-mcp-client'`);如果用户在自己的 dsh 配置里挂过微信 MCP,\n * 原样复制会让沙箱里的 agent 用同一个 bot token 起第二个消费者(抢消息/回环)。\n * 大脑与 worker 的 MCP 都由 WeMind 的激活集权威决定(`wechat` 永远不在里面),所以只剔这一种条目,其余原样保留。\n * 没有命中时返回**原文**(逐字节复制不受影响)。\n */\nexport function stripWechatMcpEntries(text: string): { text: string; removed: string[] } {\n const lines = text.split(/\\r?\\n/);\n const starts: number[] = [];\n for (let i = 0; i < lines.length; i++) if (/^-\\s/.test(lines[i])) starts.push(i);\n const removed: string[] = [];\n const drop = new Set<number>();\n for (let b = 0; b < starts.length; b++) {\n const start = starts[b];\n const end = b + 1 < starts.length ? starts[b + 1] : lines.length;\n const body = lines\n .slice(start, end)\n .filter((l) => !/^\\s*#/.test(l))\n .join(\"\\n\");\n if (!isWechatMcpEntry(body)) continue;\n removed.push(lines[start].trim().replace(/^-\\s+/, \"\").split(/\\s+#/)[0]);\n for (let i = start; i < end; i++) drop.add(i);\n }\n if (!removed.length) return { text, removed };\n const eol = text.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n let out = lines.filter((_, i) => !drop.has(i)).join(eol);\n if (/(\\r\\n|\\n)$/.test(text) && !out.endsWith(eol)) out += eol; // 剔掉的若是最后一条,别把文件末尾的换行也带走\n return { text: out, removed };\n}\n\n/** 一条 patch 条目是不是「微信 MCP」:跑 wemind-mcp 的,或 serverName 为 wechat 的 mcp-client。 */\nfunction isWechatMcpEntry(body: string): boolean {\n if (/wemind-mcp|@wemind\\/wechat-mcp/i.test(body)) return true;\n return (\n /name:\\s*['\"]?@deepseek-ai\\/dsh-mcp-client/.test(body) &&\n /serverName:\\s*['\"]?wechat['\"]?\\s*(#.*)?$/m.test(body)\n );\n}\n\n/** 把真实 profile 用户层复制进某个沙箱 home(复制前剔除微信 MCP 条目;没有命中时逐字节原样)——三个调用方共用同一段路径与剔除逻辑。\n * `src` 不存在时抛错,调用方按 best-effort 包 try/catch。返回被剔除的条目(供调用方记日志)。 */\nexport function copyProfilePatch(realHome: string, profile: string, destHome: string): string[] {\n const src = path.join(realHome, \"profiles\", profile, \"cordis.patch.yml\");\n const dst = path.join(destHome, \"profiles\", profile, \"cordis.patch.yml\");\n const raw = fs.readFileSync(src);\n const { text, removed } = stripWechatMcpEntries(raw.toString(\"utf-8\"));\n const out = removed.length ? Buffer.from(text, \"utf-8\") : raw;\n fs.mkdirSync(path.dirname(dst), { recursive: true });\n const same = fs.existsSync(dst) && fs.readFileSync(dst).equals(out);\n if (!same) fs.writeFileSync(dst, out);\n return removed;\n}\n\n/** 给用户的提示(缺 `- id: acp` 时用):说清后果 + 给出可直接粘贴的片段(值取自 agent-default-model,读不到就给占位)。 */\nexport function acpOverrideHint(realHome: string, profile: string): string | null {\n if (!realHome || !profile) return null;\n const file = path.join(realHome, \"profiles\", profile, \"cordis.patch.yml\");\n let text: string;\n try {\n text = fs.readFileSync(file, \"utf-8\");\n } catch {\n return null; // 还没有这份 profile(dsh 首次启动才生成):先不提示,「改哪份文件」还说不清\n }\n if (/^[ \\t]*-[ \\t]+id:[ \\t]*acp[ \\t]*(#.*)?$/m.test(text)) return null;\n const derived = entryConfig(text, /^\\s*-\\s+id:\\s*agent-default-model\\s*(#.*)?$/);\n const pair = derived ? (JSON.parse(derived) as [string, string]) : null;\n const provider = pair?.[0] ?? \"<provider>\";\n const model = pair?.[1] ?? \"<model>\";\n // 新 profile 是「模板注释 + `[]`」:`[]` 是已写完的 flow 根节点,后面不能再接块序列项——照「加到末尾」写出来是非法 YAML,dsh 直接起不来\n const flowRoot = /^[ \\t]*\\[[ \\t]*\\][ \\t]*(#.*)?$/m.test(text);\n const lines = [\n `提示:${file} 没有配置 - id: acp(ACP 桥的默认模型),终端 dsh --profile ${profile} 与微信大脑都会落到 deepseek-official(要 API key)。`,\n flowRoot\n ? `把文件里 [] 那一行替换成下面这段([] 是已经写完的空列表,后面不能再接条目;替换后它就是你补的第一条),两边行为就一致:`\n : `把下面这段加到那份文件末尾(插件文档写明的覆盖点),两边行为就一致:`,\n `- id: acp`,\n ` config:`,\n ` provider: ${JSON.stringify(provider)}`,\n ` model: ${JSON.stringify(model)}`,\n ];\n if (!pair)\n lines.push(\n `(provider/model 是占位符:换成你日常 dsh 用的路由,比如桌面端 profile 里 llm-pi-ai / agent-default-model 写的那组)`,\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * 已有 `- id: acp`、但它写的 model 不在该 provider 的模型目录里时的提示。\n *\n * 为什么会发生:桌面端升级会重写 `llm-pi-ai` 的 provider 目录(模型下架/改名),而用户自己补的 `- id: acp`\n * 是独立的一条、不会被桌面端自动迁移。DSH 的 ACP 桥按 model 找路由,找不到就在 `session/new` 报\n * `no configured model`——单看错误码很像 agent 坏了,实际只是这一行过期。\n * 与 `acpOverrideHint` 一样:**只读、只提示**,不替用户改真实 profile。\n */\nexport function acpModelHint(realHome: string, profile: string): string | null {\n if (!realHome || !profile) return null;\n const file = path.join(realHome, \"profiles\", profile, \"cordis.patch.yml\");\n let text: string;\n try {\n text = fs.readFileSync(file, \"utf-8\");\n } catch {\n return null;\n }\n const acp = entryConfig(text, /^\\s*-\\s+id:\\s*acp\\s*(#.*)?$/);\n if (!acp) return null; // 缺 `- id: acp` 由 acpOverrideHint 负责\n const [provider, model] = JSON.parse(acp) as [string, string];\n const models = modelsForProvider(text, provider);\n if (!models.includes(model)) {\n const def = entryConfig(text, /^\\s*-\\s+id:\\s*agent-default-model\\s*(#.*)?$/);\n const defPair = def ? (JSON.parse(def) as [string, string]) : null;\n const pool = defPair?.[0] === provider ? [defPair[1], ...models] : models;\n const suggestions = [...new Set(pool.filter((m) => models.includes(m)))].slice(0, 3);\n const lines = [\n `提示:${file} 的 - id: acp 用的是 provider=${provider} model=${JSON.stringify(model)},但这份 profile 的 provider 目录里找不到这个模型(桌面端更新模型列表后会这样,ACP 会话会报 no configured model)。`,\n suggestions.length\n ? `把 config.model 换成目录里现有的(例如 ${suggestions.map((m) => JSON.stringify(m)).join(\" / \")}),或按你日常 dsh 正在用的模型重写这一条:`\n : `把 config.model 换成这个 provider 目录里现有的模型,或按你日常 dsh 正在用的模型重写这一条:`,\n `- id: acp`,\n ` config:`,\n ` provider: ${JSON.stringify(provider)}`,\n ` model: ${JSON.stringify(suggestions[0] ?? \"<model>\")}`,\n ];\n return lines.join(\"\\n\");\n }\n return null;\n}\n\n/** 从 provider 目录里取该 provider 下 `models:` 的 `- id:` 清单(块式 YAML;provider 找不到就空)。 */\nfunction modelsForProvider(text: string, provider: string): string[] {\n const lines = text.split(/\\r?\\n/);\n const esc = provider.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const head = new RegExp(`^(\\\\s*)[\"']?${esc}[\"']?\\\\s*:\\\\s*(#.*)?$`);\n for (let i = 0; i < lines.length; i++) {\n const m = head.exec(lines[i]);\n if (!m) continue;\n const indent = m[1].length;\n const out: string[] = [];\n for (let j = i + 1; j < lines.length; j++) {\n const line = lines[j];\n if (!line.trim() || /^\\s*#/.test(line)) continue;\n const lineIndent = (line.match(/^\\s*/) ?? [\"\"])[0].length;\n if (lineIndent <= indent) break;\n const id = /^\\s*-\\s+id:\\s*(.+?)\\s*(?:#.*)?$/.exec(line);\n if (id) out.push(id[1].replace(/^[\"']|[\"']$/g, \"\"));\n }\n return out;\n }\n return [];\n}\n\n/** 读某个 patch 条目(按起始行正则定位)的 config.provider/model;缺一个就 null。 */\nfunction entryConfig(text: string, head: RegExp): string | null {\n let inside = false;\n let provider = \"\";\n let model = \"\";\n for (const line of text.split(/\\r?\\n/)) {\n if (!inside) {\n if (head.test(line)) inside = true;\n continue;\n }\n // patch 列表的下一个条目(顶层 `- ` 或别的 `- id:`)→ 当前条目结束\n if (/^-\\s+/.test(line) || /^\\s*-\\s+id:/.test(line)) break;\n if (!provider) provider = scalar(line, \"provider\");\n if (!model) model = scalar(line, \"model\");\n }\n if (!provider || !model) return null;\n return JSON.stringify([provider, model]);\n}\n\n/** 取 `key: value` 的标量值(去引号与行尾注释);块式与 flow 式都认(`config: {provider: magpie, model: auto/x}` 也是合法 YAML,DSH 接管)。 */\nfunction scalar(line: string, key: string): string {\n const block = new RegExp(`^\\\\s+${key}:\\\\s+(.+)$`).exec(line);\n const m = block ?? new RegExp(`[{,]\\\\s*${key}:\\\\s*(\"[^\"]*\"|'[^']*'|[^,}]+)`).exec(line);\n if (!m) return \"\";\n return m[1]\n .replace(/\\s+#.*$/, \"\")\n .trim()\n .replace(/^[\"']|[\"']$/g, \"\");\n}\n","import path from \"node:path\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport { spawnSync } from \"node:child_process\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { agentConfigFor, type AgentRole, type BrainConfig } from \"./brain-config.js\";\nimport { clineCredentialEnv } from \"./cline-credentials.js\";\nimport { readProfileDefaultModel } from \"./profile-model.js\";\n\n/**\n * ACP agent 与解析。\n *\n * WeMind 的大脑通过**标准 ACP(Agent Client Protocol,JSON-RPC over stdio)**驱动一个 agent CLI。ACP 是通用协议(kiro-cli、gemini-cli 等都支持),因此接入任意兼容 ACP 的 agent 不需要改协议代码——只需告诉我们三件事:① 二进制名;② 进入 ACP 模式的启动参数;③ 该 agent 读取「配置/数据目录」的环境变量名(我们把它指向 brain 沙箱)。\n *\n * 选择与覆盖(优先级从高到低):\n * ① 环境变量(一次性覆盖;临时试别的 agent 用它,别写进 profile):\n * - `WEMIND_BRAIN` 选「对话大脑」agent(默认 `auto`:自动探测已安装的 agent,见 AUTO_PROBE_ORDER)\n * - `WEMIND_WORKER_BRAIN` 选「任务 worker」agent(未设则回落 `WEMIND_BRAIN`;与文件侧 `worker ?? 顶层` 的回退规则对称)\n * - `WEMIND_BRAIN_BIN` 覆盖二进制(绝对路径或 PATH 上的名字)\n * - `WEMIND_BRAIN_ARGS` 覆盖启动参数(空格分隔;**整段替换**)\n * - `WEMIND_BRAIN_EXTRA_ARGS` 追加启动参数(空格分隔;保留预设在前面,适合只加一个开关)\n * - `WEMIND_BRAIN_HOME_ENV` 覆盖「配置目录环境变量名」\n * - `WEMIND_BRAIN_SHELL` 是否经 shell 启动(1/true)\n * - `WEMIND_BRAIN_INHERIT` 是否继承真实配置目录的记忆/规则(默认 1;0 关闭)\n * - `WEMIND_BRAIN_REAL_HOME` 覆盖被继承的真实配置目录(默认取该 agent 的 `realHome`)\n * - `WEMIND_BRAIN_INHERIT_DIRS` 覆盖继承的子目录清单(逗号/空格分隔)\n * - `WEMIND_ACP_SESSION_MODE` 覆盖预设的会话模式(`session/set_mode`):`0`/`false`/`off` 关掉该调用,其它非空值 = 用该 modeId\n * - `KIRO_CLI_BIN` 旧的 kiro 专用覆盖(仅 kiro agent、且未设 WEMIND_BRAIN_BIN 时仍生效)\n * ② 账号级持久配置(`<数据目录>/brain.json`,见 brain-config.ts):顶层 = 对话大脑,`worker` 段 = 任务 worker(缺省跟随顶层);每段可带 `agent` / `bin` / `args` / `shell` / `homeEnvVar`,另有按 agent 名存启动覆盖的 `presets` 段。写入磁盘后任何入口重启都能复现,切换用 `wemind brain set <agent 名>`。\n * ③ `auto`(未配置时的兜底):按 AUTO_PROBE_ORDER 取第一个二进制可解析的 agent;一个都没有则标记 unavailable,由启动校验明确报错(不再「静默跑起来、第一条消息才炸」)。\n * 另:未知的 `WEMIND_BRAIN` 值不再静默回退 kiro,而是 `known:false`,交由启动校验报错。\n *\n * 角色(role):同一份 agent 表既是「对话大脑」也是「任务 worker」的来源;`resolveAgent()` = 对话大脑,\n * `resolveAgentFor(cfg, \"worker\")` = 任务 worker。命令驱动的切换用 `resolvePresetByName()` —— 它**绕开环境变量**,\n * 否则用户点了切换、env 却压着它,会出现「看着成功、实际没生效」。\n */\n\nexport type AcpAgentPreset = {\n name: string;\n bin: string; // PATH 上的二进制名(非 Windows)\n binWin?: string; // Windows 下可执行名(默认在 bin 后加 .exe)\n fallbackBinPaths?: string[]; // PATH 上找不到时的已知安装位置(如官方安装器的固定目录)\n requiresBins?: string[]; // 除 bin 外还必须在 PATH 上解析到的运行时(如 pi-acp 内部 spawn `pi`;只装适配器不够)\n args: string[]; // 进入 ACP 模式的启动参数\n homeEnvVar?: string; // agent 读取其配置/数据目录的环境变量名;指向 brain 沙箱。留空=不设置\n shell?: boolean; // 是否经 shell 启动(npx/.cmd/.bat 类适配器在 Windows 需要 true)\n // —— 继承「记忆/规则/技能」配置(让微信大脑与日常使用读到同样内容)——\n realHome?: string; // 该 agent 真实配置目录(被继承的来源)。不同 agent 布局不同,故下沉到 agent\n inheritDirs?: string[]; // 从 realHome junction 进沙箱的「知识/记忆」类子目录(不含 MCP/会话目录)\n inheritFiles?: string[]; // 从 realHome 拷进沙箱配置目录的「单文件」记忆(如 AGENTS.md;目录式记忆用 inheritDirs)\n // —— 沙箱约定(不同 agent 的配置目录位置与 MCP 文件格式不同)——\n configSubdir?: string; // agent 配置目录相对沙箱根的子路径(kiro 空=沙箱根;opencode \"opencode\")\n mcpFormat?: \"kiro\" | \"opencode\"; // MCP 配置文件的组织格式(默认 kiro)\n mcpConfigFile?: string; // MCP 配置文件相对「配置目录」的路径(默认 settings/mcp.json)\n catalogFile?: string; // 可选 MCP 目录来源文件相对 realHome 的路径(默认 settings/mcp.json)\n env?: Record<string, string>; // 额外注入的环境变量(强制;值里 %HOME% → 沙箱根目录)\n envDefaults?: Record<string, string>; // 默认值:父进程已设同名变量时不覆盖。审批类开关(DSH_PERMISSION_MODE / INITIAL_AGENT_MODE)用它,部署方可用环境变量收紧权限\n credentialEnv?: (\n realHome: string | undefined,\n parentEnv: NodeJS.ProcessEnv,\n ) => Record<string, string>; // 从 agent 自己的凭据文件派生环境变量(仅父进程未显式提供时;如 Cline 自定义 provider 的 CLINE_PROVIDER/CLINE_API_KEY)\n // —— 协议/能力差异(按 agent 覆盖;默认保持 kiro/opencode 旧行为)——\n restore?: \"load\" | \"resume\"; // 恢复会话的方式:load(默认)或 resume(如 DSH)\n replayOnLoad?: boolean; // session/load|resume 返回后仍会异步回放历史帧(gemini 的 loadSession 里 streamHistory 不 await):恢复后先等回放帧安静下来再提问,否则历史会被拼进本轮答复\n firstFrameMs?: number; // 首帧超时覆盖(默认 25s;慢启动/committed-only agent 可放宽)\n heartbeatMs?: number; // 静默期心跳间隔覆盖(默认 15s;有 thought/tool/usage 帧的 agent 通常不需要)\n images?: boolean; // 是否接受图片输入(默认 true;DSH 官方 0.2.1-rc.2 为 false)\n credentialFiles?: string[]; // 需要从 realHome 同步进沙箱的凭据文件(相对配置目录;新者胜)\n identityFiles?: string[]; // 必须与 realHome 保持一致的标识文件(相对配置目录;内容不同即覆盖)\n settingsBase?: string; // 以 realHome 下该文件为基底生成沙箱设置(剥离 MCP/插件/自定义供应商并关自动更新)\n settingsStrip?: string[]; // settingsBase 基底里要删掉的键(默认见 DEFAULT_SETTINGS_STRIP;gemini 只删 mcpServers,保留 model/security)\n settingsDefaults?: Record<string, unknown>; // 沙箱 settings.json 的默认键:真实配置里没有该键时补上(用户显式设过则保留用户的值;只改沙箱副本)\n model?: string; // 默认模型值(如 DSH 的 [\"deepseek-account\",\"deepseek-flash\"]):经 session/set_config_option(agent 自己的 ACP 接口)在会话建立后下发一次;可用 brain.json / `brain set --model` 覆盖,`auto` = 跟随默认;配了 realProfile 时默认值优先从真实 profile 读\n modelArg?: string; // 不支持 session/set_config_option 的 agent:模型改为追加到启动参数(如 gemini 的 \"--model\"),不再走 ACP 下发\n realProfile?: string; // 真实 home 里该 agent 的 profile 名(`profiles/<名>/cordis.patch.yml` 是用户/桌面端维护的用户层:沙箱**原样复制**、不追加条目,默认模型从它读——优先 `- id: acp`,否则 `agent-default-model`);如 DSH 的 \"acp\"\n mcpViaAcp?: boolean; // MCP 走 ACP session/new|resume 的 mcpServers(不写配置文件;如 DSH)\n sessionMode?: string; // 会话建立/恢复后经 `session/set_mode` 切到的模式 id(agent 自己的 ACP 接口);用于「免审批」模式:claude-code 的 bypassPermissions、codex 的 agent-full-access。留空=不调用\n kiroEngine?: boolean; // kiro:`acp` 的 v2 / v3 引擎启动参数与免审批方式不同(v2 `--trust-all-tools`;v3 用沙箱 settings/permissions.yaml 的 allow-all)\n isolateSessions?: boolean; // 会话持久化 root 用 agent 自己的 `--patch` 指到账号数据目录下(DSH:与同沙箱其他 agent 的会话文件分开,见 docs/接入-DeepSeek.md)\n};\n\n/** settingsBase 的默认剥离清单:MCP 必须由沙箱权威决定(防 wechat 回环);插件/自定义供应商可能自带 MCP 或明文密钥;model 可能指向已剥离的供应商。 */\nexport const DEFAULT_SETTINGS_STRIP = [\"mcpServers\", \"enabledPlugins\", \"providers\", \"model\"];\n\n/**\n * 已知 ACP agent。\n * - 内置 9 个预设都**至少过了探针级实测**(kiro / opencode / deepseek / qoder / cline / gemini 另有真机使用或 E2E 记录)。\n * - 包名/参数/ACP 能力仍随上游版本变化:升级 agent 后按 docs/接入其它-ACP-Agent.md 复验;自定义 agent 需自行核对官方参数。\n * - 区别两类:① **原生 ACP**(CLI 自带 ACP 模式,直接起,如 kiro、gemini);② **需适配器**(CLI 本身不说 ACP,要起一个适配器进程,如 Claude Code、Codex)。\n */\nexport const ACP_AGENT_PRESETS: Record<string, AcpAgentPreset> = {\n // —— 原生 ACP ——\n kiro: {\n name: \"kiro\",\n bin: \"kiro-cli\",\n binWin: \"kiro-cli.exe\",\n args: [\"acp\", \"--trust-all-tools\"],\n homeEnvVar: \"KIRO_HOME\",\n realHome: path.join(os.homedir(), \".kiro\"),\n inheritDirs: [\"steering\", \"memories\", \"skills\", \"agents\", \"powers\", \"tasks\", \"extensions\"],\n kiroEngine: true,\n },\n // Gemini CLI(原生 ACP,0.66.0 已实测)。`--acp` 取代已弃用的 `--experimental-acp`;`--approval-mode yolo` 自动批准所有工具调用(与 `--yolo` 互斥,二选一);`--skip-trust` 信任当前工作区——**未信任的目录会把 yolo 静默降回 default,并且连 MCP 都不加载**(用户级 server 也被抑制),所以必须带上(或用 GEMINI_CLI_TRUST_WORKSPACE=true)。\n // - 沙箱:`GEMINI_CLI_HOME` 指向沙箱根,但 Gemini 的配置目录是 `<它>/.gemini`(实测),故 configSubdir=\".gemini\";`~/.gemini` 的 skills/ 与 GEMINI.md 是它的全局技能与记忆。\n // - MCP:读配置目录 settings.json 的 `mcpServers`(kiro 格式)。ACP `session/new` 的 mcpServers 实测也能用,但 `initialize` 只声明 http/sse、不声明 acp——走它自己的配置文件更稳。\n // - 设置:以真实 settings.json 为基底(保留 model / security / ui),只剥 `mcpServers`(MCP 必须来自沙箱 curated,防 wechat 回环),并关自动更新。认证走 `security.auth.selectedType`;API key 用户同步 `.env`(GEMINI_API_KEY / GOOGLE_GEMINI_BASE_URL),Google 登录用户同步 `oauth_creds.json` + `google_accounts.json`——settings 镜像了 `oauth-personal` 却缺 token 时,它会卡在登录流程、连 ACP initialize 都不回。\n // - 模型:不支持 `session/set_config_option`(-32601),`--model` 走启动参数(modelArg);默认不传,跟随 settings.json 的 model.name。\n // - 恢复:loadSession=true、强杀后 session/load 能接回上下文;但 load 返回后它还会**异步回放历史**(streamHistory 不 await),replayOnLoad 让恢复后先等回放结束。\n // - `session/close` 未实现(-32601,WeMind 不调用);图片实测可收;工具调用 0 反向请求。\n gemini: {\n name: \"gemini\",\n bin: \"gemini\",\n binWin: \"gemini.cmd\",\n args: [\"--acp\", \"--approval-mode\", \"yolo\", \"--skip-trust\"],\n homeEnvVar: \"GEMINI_CLI_HOME\",\n realHome: path.join(os.homedir(), \".gemini\"),\n configSubdir: \".gemini\", // GEMINI_CLI_HOME 是「家目录」,Gemini 在它下面建 .gemini/\n inheritDirs: [\"skills\"],\n inheritFiles: [\"GEMINI.md\"],\n mcpFormat: \"kiro\",\n mcpConfigFile: \"settings.json\",\n catalogFile: \"settings.json\",\n settingsBase: \"settings.json\",\n settingsStrip: [\"mcpServers\"],\n credentialFiles: [\".env\", \"oauth_creds.json\", \"google_accounts.json\"],\n modelArg: \"--model\",\n replayOnLoad: true,\n shell: true,\n },\n // OpenCode:原生 ACP(`opencode acp`)。\n // - Windows 上是 npm shim(PATH 只有 opencode.cmd),故 shell:true + 裸名 binWin。\n // - 配置目录走 XDG_CONFIG_HOME(指向沙箱 → 配置落在 <沙箱>/opencode/),可真正替换默认 global 目录实现隔离;(OPENCODE_CONFIG_DIR 只「追加」不替换 global,无法隔离,故不用它。)\n // - MCP 配置文件是 opencode.json 的 mcp.servers(格式与 kiro 的 settings/mcp.json 不同)。\n // - 记忆/技能为目录式 skills/agents/commands + 单文件 AGENTS.md。\n // 详见 docs/接入-OpenCode.md(含 M1 实测)。\n opencode: {\n name: \"opencode\",\n bin: \"opencode\",\n binWin: \"opencode\",\n args: [\"acp\"],\n shell: true,\n homeEnvVar: \"XDG_CONFIG_HOME\",\n realHome: path.join(os.homedir(), \".config\", \"opencode\"),\n inheritDirs: [\"skills\", \"agents\", \"commands\"],\n inheritFiles: [\"AGENTS.md\"],\n configSubdir: \"opencode\",\n mcpFormat: \"opencode\",\n mcpConfigFile: \"opencode.json\",\n catalogFile: \"opencode.json\",\n env: { OPENCODE_DISABLE_PROJECT_CONFIG: \"1\" }, // 不从 cwd 祖先目录发现用户 opencode.json\n },\n // DeepSeek Harness(官方 ACP profile)。\n // - 桌面端自带 `dsh.cmd`(只有 .cmd,没有无扩展名 dsh),故 binWin 必须写全;\n // - DSH_HOME 指向 brain 沙箱;`.credentials.yaml` 由沙箱同步(桌面登录态);\n // - 无 session/load,用 session/resume;image:false(收图需上层拦截);\n // - **模型:跟着用户自己 dsh 的配置走(WeMind 只读、只复制,不替 agent 合成配置)。** 桌面端把 provider 目录与默认模型写进 `~/.dsh/profiles/<profile>/cordis.patch.yml`。\n // `dsh --profile acp` 的 ACP 桥插件(`@deepseek-ai/dsh-acp`)默认模型写死在它**自己的 bundle patch**里:`- id: acp` 的\n // `config: { provider: deepseek-official, model: deepseek-v4-flash }`;profile 用户层只覆盖 `llm-pi-ai` / `agent-default-model`,\n // `- id: acp` 要靠**用户自己**补(插件 README:a later patch can replace that row's complete config)——这就是\n // 「agent-default-model 写着 magpie、session/new 仍报 deepseek-official、一 prompt 就 no API key」的原因(2026-10-29 拆开 app.asar 核实)。\n // 所以 `realProfile: \"acp\"`:沙箱**原样复制**真实 profile 用户层(否则沙箱是空壳 `[]`,连 magpie 路由都没有;不追加条目),\n // 缺 `- id: acp` 时由 `wemind brain` 提示用户补到真实 profile(`acpOverrideHint`)。\n // 默认模型从这份配置读(优先 `- id: acp`,否则 `agent-default-model`)→ 用户日常用哪个模型,微信大脑就用哪个。\n // - 读不到时回退到下面的 `model`(deepseek-account/deepseek-flash:同样显示为 DeepSeek-V4.1-Flash,走账号登录);\n // 显式换模型用 `wemind brain set deepseek --model \"…\"`,`--model auto` = 跟随真实 profile 的默认。\n // `set_config_option`(agent 自己的 ACP 接口)在会话建立后按同一个值下发一次:幂等,也是显式覆盖的通道。\n // - magpie 路由走桌面端的本机模型网关(127.0.0.1:3425),**桌面端要在运行**——和用户日常 dsh 用 magpie 时的前提一样。\n // 详见 docs/接入-DeepSeek.md(含 0.2.1-rc.2 / 0.2.1-rc.3 真机实测)。\n deepseek: {\n name: \"deepseek\",\n bin: \"dsh\",\n binWin: \"dsh.cmd\",\n args: [\"--profile\", \"acp\"],\n shell: true,\n homeEnvVar: \"DSH_HOME\",\n realHome: path.join(os.homedir(), \".dsh\"),\n inheritDirs: [\"skills\"],\n inheritFiles: [\"AGENTS.md\"],\n credentialFiles: [\".credentials.yaml\"],\n restore: \"resume\",\n firstFrameMs: 60_000,\n heartbeatMs: 60_000,\n images: false,\n realProfile: \"acp\",\n model: '[\"deepseek-account\",\"deepseek-flash\"]',\n mcpViaAcp: true, // 不写 mcp.json;激活集转成 ACP mcpServers 传给 session/new|resume\n isolateSessions: true, // `--patch` 把 session-persistence-jsonl.root 指到账号数据目录(见 docs/接入-DeepSeek.md)\n // 免审批:DSH 没有启动 flag,也没有会话级权限配置项;`DSH_PERMISSION_MODE` 是官方 bundle 读的默认值来源\n // (文档正文未收录,升级后若失效见 docs/接入-DeepSeek.md),取值 danger-full-access = 绕过文件沙箱、不产生提权请求。\n // 注意 `approval.policy: never` 是「不问并拒绝」,不能单独拿来放行——这里只用 danger-full-access。\n // 放在 envDefaults:daemon 进程里显式设了同名变量(如 workspace-write)时以用户为准(可收紧)。\n envDefaults: { DSH_PERMISSION_MODE: \"danger-full-access\" },\n },\n\n // Qoder CLI(官方原生 ACP,qodercli 1.1.78 实测)。\n // - 直接起 qodercli.exe:`qoder` 是分发器(cmd → powershell → qodercli),多一层管道且会与 IDE 命令混淆;\n // - QODER_CONFIG_DIR 指向 <沙箱>/qoder:与 Qoder Desktop 共用的 ~/.qoder 绝不写入;\n // - 登录态要 .auth/user + .auth/machine_id 两个文件(只带 user 会 Authentication required);\n // - MCP 在 settings.json 的 mcpServers(与其它设置同文件),改后需重启进程 → 加载/卸载 MCP 的 restart 已覆盖;\n // - --yolo 下工具调用不发反向请求;capabilities 有 load/resume/image;\n // - providers(BYOK,含明文密钥)与 model 不继承,见 docs/接入-Qoder.md §5。\n qoder: {\n name: \"qoder\",\n bin: \"qodercli\",\n binWin: \"qodercli.exe\",\n // 官方安装器的固定位置;pm2/服务里 PATH 可能不含它,故直接兜底\n fallbackBinPaths:\n process.platform === \"win32\"\n ? [path.join(os.homedir(), \".qoder\", \"bin\", \"qodercli\", \"qodercli.exe\")]\n : [],\n args: [\"--acp\", \"--yolo\"],\n env: { QODER_CONFIG_DIR: `%HOME%${path.sep}qoder` },\n realHome: path.join(os.homedir(), \".qoder\"),\n // memory/ 是 Qoder agent 的用户画像/偏好记忆(实测存在),与日常使用共用一份(junction);skills/agents/commands/AGENTS.md 在 Qoder 1.1.78 的本机不存在 → 当前为空操作,保留以便上游新增后自动生效\n inheritDirs: [\"memory\", \"skills\", \"agents\", \"commands\"],\n inheritFiles: [\"AGENTS.md\"],\n configSubdir: \"qoder\",\n mcpFormat: \"kiro\",\n mcpConfigFile: \"settings.json\",\n catalogFile: \"settings.json\",\n settingsBase: \"settings.json\",\n credentialFiles: [\".auth/user\"],\n identityFiles: [\".auth/machine_id\"],\n },\n\n // Cline CLI(官方原生 ACP,cline 3.0.74 实测;`cline-acp` 适配器已归档,官方让直接用 `cline --acp`)。\n // - `CLINE_DIR` 指到 <沙箱>/cline(不用沙箱根:大脑 cwd 就是沙箱根,避免把 agent 自己的配置目录当工作区);\n // - 登录态:ACP 只自动恢复它自己的 3 种登录(cline / cline-pass / openai-codex,读 data/settings/providers.json);\n // 自定义/API-key provider(如 openai-compatible)必须由 daemon 进程环境提供 CLINE_PROVIDER + CLINE_API_KEY,\n // 否则 session/new 直接 `-32000 Authentication required`(Cline 不会去看 lastUsedProvider);\n // - MCP:data/settings/cline_mcp_settings.json({mcpServers},与 kiro 同格式);沙箱用 CLINE_MCP_SETTINGS_PATH 指到\n // <沙箱>/cline/mcp.json,真实文件不动。ACP `session/new.mcpServers` 不被 Cline 使用(它只存不用)→ mcpViaAcp 保持 false;\n // - 免审批:`--auto-approve true`(Cline 自己文档给的 ACP 启动开关);宿主应答器仍默认拒绝权限请求;\n // - 能力:loadSession / images 可用,重启后 session/load 能接回上下文;session/close 未实现(-32601,WeMind 不调用)。\n cline: {\n name: \"cline\",\n bin: \"cline\",\n binWin: \"cline.cmd\",\n args: [\"--acp\", \"--auto-approve\", \"true\"],\n shell: true,\n // 3.0.74 实测:initialize 广告 image:true,但 ACP prompt 的 de() 只保留 text 块(image 被直接丢弃),\n // 模型只会看到「用户发来一张图片」的文字提示。按「实际不支持」处理,让 daemon 友好拒绝而不是让模型猜。\n images: false,\n env: {\n CLINE_DIR: `%HOME%${path.sep}cline`,\n CLINE_DATA_DIR: `%HOME%${path.sep}cline${path.sep}data`,\n CLINE_MCP_SETTINGS_PATH: `%HOME%${path.sep}cline${path.sep}mcp.json`,\n },\n realHome: path.join(os.homedir(), \".cline\"),\n // 用户级规则/技能/agent 定义与日常 Cline 共用一份;hooks/plugins 不继承(插件可自带 MCP,沙箱不允许 wechat 之外的意外入口)\n inheritDirs: [\"rules\", \"skills\", \"agents\"],\n configSubdir: \"cline\",\n mcpFormat: \"kiro\",\n mcpConfigFile: \"mcp.json\",\n catalogFile: \"data/settings/cline_mcp_settings.json\",\n // providers.json:登录态(OAuth / API-key);models.json:provider 的模型目录缓存——\n // 缺它时 Cline 的 session/new 会回退到 gpt-4o(自定义 provider 下 prompt 直接报「magpie knows no model \"gpt-4o\"」)\n credentialFiles: [\"data/settings/providers.json\", \"data/settings/models.json\"],\n // 自定义/API-key provider:Cline ACP 不读 providers.json 的 lastUsedProvider,按官方 ACP 逃生口补 env\n // (父进程显式设了 CLINE_PROVIDER/CLINE_API_KEY 时完全不接管;OAuth 条目没有 apiKey,返回空)\n credentialEnv: clineCredentialEnv,\n },\n\n // —— 需适配器(CLI 不原生支持 ACP,经官方/第三方适配器桥接)——\n // pi coding agent(@earendil-works/pi-coding-agent)经社区适配器 pi-acp 接入:适配器 spawn `pi --mode rpc`,在 ACP 与 pi RPC 之间桥接。\n // 2026-10-30 探针 + 2026-11-01 复跑(pi-acp 0.0.34 + pi 1.4.0,见 docs/接入-pi.md):\n // - session/load 原生支持(强杀后上下文接上);image:true;首帧 0–3ms;有 thought/tool 帧;无反向请求——pi 默认不做逐次工具审批(官方 security.md 明说),无需 yolo flag / sessionMode;\n // - 模型跟随用户 pi 配置(settings.json 的 defaultProvider/defaultModel 随凭据原样同步进沙箱);覆盖用 `wemind brain set pi --model \"<provider>/<model>\"`(session/set_config_option 的 configId 就叫 model);\n // - MCP:pi 读 <PI_CODING_AGENT_DIR>/mcp.json(`pi mcp list` 实测;不是 settings/mcp.json),格式与 kiro 相同 {mcpServers:{名:{command,args,env}}};不设 mcpViaAcp(pi-acp 只把 mcpServers 存进会话、从不读取);\n // - 适配器只是桥:真正进程是 PATH 上的 pi 本体(requiresBins 会在缺 pi 时提前报错,而不是等 session/new 才炸)。\n // - 适配器的会话映射表固定写在真实 home 的 ~/.pi/pi-acp/session-map.json(不随沙箱走):跨账号共享的只是 id→会话文件的映射,会话本体仍在各自沙箱的 sessions/ 下。\n // - 适配器类不参与 auto 探测(装好适配器 ≠ 已登录/配好模型),用 `wemind brain set|switch pi` 显式选用。\n pi: {\n name: \"pi\",\n bin: \"pi-acp\",\n binWin: \"pi-acp.cmd\", // npm 全局 shim 只有 .cmd,onPath 按精确文件名判定\n requiresBins: [\"pi\"], // pi-acp 内部 spawn `pi --mode rpc`;只装适配器不够\n args: [],\n shell: true,\n homeEnvVar: \"PI_CODING_AGENT_DIR\", // pi 的配置目录 env(默认 ~/.pi/agent)→ 指向沙箱根\n realHome: path.join(os.homedir(), \".pi\", \"agent\"),\n inheritDirs: [\"skills\"],\n inheritFiles: [\"AGENTS.md\"], // pi 读 <agent-dir>/AGENTS.md 作跨目录全局指令\n // 原样同步:默认模型/供应商(settings.json)、登录态(auth.json)、自定义供应商与模型目录(models*.json,可能含网关地址/密钥)\n credentialFiles: [\"settings.json\", \"auth.json\", \"models.json\", \"models-store.json\"],\n // pi-acp 把 pi 的启动横幅(## Context / ## Skills …)当作 agent_message_chunk 发出来,那是给人看的启动信息,进微信回复就是噪声。\n // pi 自己的 `quietStartup: true` 关掉它(docs/settings.md);写进沙箱副本,用户显式设过就保留用户的值。\n settingsDefaults: { quietStartup: true },\n // pi 的 MCP 是 <agent-dir>/mcp.json(不是 settings/mcp.json);kiro 格式与 pi 兼容\n mcpConfigFile: \"mcp.json\",\n catalogFile: \"mcp.json\",\n },\n // Claude Code:官方 ACP 适配器 @agentclientprotocol/claude-agent-acp(bin: claude-agent-acp;**Node ≥22**)。\n // - **全局安装,不用 npx**(口径同 codex):daemon 常驻、worker 每条任务起进程,npx 每次冷启动都要问 registry/现下包,离线不可用且版本漂移;\n // `npm i -g @agentclientprotocol/claude-agent-acp`。适配器自带 @anthropic-ai/claude-agent-sdk(含平台原生二进制);\n // 若装的时候漏了 optional 依赖(native binary not found),可用 CLAUDE_CODE_EXECUTABLE 指向本机已装的 claude 二进制。\n // - 免审批:适配器**不解析** --dangerously-skip-permissions 之类 CLI flag;会话建立/恢复后走 `session/set_mode {modeId:\"bypassPermissions\"}`(见 sessionMode)。\n // 以 root 运行且未设 IS_SANDBOX=1 时该模式不可用——会话回包的 availableModes 里没有它,applySessionMode 会跳过并记日志。\n // - 沙箱:CLAUDE_CONFIG_DIR 指向 <BRAIN_HOME>/claude(实测会把 ~/.claude.json 一起搬进该目录,用户级 mcpServers 不会漏进大脑);\n // CLAUDE.md/skills/agents/commands 与日常使用共享;OAuth 登录态 .credentials.json 同步进沙箱。\n // **不复制 settings.json**:它常含网关 token(env),按 §4.9 密钥由 daemon 进程环境提供;需要网关时设 ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL。\n // - MCP:走 ACP session/new|load 的 mcpServers(mcpViaAcp),不写配置文件;目录取真实 ~/.claude.json 的 mcpServers(`catalogFile` 用 `..` 回到 home)。\n // 探针实测 stdio MCP 可用(尽管 initialize 的 mcpCapabilities 只声明 http/sse——能力字段不可信的又一例)。\n // - 2026-10-30 探针实测(claude-agent-acp 0.99.1 / Claude Code 2.1.314,Windows):\n // loadSession ✅、sessionCapabilities.resume/close/delete/fork/list ✅、image ✅、stdio MCP ✅、bypassPermissions 下 0 反向请求;\n // 重启后 session/load 上下文保留;session/new ~1.2s、首帧 ~2ms、首段正文 ~8–12s。真机微信单轮 E2E 已过(2026-10-31);详见 docs/接入-ClaudeCode.md(重启续聊/MCP/任务待复测)。\n \"claude-code\": {\n name: \"claude-code\",\n bin: \"claude-agent-acp\",\n binWin: \"claude-agent-acp.cmd\",\n args: [],\n shell: true,\n env: { CLAUDE_CONFIG_DIR: `%HOME%${path.sep}claude` },\n realHome: path.join(os.homedir(), \".claude\"),\n inheritDirs: [\"skills\", \"agents\", \"commands\"],\n inheritFiles: [\"CLAUDE.md\"],\n configSubdir: \"claude\",\n // 真实 ~/.claude.json 与 realHome 同级;path.join 后得到 ~/.claude/../.claude.json\n catalogFile: path.join(\"..\", \".claude.json\"),\n credentialFiles: [\".credentials.json\"],\n mcpViaAcp: true,\n sessionMode: \"bypassPermissions\",\n },\n // Codex:需适配器(Codex CLI 本身不说 ACP)。适配器是 npm 包 `@agentclientprotocol/codex-acp`(bin: codex-acp;官方维护)。\n // - **全局安装,不用 npx**(口径同 claude-code):daemon 常驻、worker 每条任务起进程,npx 每次冷启动都要问 registry/现下包,离线不可用且版本漂移;\n // `npm i -g @agentclientprotocol/codex-acp`。适配器自带 @openai/codex 依赖(含平台原生二进制,约 100MB);\n // 想复用本机已装的 Codex 可装适配器时加 --omit=optional 并设 CODEX_PATH=<codex.exe 绝对路径>(适配器官方支持的环境变量)。\n // - Windows 上 npm 全局安装产出的是 codex-acp.cmd(**无 .exe**),故 binWin 写 .cmd + shell(.cmd 必须经 shell,§4.6);standalone 二进制用 --bin 指定。\n // - codex-acp **不解析** CLI 参数(codex 的 --yolo/--full-auto 都不适用);免审批走 ACP `session/set_mode {modeId:\"agent-full-access\"}`\n // (= approval never + sandbox danger-full-access),并同时用 README 记录的环境变量 INITIAL_AGENT_MODE 兜底(旧版本无 set_mode 时仍生效)。\n // - 认证走 Codex 自己(ChatGPT 登录或 OPENAI_API_KEY);已有 ~/.codex/config.toml 直接沿用——适配器不读 WeMind 沙箱,故无 homeEnvVar。\n // - MCP:**不支持客户端提供的 stdio MCP server**(探针实测 mcpCapabilities.acp=false,session/new 带 stdio mcpServers 不报错但模型看不到工具)→ 不开 mcpViaAcp;\n // 微信侧「加载/卸载 MCP」对 codex 无效(与 claude-code 的 stdio MCP 可用相反——能力字段也不完全可信,以实测为准)。\n // - 2026-10-30 探针实测(codex-acp 2.5.0 / Codex CLI 0.165,Windows):\n // loadSession ✅、promptCapabilities.image ✅、session/set_mode ✅、session/set_config_option(model) ✅、session/load 后上下文保留、\n // 工具调用 0 反向请求(只收到无 id 的 _auth/status_update 通知);首帧 ~20ms、首字 3–4s、带图整轮 ~24s。\n // 真机 E2E 已通过(2026-11-01:worker 与 brain 通道均通);详见 docs/接入-Codex.md。\n codex: {\n name: \"codex\",\n bin: \"codex-acp\",\n binWin: \"codex-acp.cmd\",\n args: [],\n shell: true,\n envDefaults: { INITIAL_AGENT_MODE: \"agent-full-access\" },\n sessionMode: \"agent-full-access\",\n // 回合可能跑几十秒(实测带图的回合 ~24s),且适配器会发 thought / tool_call / usage 帧;心跳放长,少占微信连发额度(约 10 条/用户)。\n heartbeatMs: 60_000,\n },\n};\n\nfunction onPath(exe: string): string | null {\n for (const dir of (process.env.PATH ?? \"\").split(path.delimiter)) {\n if (dir && fs.existsSync(path.join(dir, exe))) return path.join(dir, exe);\n }\n return null;\n}\n\n/** 同 onPath,但按平台补齐扩展名:适配器依赖的运行时(如 pi)在 Windows 上通常是 .cmd。 */\nfunction onPathAny(exe: string): string | null {\n if (process.platform === \"win32\")\n return onPath(`${exe}.cmd`) ?? onPath(`${exe}.exe`) ?? onPath(exe);\n return onPath(exe);\n}\n\n/**\n * `auto` 的探测顺序:按序取第一个「二进制能在 PATH 上解析到」的 agent。只收纳**原生 ACP 且二进制可直接判定**的 agent——适配器类(`claude-code` 走 `claude-agent-acp`、`codex` 走 `codex-acp`、`pi` 走 `pi-acp`)不参与自动探测:它们是独立安装的桥接进程、各有自己的认证,误选中会让「装了但没登录」的机器静默换脑;显式选用(`wemind brain set|switch <名>`)更安全。顺序按本机实测的启动耗时排(暖启动:opencode ~1.7s、cline ~2.0s、qoder ~2.5s、gemini ~4.3s);kiro 与 deepseek 在该轮实测中未握手成功(kiro `session/new` 超时、deepseek 旧会话 artifact 布局错误),暂排最后,修复后可再前移。\n */\nexport const AUTO_PROBE_ORDER = [\n \"opencode\",\n \"cline\",\n \"qoder\",\n \"gemini\",\n \"deepseek\",\n \"kiro\",\n] as const;\n\n/**\n * 该 agent 预设里「进入 ACP 模式」的标记 token:从默认 args 中挑出含 `acp` 的项。\n * 只用于整段替换 args 时的静态提醒;适配器(claude-code / codex)的 args 为空 → 返回空数组(不检查——适配器本体就是 ACP server,没有模式参数)。\n * 不能只查裸 `acp`:qoder 是 `--acp`、gemini 是 `--acp`、deepseek 是 `--profile acp`。\n */\nexport function acpModeTokens(name: string): string[] {\n const preset = ACP_AGENT_PRESETS[name];\n return preset ? preset.args.filter((a) => /acp/i.test(a)) : [];\n}\n\n/**\n * 整段替换 args 时的静态提醒(**只提醒、不拒绝**):新 args 里找不到该 agent 的 ACP 模式标记时返回文案。\n * `binOverridden`(env / brain.json 指定了自定义 bin)时不检查——自定义入口/包装脚本可能自带 ACP 模式,静态判断不可靠。\n */\nexport function acpArgsWarning(\n name: string,\n args: string[],\n binOverridden: boolean,\n): string | null {\n if (binOverridden) return null;\n const markers = acpModeTokens(name);\n if (!markers.length) return null;\n // 宽匹配:任何含 `acp` 的 token 都算(`--experimental-acp` 是 gemini 旧版仍在用的等价开关,不能被判成漏参数)\n if (args.some((a) => /acp/i.test(a))) return null;\n return `args 里没有 ${name} 的 ACP 模式参数(预设是 ${markers.map((m) => `\\`${m}\\``).join(\" / \")})——进程可能起得来但不会说 ACP,首条消息会握手超时。请把它一起写回去,或改用 --extra-args 只追加。`;\n}\n\n/** 运行时诊断:握手失败 / 进程在握手前退出时追加的提示;args 看起来正常、或 bin 是自定义入口时返回空串。 */\nexport function acpArgsHint(ag: ResolvedAgent): string {\n if (ag.binOverridden) return \"\"; // 自定义入口/包装脚本可能自带 ACP 模式,静态判断不可靠\n const markers = acpModeTokens(ag.name);\n if (!markers.length || ag.args.some((a) => /acp/i.test(a))) return \"\";\n return `(当前 args=[${ag.args.join(\" \")}] 里没有 ${markers.map((m) => `\\`${m}\\``).join(\" / \")}——若覆盖过 --args,确认没漏 ACP 模式参数;也可改用 --extra-args 只追加)`;\n}\n\n/** agent 在该平台的「可执行文件名」(用于 PATH 探测)。 */\nfunction presetExe(preset: Pick<AcpAgentPreset, \"bin\" | \"binWin\">): string {\n return process.platform === \"win32\" ? (preset.binWin ?? `${preset.bin}.exe`) : preset.bin;\n}\n\n/**\n * 解析预设二进制的实际位置:显式覆盖(`presets[名].bin`)> PATH > 预设的 `fallbackBinPaths`。\n * `presetAvailable` 与 `resolveAgentCore` 共用这一份判据,避免「能解析到却探测说没有」(qoder 的官方安装目录就是这种情况)。\n * 导出仅供单测。\n */\nexport function presetBinPath(\n preset: Pick<AcpAgentPreset, \"bin\" | \"binWin\" | \"fallbackBinPaths\">,\n overrideBin?: string,\n): string | null {\n if (overrideBin) return locateBin(overrideBin);\n return (\n onPath(presetExe(preset)) ??\n (preset.fallbackBinPaths ?? []).find((p) => fs.existsSync(p)) ??\n null\n );\n}\n\n/**\n * 该 agent 的二进制是否可解析(只判定、不启动)。\n * 传入 cfg 时,`presets[名].bin` 里存的绝对路径也算「可用」——否则桌面端 DSH 这种「装了但不在 PATH」的 agent,auto 探测会白白跳过它。\n */\nexport function presetAvailable(key: string, cfg?: BrainConfig | null): boolean {\n const preset = ACP_AGENT_PRESETS[key];\n if (!preset) return false;\n return presetBinPath(preset, cfg?.presets?.[key.toLowerCase()]?.bin) !== null;\n}\n\n/**\n * 解析一个 bin 说明(绝对/相对路径,或 PATH 上的裸名)到实际文件;解析不到返回 null。\n * 裸名在 Windows 上会补试 `.exe`/`.cmd`,覆盖 npm shim 的常见布局。\n */\nfunction locateBin(bin: string): string | null {\n if (!bin) return null;\n if (bin.includes(\"/\") || bin.includes(path.sep)) return fs.existsSync(bin) ? bin : null;\n const candidates = [bin];\n if (process.platform === \"win32\" && !/\\.[A-Za-z0-9]+$/.test(bin))\n candidates.push(`${bin}.exe`, `${bin}.cmd`);\n for (const c of candidates) {\n const hit = onPath(c);\n if (hit) return hit;\n }\n return null;\n}\n\n/** daemon 因「大脑配置错误」退出时用的退出码;后台守护进程据此停止自动重启(配置错误需人工修,重拉无益)。 */\nexport const BRAIN_CONFIG_EXIT_CODE = 78;\n\nexport type ResolvedAgent = {\n name: string;\n bin: string;\n args: string[];\n homeEnvVar?: string;\n shell: boolean;\n inherit: boolean;\n realHome?: string;\n inheritDirs: string[];\n inheritFiles: string[];\n configSubdir: string;\n mcpFormat: \"kiro\" | \"opencode\";\n mcpConfigFile: string;\n catalogFile: string;\n env: Record<string, string>;\n /** 预设给的「默认环境变量」:父进程已设同名变量时不覆盖(用于审批类开关,部署方可以收紧)。 */\n envDefaults: Record<string, string>;\n /** 从 agent 自己的凭据文件派生环境变量(父进程未显式提供时;如 Cline 自定义 provider)。 */\n credentialEnv?: (\n realHome: string | undefined,\n parentEnv: NodeJS.ProcessEnv,\n ) => Record<string, string>;\n restore?: \"load\" | \"resume\";\n /** session/load|resume 返回后还会异步回放历史帧:恢复后先等帧流安静,再开始本轮提问。 */\n replayOnLoad: boolean;\n firstFrameMs?: number;\n heartbeatMs?: number;\n images: boolean;\n credentialFiles: string[];\n identityFiles: string[];\n settingsBase?: string;\n /** settingsBase 基底里要删掉的键(默认 DEFAULT_SETTINGS_STRIP)。 */\n settingsStrip: string[];\n /** 沙箱 settings.json 的预设默认键(只补缺失键;用户显式设过则保留用户的值)。 */\n settingsDefaults: Record<string, unknown>;\n model?: string;\n /** 模型改为追加到启动参数(agent 不支持 session/set_config_option 时;如 gemini 的 \"--model\")。 */\n modelArg?: string;\n realProfile?: string;\n mcpViaAcp: boolean;\n /** 会话建立/恢复后切到的模式 id(`session/set_mode`);「免审批」用,留空=不调用。 */\n sessionMode?: string;\n /** kiro 是否跑在 v3 引擎(决定启动参数与能否用 v3 的 permissions.yaml 免审批)。 */\n kiroV3?: boolean;\n /** 启动前要落盘的 session-root patch(DSH:`--patch` 指向该文件,root 指向账号数据目录)。 */\n sessionPatch?: { file: string; root: string };\n /** 请求的原始值(来自 env / brain.json / 缺省 `auto`)。 */\n requestedKey: string;\n /** `requestedKey` 的来源。 */\n source: \"env\" | \"file\" | \"auto\";\n /** `requestedKey` 是否为已知 agent(未知 → false,不再静默回退 kiro)。 */\n known: boolean;\n /** 二进制是否解析到了实际文件(找不到 → false)。 */\n available: boolean;\n /** 解析到的二进制绝对路径(`available` 时才有)。 */\n binPath: string | null;\n /** 除 bin 外还必须能在 PATH 上解析到的运行时(如 pi-acp 需要 pi 本体)。 */\n requiresBins: string[];\n /** bin 是否来自覆盖(env / brain.json),而不是预设——静态 ACP 标记检查据此跳过(自定义入口可能自带 ACP 模式)。 */\n binOverridden: boolean;\n};\n\n/** 组装 spawn 用的环境变量:父进程 env → `homeEnvVar` 指向沙箱 → 从凭据文件派生的 env(父进程未提供时)→ 预设强制 `env` → 预设 `envDefaults`(父进程已设同名变量时保留父进程的,`skipped` 收集这些键供调用方记日志)。 */\nexport function agentEnv(\n ag: ResolvedAgent,\n home: string,\n skipped: string[] = [],\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n if (ag.homeEnvVar) env[ag.homeEnvVar] = home; // 把沙箱目录传给 agent\n // 从 agent 自己的凭据文件派生(如 Cline 的 CLINE_PROVIDER/CLINE_API_KEY);helper 内部已保证父进程显式值优先\n for (const [k, v] of Object.entries(ag.credentialEnv?.(ag.realHome, process.env) ?? {})) {\n if (env[k] === undefined) env[k] = v;\n }\n for (const [k, v] of Object.entries(ag.env ?? {})) env[k] = v.replaceAll(\"%HOME%\", home);\n for (const [k, v] of Object.entries(ag.envDefaults ?? {})) {\n if (process.env[k] !== undefined) {\n skipped.push(k);\n continue;\n }\n env[k] = v.replaceAll(\"%HOME%\", home);\n }\n return env;\n}\n\n/** 该角色「选 agent」的环境变量:worker 先看 `WEMIND_WORKER_BRAIN`,未设则回落 `WEMIND_BRAIN`。 */\nfunction envKeyFor(role: AgentRole): string | undefined {\n const key =\n role === \"worker\"\n ? process.env.WEMIND_WORKER_BRAIN?.trim() || process.env.WEMIND_BRAIN?.trim()\n : process.env.WEMIND_BRAIN?.trim();\n return key || undefined;\n}\n\n/** 模型覆盖里的「跟随 agent 默认」写法(`brain set deepseek --model auto`):不指定任何模型。 */\nfunction isDefaultModel(v: string): boolean {\n const s = v.trim().toLowerCase();\n return s === \"auto\" || s === \"default\";\n}\n\n/**\n * kiro 的 ACP 引擎:启动参数里的 `--agent-engine` → 真实 `settings/cli.json` 的 `acp.agentEngine` → CLI 主版本 ≥3 的默认 v3 → 其余 v2。\n * 2.37.1 的 `kiro-cli acp --help` 写明「v2 when neither is set」;「CLI ≥3 默认 v3」是**假设/待实测**,只作兜底(防未来大版本把默认切到 v3 后仍带 v2 专属参数 → 握手超时)。\n * `--agent-engine` 还接受 `v1`,未验证,按 v2 处理(保留 v2 参数);`chat.agentEngine` **不影响** acp(官方文档原话),不能拿它推断。\n * v2 与 v3 的免审批方式不同:v2 是 `--trust-all-tools`(v3 会拒绝这个参数),v3 走 `settings/permissions.yaml` 的 allow-all 规则。\n */\nfunction kiroEngineFromArgs(args: readonly string[]): string | undefined {\n for (let i = 0; i < args.length; i++) {\n if (args[i].startsWith(\"--agent-engine=\")) return args[i].split(\"=\")[1];\n if (args[i] === \"--agent-engine\") return args[i + 1];\n }\n return undefined;\n}\n\nfunction kiroEngineFromSettings(realHome: string | undefined): string | undefined {\n if (!realHome) return undefined;\n try {\n const j = JSON.parse(fs.readFileSync(path.join(realHome, \"settings\", \"cli.json\"), \"utf-8\"));\n const v = j?.[\"acp.agentEngine\"];\n return typeof v === \"string\" ? v.toLowerCase() : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst kiroMajorCache = new Map<string, number | null>();\n/** `kiro-cli --version` 的主版本(只探一次;找不到/超时/解析失败都按 null,调用方回退 v2)。 */\nfunction kiroCliMajor(binPath: string | null): number | null {\n if (!binPath) return null;\n if (kiroMajorCache.has(binPath)) return kiroMajorCache.get(binPath) ?? null;\n let major: number | null = null;\n try {\n const r = spawnSync(binPath, [\"--version\"], {\n encoding: \"utf-8\",\n timeout: 5_000,\n windowsHide: true,\n });\n const m = /(?:^|\\s)(\\d+)\\.\\d+\\.\\d+/.exec(`${r.stdout ?? \"\"}\\n${r.stderr ?? \"\"}`);\n if (m) major = Number(m[1]);\n } catch {\n /* 找不到/超时:按未知处理 */\n }\n kiroMajorCache.set(binPath, major);\n return major;\n}\n\nfunction resolveKiroV3(\n args: readonly string[],\n realHome: string | undefined,\n binPath: string | null,\n): boolean {\n const fromArgs = kiroEngineFromArgs(args);\n if (fromArgs) return fromArgs.toLowerCase() === \"v3\";\n const fromSettings = kiroEngineFromSettings(realHome);\n if (fromSettings) return fromSettings === \"v3\";\n return (kiroCliMajor(binPath) ?? 0) >= 3;\n}\n\n/** v3 拒绝 v2 专属的整会话信任参数;v3 的免审批改由沙箱 `settings/permissions.yaml` 承担。 */\nfunction stripKiroV2TrustArgs(args: readonly string[]): string[] {\n const kept: string[] = [];\n for (let i = 0; i < args.length; i++) {\n const a = args[i];\n if (a === \"--trust-all-tools\" || a === \"-a\") continue;\n if (a === \"--trust-tools\") {\n i++;\n continue;\n }\n if (a.startsWith(\"--trust-tools=\")) continue;\n kept.push(a);\n }\n return kept;\n}\n\n/** DSH 会话持久化目录(账号数据目录内,按角色分开;与 kiro/pi 共用的沙箱根隔离开)。\n * `dataDir` 允许调用方显式指定账号目录:CLI 进程里的 `DATA_DIR` 是**默认账号**的目录,不能拿来替别的账号解析(`brain check`/`list`/`stop` 会用到)。 */\nfunction dshSessionPatchFor(role: AgentRole, dataDir: string): { file: string; root: string } {\n const dir = path.join(dataDir, \"agent-sessions\", \"deepseek\");\n return {\n file: path.join(dir, `session-root-${role}.patch.yml`),\n root: path.join(dir, role),\n };\n}\n\n/**\n * 启动前落盘 agent 需要的派生配置文件(DSH 的 session-root patch)。\n * 与沙箱准备分开:`ensureBrainHome` 只在启动/换脑时调,`brain check` 和 worker 每次起进程也要有这份文件。\n */\nexport function prepareAgent(ag: ResolvedAgent): void {\n const patch = ag.sessionPatch;\n if (!patch) return;\n try {\n fs.mkdirSync(patch.root, { recursive: true });\n fs.mkdirSync(path.dirname(patch.file), { recursive: true });\n // DSH 的 patch 是 YAML 列表;JSON.stringify 给双引号字符串,Windows 反斜杠也能正确转义。\n const body = `- id: session-persistence-jsonl\\n config:\\n root: ${JSON.stringify(patch.root)}\\n`;\n if (!fs.existsSync(patch.file) || fs.readFileSync(patch.file, \"utf-8\") !== body)\n fs.writeFileSync(patch.file, body, \"utf-8\");\n } catch {\n /* best-effort:写不了就让 agent 自己报错(缺 --patch 文件比静默换目录更容易诊断) */\n }\n}\n\n/** kiro v3 的免审批默认(等价于 v2 的 `--trust-all-tools`):沙箱 settings/permissions.yaml 缺失时补上。 */\nexport function ensureKiroV3Permissions(ag: ResolvedAgent, settingsDir: string): void {\n if (!ag.kiroV3) return;\n try {\n const dst = path.join(settingsDir, \"permissions.yaml\");\n if (fs.existsSync(dst)) return; // 真实配置里已有(已同步进沙箱)就尊重用户的规则\n fs.mkdirSync(settingsDir, { recursive: true });\n fs.writeFileSync(dst, \"rules:\\n - capability: all\\n effect: allow\\n\", \"utf-8\");\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * 解析某角色要用的 ACP agent(核心实现,见文件头注释的三级优先级)。\n * 覆盖优先级:环境变量 > 本角色那段自带 > `presets[<agent 名>]` > 内置默认。\n * `ignoreEnv`:命令驱动的切换(`resolvePresetByName`)必须绕开 `WEMIND_BRAIN*`,否则会出现「点了切换、env 却压着它」的假成功。\n */\nfunction resolveAgentCore(\n role: AgentRole,\n cfg: BrainConfig | null,\n opts: { ignoreEnv?: boolean; dataDir?: string } = {},\n): ResolvedAgent {\n const envKey = opts.ignoreEnv ? undefined : envKeyFor(role);\n const roleCfg = agentConfigFor(cfg, role);\n const fileKey = roleCfg?.agent?.trim();\n const requestedKey = (envKey || fileKey || \"auto\").toLowerCase();\n const source: ResolvedAgent[\"source\"] = envKey ? \"env\" : fileKey ? \"file\" : \"auto\";\n\n // 选 agent:auto → 探测第一个可用;未知值 → 标 known:false(结构上退回 kiro 兜底,交启动校验报错)。\n let known = true;\n let key = requestedKey;\n if (key === \"auto\") {\n key = AUTO_PROBE_ORDER.find((k) => presetAvailable(k, cfg)) ?? \"kiro\";\n } else if (!ACP_AGENT_PRESETS[key]) {\n known = false;\n key = \"kiro\";\n }\n const preset = ACP_AGENT_PRESETS[key];\n // 按 agent 名存的启动覆盖:无论谁在用这个 agent 都适用(顶层覆盖只属于「写入磁盘那个 agent」,切走后不该跟过去)。\n const presetOv = cfg?.presets?.[key];\n\n // roleCfg 的 bin/args/shell/homeEnvVar 是**给该段所写那个 agent** 的启动覆盖;环境变量临时切到别的 agent 时不能沿用(否则会拿 deepseek 的 dsh.cmd 去起 qoder)。\n const overrideCfg =\n envKey && roleCfg?.agent?.trim() && roleCfg.agent.trim().toLowerCase() !== requestedKey\n ? null\n : roleCfg;\n\n const shellEnv = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_SHELL?.trim();\n const shell =\n shellEnv !== undefined && shellEnv !== \"\"\n ? shellEnv === \"1\" || shellEnv.toLowerCase() === \"true\"\n : (overrideCfg?.shell ?? presetOv?.shell ?? preset.shell ?? false);\n\n // 二进制:env 覆盖 > (kiro 旧变量) > 本段覆盖 > presets[agent 名] 覆盖 > PATH 解析 > agent 裸名。\n // 经 shell 启动时用裸名(让 shell 从 PATH 解析,避免带空格的全路径在 shell 下出问题)。\n const envBin = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_BIN?.trim();\n const kiroBin =\n opts.ignoreEnv || preset.name !== \"kiro\" ? undefined : process.env.KIRO_CLI_BIN?.trim();\n const explicitBin = envBin || kiroBin || overrideCfg?.bin?.trim() || presetOv?.bin?.trim() || \"\";\n const exe = presetExe(preset);\n const binPath = explicitBin ? locateBin(explicitBin) : presetBinPath(preset);\n const bin =\n explicitBin ||\n (shell ? (process.platform === \"win32\" ? exe : preset.bin) : binPath || preset.bin);\n const available = known && binPath !== null;\n\n // 继承「记忆/规则/技能」:来源目录与子目录清单按 agent 而异(下沉到 agent),env 可覆盖。\n // 放在 args 之前:kiro 的引擎选择与 v3 的信任参数清洗都要用它。\n const inherit =\n (process.env.WEMIND_BRAIN_INHERIT ?? process.env.WECHAT_BRAIN_INHERIT ?? \"1\") !== \"0\";\n const realHome =\n process.env.WEMIND_BRAIN_REAL_HOME?.trim() ||\n process.env.WECHAT_REAL_KIRO_HOME?.trim() ||\n preset.realHome;\n\n const argsOverride = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_ARGS?.trim();\n const baseArgs = argsOverride\n ? argsOverride.split(/\\s+/)\n : overrideCfg?.args && overrideCfg.args.length\n ? overrideCfg.args\n : presetOv?.args && presetOv.args.length\n ? presetOv.args\n : preset.args;\n // 追加参数:与 args 的「整段替换」不同,extraArgs 永远拼在后面(保留预设里的 ACP 模式参数)。\n // 来源优先级同 args:env > 本段(该角色)> presets[agent 名]。\n const extraEnv = opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_EXTRA_ARGS?.trim();\n const extraArgs = extraEnv\n ? extraEnv.split(/\\s+/)\n : overrideCfg?.extraArgs && overrideCfg.extraArgs.length\n ? overrideCfg.extraArgs\n : presetOv?.extraArgs && presetOv.extraArgs.length\n ? presetOv.extraArgs\n : [];\n let args = [...baseArgs, ...extraArgs];\n\n // kiro:v3 删掉 v2 专属信任参数并显式声明引擎;v2 保持原样(`--trust-all-tools`)。\n const kiroV3 = preset.kiroEngine ? resolveKiroV3(args, realHome, binPath) : false;\n if (kiroV3) {\n args = stripKiroV2TrustArgs(args);\n if (!kiroEngineFromArgs(args)) args.push(\"--agent-engine\", \"v3\");\n }\n\n // 会话目录隔离:agent 的 `--patch` 指向账号数据目录下的 per-role 文件(DSH)。\n // 用户自己显式给了 `--patch` 就尊重用户,不追加第二个。\n let sessionPatch: ResolvedAgent[\"sessionPatch\"];\n if (preset.isolateSessions && !args.includes(\"--patch\")) {\n sessionPatch = dshSessionPatchFor(role, opts.dataDir ?? DATA_DIR);\n args.push(\"--patch\", sessionPatch.file);\n }\n\n const homeEnvVar =\n (opts.ignoreEnv ? undefined : process.env.WEMIND_BRAIN_HOME_ENV?.trim()) ||\n overrideCfg?.homeEnvVar?.trim() ||\n presetOv?.homeEnvVar?.trim() ||\n preset.homeEnvVar;\n\n // 模型:显式覆盖(本段 > presets[agent 名])优先;`auto`/`default` 或没写 = 跟随默认——\n // 配了 realProfile 的 agent(DSH)从用户真实 profile 读(优先 `- id: acp`,否则 `agent-default-model`;WeMind 只读不写,缺 `- id: acp` 由 `wemind brain` 提示用户补);\n // 读到的值经 `set_config_option` 下发;读不到时:`auto` = 不下发,没写 = 用内置预设值兜底。\n const rawModel = overrideCfg?.model?.trim() || presetOv?.model?.trim() || \"\";\n const explicitModel = rawModel && !isDefaultModel(rawModel) ? rawModel : \"\";\n const derivedModel =\n !explicitModel && preset.realProfile && realHome && inherit\n ? readProfileDefaultModel(realHome, preset.realProfile)\n : null;\n const model =\n explicitModel || derivedModel || (isDefaultModel(rawModel) ? undefined : preset.model);\n // 不支持 session/set_config_option 的 agent(gemini):模型用它自己的 CLI 参数传,进程启动时生效。\n if (preset.modelArg && model) args.push(preset.modelArg, model);\n const dirsEnv = process.env.WEMIND_BRAIN_INHERIT_DIRS?.trim();\n const inheritDirs = dirsEnv\n ? dirsEnv.split(/[,\\s]+/).filter(Boolean)\n : (preset.inheritDirs ?? []);\n\n // 会话模式:预设默认;`WEMIND_ACP_SESSION_MODE=0/false/off` 关闭(让 agent 回到自己的审批流程),\n // 其它非空值 = 覆盖成该 modeId(自定义 agent 用)。环境变量是一次性覆盖,持久值仍以预设为准。\n const modeEnv = process.env.WEMIND_ACP_SESSION_MODE?.trim();\n const modeOff = !!modeEnv && [\"0\", \"false\", \"off\", \"no\"].includes(modeEnv.toLowerCase());\n const sessionMode = modeOff ? undefined : modeEnv || preset.sessionMode;\n\n return {\n name: preset.name,\n bin,\n args,\n homeEnvVar,\n shell,\n inherit,\n realHome,\n inheritDirs,\n inheritFiles: preset.inheritFiles ?? [],\n configSubdir: preset.configSubdir ?? \"\",\n mcpFormat: preset.mcpFormat ?? \"kiro\",\n mcpConfigFile: preset.mcpConfigFile ?? \"settings/mcp.json\",\n catalogFile: preset.catalogFile ?? \"settings/mcp.json\",\n env: preset.env ?? {},\n envDefaults: preset.envDefaults ?? {},\n credentialEnv: preset.credentialEnv,\n restore: preset.restore,\n replayOnLoad: preset.replayOnLoad ?? false,\n firstFrameMs: preset.firstFrameMs,\n heartbeatMs: preset.heartbeatMs,\n images: preset.images ?? true,\n credentialFiles: preset.credentialFiles ?? [],\n identityFiles: preset.identityFiles ?? [],\n settingsBase: preset.settingsBase,\n settingsStrip: preset.settingsStrip ?? DEFAULT_SETTINGS_STRIP,\n settingsDefaults: preset.settingsDefaults ?? {},\n model,\n modelArg: preset.modelArg,\n realProfile: preset.realProfile,\n mcpViaAcp: preset.mcpViaAcp ?? false,\n sessionMode,\n kiroV3,\n sessionPatch,\n requestedKey,\n source,\n known,\n available,\n binPath,\n requiresBins: preset.requiresBins ?? [],\n binOverridden: !!explicitBin,\n };\n}\n\n/** 解析当前要用的**对话大脑**:环境变量 > 账号 brain.json 顶层 > auto。\n * `dataDir` 只影响 DSH 的 `--patch` 路径;daemon 内默认就是本账号目录,CLI 处理别的账号时由调用方传入 `accountDir(key)`。 */\nexport function resolveAgent(cfg?: BrainConfig | null, dataDir?: string): ResolvedAgent {\n return resolveAgentCore(\"brain\", cfg ?? null, dataDir ? { dataDir } : {});\n}\n\n/** 解析某**角色**要用的 ACP agent:`brain` = 对话大脑(同 resolveAgent),`worker` = 任务 worker(`worker` 段 > 顶层)。 */\nexport function resolveAgentFor(\n cfg: BrainConfig | null | undefined,\n role: AgentRole,\n dataDir?: string,\n): ResolvedAgent {\n return resolveAgentCore(role, cfg ?? null, dataDir ? { dataDir } : {});\n}\n\n/** 全部 agent 名(`auto` 不在其中:它只是「自动探测」的开关)。 */\nexport function presetNames(): string[] {\n return Object.keys(ACP_AGENT_PRESETS);\n}\n\n/**\n * 按名字解析一个 agent,**绕开环境变量**——供「切换大脑」这类命令使用。\n *\n * 为什么绕开:`WEMIND_BRAIN` 优先级高于 brain.json,命令若也走 env 优先级,会出现「用户点了切换、看着成功,实际还是 env 指的那个」。\n * 命令语义 = 用户此刻的明确意图,应压过一次性环境覆盖(`resolvePresetByName` 的结果由调用方写进 brain.json)。\n * 同理,属于「别的 agent」的启动覆盖不会被沿用;但 `presets[名]` 里按名字存的覆盖会(切走再切回不丢 `--bin`)。\n *\n * 失败返回面向用户的原因(未知 agent / 该 agent 二进制找不到),成功返回可直接 spawn 的 agent。\n */\nexport function resolvePresetByName(\n name: string,\n cfg?: BrainConfig | null,\n dataDir?: string,\n): { ok: true; agent: ResolvedAgent } | { ok: false; problem: string } {\n const key = (name ?? \"\").trim().toLowerCase();\n if (!key) return { ok: false, problem: \"需要指定 agent 名(或 `auto` 重新自动探测)。\" };\n const base: BrainConfig = { agent: key, presets: cfg?.presets };\n // 顶层启动覆盖只属于「写入磁盘那个 agent」:目标就是它时带上(例如 `brain set deepseek --bin …` 后切回 deepseek)。\n if (cfg?.agent?.trim().toLowerCase() === key) {\n if (cfg.bin) base.bin = cfg.bin;\n if (cfg.args?.length) base.args = cfg.args;\n if (cfg.extraArgs?.length) base.extraArgs = cfg.extraArgs;\n if (cfg.shell !== undefined) base.shell = cfg.shell;\n if (cfg.homeEnvVar) base.homeEnvVar = cfg.homeEnvVar;\n if (cfg.model) base.model = cfg.model;\n }\n const agent = resolveAgentCore(\"brain\", base, {\n ignoreEnv: true,\n ...(dataDir ? { dataDir } : {}),\n });\n const problem = agentProblem(agent);\n return problem ? { ok: false, problem } : { ok: true, agent };\n}\n\n/**\n * 适配器类 agent 的安装指引:找不到适配器二进制时**直接给出可粘贴的命令**。\n * 口径:**适配器一律全局安装**(daemon 常驻、worker 每条任务起进程;npx 每次冷启动要联网现下、版本不可复现)。\n */\nconst ADAPTER_INSTALL_HINT: Record<string, string> = {\n \"claude-code\":\n \"`npm i -g @agentclientprotocol/claude-agent-acp`(装完得到 claude-agent-acp;Windows 上是 claude-agent-acp.cmd;**需 Node ≥22**)\",\n codex:\n \"`npm i -g @agentclientprotocol/codex-acp`(装完得到 codex-acp;Windows 上是 codex-acp.cmd)\",\n pi: \"`npm i -g pi-acp`(装完得到 pi-acp;Windows 上是 pi-acp.cmd);另需 `npm i -g @earendil-works/pi-coding-agent` 提供 pi 本体,并在终端里先登录/配好模型\",\n};\n\n/**\n * 校验解析后的大脑是否可用:可用返回 null,否则返回一句面向用户的说明。\n * 供 daemon 启动校验(早失败、说人话)与 `wemind brain` / `wemind brain check` 复用。\n */\nexport function agentProblem(ag: ResolvedAgent): string | null {\n if (!ag.known)\n return `未知 agent「${ag.requestedKey}」。可选:${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}(自定义二进制可用 WEMIND_BRAIN_BIN 或 brain set --bin 指定)`;\n if (ag.requestedKey === \"auto\" && !ag.available)\n return `自动探测未找到可用的大脑(已试:${AUTO_PROBE_ORDER.join(\"、\")})。请安装其中之一,或运行 \\`wemind brain set <agent 名>\\``;\n if (!ag.available) {\n const hint = ADAPTER_INSTALL_HINT[ag.name];\n if (hint)\n return `找不到大脑「${ag.name}」的适配器「${ag.bin}」。先装适配器:${hint};装完仍找不到就 \\`wemind brain set ${ag.name} --bin \"<绝对路径>\"\\` 指定它。`;\n return ag.name === \"deepseek\"\n ? `找不到 DeepSeek 的「${ag.bin}」。桌面端通常有 dsh.cmd;把它的绝对路径传给 \\`wemind brain set deepseek --bin \"<...>\\\\dsh.cmd\"\\``\n : ag.name === \"qoder\"\n ? `找不到 Qoder CLI 的「${ag.bin}」。官方安装会放在 ~/.qoder/bin/qodercli/ 并加入 PATH;确认后重开终端,或 \\`wemind brain set qoder --bin \"<...>\\\\qodercli.exe\"\\``\n : ag.name === \"cline\"\n ? `找不到 Cline CLI 的「${ag.bin}」。安装:\\`npm i -g cline\\`(Node 20+),装完后重开终端;或用 \\`wemind brain set cline --bin \"<...>\\\\cline.cmd\"\\``\n : `找不到大脑「${ag.name}」的二进制「${ag.bin}」。安装它,或运行 \\`wemind brain set <agent 名>\\` 切换`;\n }\n // 适配器已装、但它依赖的运行时没装(如 pi-acp 找到了、pi 本体不在 PATH):\n // 提前说清楚,别等 session/new 才抛一句适配器的内部错误。\n const missingRuntime = ag.requiresBins.filter((b) => !onPathAny(b));\n if (missingRuntime.length) {\n const hint = ADAPTER_INSTALL_HINT[ag.name];\n return `找不到「${ag.name}」需要的运行时「${missingRuntime.join(\"、\")}」。${hint ? `安装:${hint}` : \"先安装它,再试。\"}`;\n }\n return null;\n}\n\n/**\n * 认证失败时按 agent 补一句可执行的建议(`wemind brain check` 与 daemon 日志复用)。\n * Cline 的 ACP 只自动恢复它自己的 3 种登录;自定义 provider 必须靠环境变量——\n * 「重启 daemon」对它没用,所以不能只套用通用的「密钥/鉴权」提示。\n * 其它 agent 返回空串(不改变既有文案)。\n */\nexport function authHint(agentName: string, message: string): string {\n if (agentName !== \"cline\") return \"\";\n if (!/authentication required|authenticate|unauthorized|not authenticated/i.test(message))\n return \"\";\n return \"Cline 的 ACP 只自动恢复 cline / cline-pass / openai-codex 三种登录;用自定义 provider(如 openai-compatible)时,请在启动 wemind 的环境里设置 CLINE_PROVIDER 与 CLINE_API_KEY(设置后重启一次 daemon)。\";\n}\n","import { resolveAgentFor, type ResolvedAgent } from \"./brain-agents.js\";\nimport { loadBrainConfig, type AgentRole, type BrainConfig } from \"./brain-config.js\";\n\n/**\n * 角色的 agent「槽位」:把「当前用哪个 agent」从**模块级常量**(改造前 `acp-brain.ts:25`、\n * `worker-acp.ts:28`、`mcp-sandbox.ts:21`、`worker-sandbox.ts:22` 都是 import 时解析一次就冻死)\n * 变成**运行时可变状态**——否则「热切换大脑」只能靠重启整个 daemon。\n *\n * 两个刻意的设计:\n * - **懒解析 + 记忆化**:模块加载期不读配置,第一次真正用到才解析。沙箱测试靠「先设环境变量、再动态 import」\n * 来构造场景(`sandbox-qoder.test.ts`),加载期解析会让这些用例变成时序敏感。`runDaemon` 开头会\n * `seedSlots()` 显式种入,所以「启动即定」的语义没变。\n * - **变更只有一个入口**:`setAgent` 只由切换流程调用;运行中的回合一律**显式持有并传递**自己的 agent\n * (见 acp-brain 的 `ensureProcess(ag)/ensureSession(ag,…)/promptRequest(ag,…)`),过程内不再读槽位——\n * 于是「握手到一半换了 agent」在结构上不可能发生,不需要加锁。\n */\n\nconst slots = new Map<AgentRole, ResolvedAgent>();\n\n/** 取某角色当前的 agent(懒解析 + 记忆化)。 */\nexport function getAgent(role: AgentRole): ResolvedAgent {\n const hit = slots.get(role);\n if (hit) return hit;\n const ag = resolveAgentFor(loadBrainConfig(), role);\n slots.set(role, ag);\n return ag;\n}\n\n/** 只看不清:没种过返回 undefined(**不**触发解析)。用于「展示当前状态」这类不该有副作用的路径。 */\nexport function peekAgent(role: AgentRole): ResolvedAgent | undefined {\n return slots.get(role);\n}\n\n/** 启动时种入(runDaemon):按给定配置把两个角色定下来。 */\nexport function seedSlots(cfg: BrainConfig | null = loadBrainConfig()): void {\n slots.set(\"brain\", resolveAgentFor(cfg, \"brain\"));\n slots.set(\"worker\", resolveAgentFor(cfg, \"worker\"));\n}\n\n/** 覆盖槽位(切换成功后才调用)。传 null 清掉缓存,下次 `getAgent` 重新解析。 */\nexport function setAgent(role: AgentRole, ag: ResolvedAgent | null): void {\n if (ag) slots.set(role, ag);\n else slots.delete(role);\n}\n\n/** 仅供测试:清空槽位,让下一次解析重新读环境变量与配置。 */\nexport function resetSlotsForTest(): void {\n slots.clear();\n}\n","/**\n * MCP 配置的「格式」适配:不同 ACP agent 把 MCP server 定义放在不同文件、不同结构里。\n * 本模块只做纯数据的组织/解析(无文件 IO),便于单测;沙箱模块负责写入磁盘。\n *\n * - `kiro`:独立文件 `settings/mcp.json`,结构 `{ \"mcpServers\": { 名: {command,args,env} } }`\n * - `opencode`:主配置 `opencode.json`,结构 `{ \"mcp\": { \"servers\": { 名: {type,command,environment} } } }`(与 model 等共处一文件)\n */\nexport type McpFormat = \"kiro\" | \"opencode\";\n\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n\n/** 从配置对象读出 server 定义表(按格式);缺失/异常一律返回空表。 */\n/**\n * 一条 MCP 定义是不是微信 MCP:名字之外再看命令/参数/URL。\n * 只按名字(`wechat`)拦不住改名后的同一条目——它仍会启动 `wemind-mcp`、拿同一个 bot token 长轮询,与 daemon 抢消息。\n * brain 与 worker 共用(worker 的排除集同样是「按名字 + 按定义」两道)。\n */\nexport function isWechatMcpServer(entry: unknown): boolean {\n if (!entry || typeof entry !== \"object\") return false;\n const e = entry as Record<string, unknown>;\n const parts: string[] = [];\n const push = (v: unknown): void => {\n if (typeof v === \"string\") parts.push(v);\n };\n push(e.command);\n if (Array.isArray(e.command)) for (const x of e.command) push(x);\n if (Array.isArray(e.args)) for (const x of e.args) push(x);\n push(e.url);\n return parts.some((p) => /wemind-mcp|@wemind\\/wechat-mcp/i.test(p));\n}\n\nexport function readMcpServers(obj: unknown, format: McpFormat): Record<string, unknown> {\n if (!isObj(obj)) return {};\n const servers =\n format === \"opencode\"\n ? isObj((obj as any).mcp)\n ? (obj as any).mcp.servers\n : undefined\n : (obj as any).mcpServers;\n return isObj(servers) ? servers : {};\n}\n\n/** 把 server 定义表写回配置对象(按格式),返回**新对象**(不改原对象),并保留其它键(如 opencode 的 model)。 */\nexport function withMcpServers(\n obj: unknown,\n servers: Record<string, unknown>,\n format: McpFormat,\n): Record<string, unknown> {\n const base: Record<string, unknown> = isObj(obj) ? { ...obj } : {};\n if (format === \"opencode\") {\n const mcp: Record<string, unknown> = isObj(base.mcp) ? { ...(base.mcp as object) } : {};\n mcp.servers = servers;\n base.mcp = mcp;\n } else {\n base.mcpServers = servers;\n }\n return base;\n}\n\n/**\n * 宽松解析 JSONC(去 // 与块注释、去尾随逗号)。用于用户可能带注释的配置:opencode.jsonc、Gemini 的 settings.json(它自己用 strip-json-comments);失败返回 null(调用方按「无配置」处理,不抛)。\n */\nexport function parseJsonc(text: string): Record<string, unknown> | null {\n try {\n return JSON.parse(text) as Record<string, unknown>;\n } catch {\n /* 继续做宽松清洗 */\n }\n try {\n const parsed = JSON.parse(stripTrailingCommas(stripJsonComments(text)));\n return isObj(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/** 字符串感知地去掉行注释与块注释:字符串里的同形文本是数据,不能删(手写正则版会改坏 `\"x // y\"`)。 */\nfunction stripJsonComments(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n const c = text[i];\n if (c === '\"') {\n out += c;\n i++;\n while (i < text.length) {\n const ch = text[i];\n out += ch;\n i++;\n if (ch === \"\\\\\") {\n if (i < text.length) out += text[i++]; // 转义字符连同下一个字符原样保留\n } else if (ch === '\"') break;\n }\n continue;\n }\n if (c === \"/\" && text[i + 1] === \"/\") {\n i += 2;\n while (i < text.length && text[i] !== \"\\n\") i++;\n continue;\n }\n if (c === \"/\" && text[i + 1] === \"*\") {\n i += 2;\n while (i < text.length && !(text[i] === \"*\" && text[i + 1] === \"/\")) i++;\n i += 2;\n continue;\n }\n out += c;\n i++;\n }\n return out;\n}\n\n/** 字符串感知地去掉尾随逗号(字符串里的 `,}` / `,]` 原样保留)。 */\nfunction stripTrailingCommas(text: string): string {\n let out = \"\";\n let i = 0;\n while (i < text.length) {\n const c = text[i];\n if (c === '\"') {\n out += c;\n i++;\n while (i < text.length) {\n const ch = text[i];\n out += ch;\n i++;\n if (ch === \"\\\\\") {\n if (i < text.length) out += text[i++];\n } else if (ch === '\"') break;\n }\n continue;\n }\n if (c === \",\") {\n let j = i + 1;\n while (j < text.length && /\\s/.test(text[j])) j++;\n if (text[j] === \"}\" || text[j] === \"]\") {\n i++;\n continue;\n }\n }\n out += c;\n i++;\n }\n return out;\n}\n\n// ---- ACP 传输:把 catalog 条目转成 session/new 的 mcpServers(配了 mcpViaAcp 的 agent 用) ----\n\n/** ACP `session/new` 的 stdio MCP 声明(env 是 `{name,value}[]`,与 kiro/opencode 的 map 不同)。 */\nexport type AcpMcpServer = {\n name: string;\n command: string;\n args: string[];\n env: Array<{ name: string; value: string }>;\n};\n\n/** ACP `session/new` 的 HTTP MCP 声明(官方 DSH 已广告 http:true;SSE 不支持)。 */\nexport type AcpMcpHttpServer = {\n name: string;\n type: \"http\";\n url: string;\n headers: Array<{ name: string; value: string }>;\n};\n\nfunction envEntries(map: unknown): Array<{ name: string; value: string }> {\n if (!isObj(map)) return [];\n return Object.entries(map)\n .filter(([, v]) => v !== undefined && v !== null)\n .map(([name, v]) => ({ name, value: String(v) }));\n}\n\n/** 该 catalog 条目能否转成 ACP 声明(addMcp 加载前的预校验也用它)。 */\nexport function isConvertibleToAcp(entry: unknown): boolean {\n if (!isObj(entry)) return false;\n if (typeof entry.command === \"string\" && entry.command) return true; // kiro local\n if (Array.isArray(entry.command) && typeof entry.command[0] === \"string\" && entry.command[0])\n return true; // opencode local\n if (typeof entry.url === \"string\" && entry.url) return true; // remote/http\n return false;\n}\n\n/**\n * 把 catalog 的 server 定义表转成 ACP `mcpServers`(stdio / HTTP)。\n * - kiro `{ command, args, env }` → stdio;\n * - opencode `{ type:\"local\", command:[...], environment }` → stdio(拆 command[0]/args);\n * - `{ url }`(kiro 远程 / opencode remote)→ HTTP(SSE 不被 ACP 支持,无法从定义识别,由服务端拒绝)。\n * 返回转换结果与**被跳过的名字**(结构不认识/明显残缺)——调用方记日志,避免坏条目把整个 session 拖挂。\n */\nexport function toAcpMcpServers(\n servers: Record<string, unknown>,\n opts: { exclude?: Iterable<string> } = {},\n): { servers: Array<AcpMcpServer | AcpMcpHttpServer>; skipped: string[] } {\n const exclude = new Set(opts.exclude ?? []);\n const out: Array<AcpMcpServer | AcpMcpHttpServer> = [];\n const skipped: string[] = [];\n for (const [name, entry] of Object.entries(servers)) {\n if (exclude.has(name)) continue;\n if (!isObj(entry)) {\n skipped.push(name);\n continue;\n }\n // opencode local:command 是数组\n if (Array.isArray(entry.command)) {\n const cmd = entry.command[0];\n if (typeof cmd !== \"string\" || !cmd) {\n skipped.push(name);\n continue;\n }\n out.push({\n name,\n command: cmd,\n args: entry.command.slice(1).map(String),\n env: envEntries(entry.environment ?? entry.env),\n });\n continue;\n }\n // kiro local:command 是字符串\n if (typeof entry.command === \"string\" && entry.command) {\n out.push({\n name,\n command: entry.command,\n args: Array.isArray(entry.args) ? entry.args.map(String) : [],\n env: envEntries(entry.env),\n });\n continue;\n }\n // 远程:url → HTTP(header 兼容 map 与 {name,value}[] 两种写法)\n if (typeof entry.url === \"string\" && entry.url) {\n const rawHeaders = entry.headers;\n const headers = isObj(rawHeaders)\n ? envEntries(rawHeaders)\n : Array.isArray(rawHeaders)\n ? rawHeaders\n .filter(\n (h): h is { name: string; value: string } =>\n isObj(h) && typeof (h as any).name === \"string\",\n )\n .map((h) => ({\n name: String((h as any).name),\n value: String((h as any).value ?? \"\"),\n }))\n : [];\n out.push({ name, type: \"http\", url: entry.url, headers });\n continue;\n }\n skipped.push(name);\n }\n return { servers: out, skipped };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { ResolvedAgent } from \"./brain-agents.js\";\n\n/**\n * 把 agent 真实配置目录里的「记忆/规则/技能」链入沙箱配置目录,并在**换 agent** 时清掉上一个 agent 留下的条目。\n *\n * 为什么需要清单:kiro / deepseek / pi 的 `configSubdir` 都是空(配置目录就是沙箱根),共用同一个目录。\n * 热切换后若只做「不存在才建」,新 agent 会继续读到上一个 agent 的 `skills` 链接与 `AGENTS.md`——\n * 表现为「切到 pi 了,但回答仍按 kiro 的记忆来」。清单记录上次由 WeMind 链入/拷贝的名字,只清理这些,\n * 真实配置目录永远不动(沙箱里的非链接目录也保留,避免误删 agent 自建数据)。\n *\n * brain 与 worker 共用(两边的沙箱布局相同,只是根目录不同)。\n */\n\nconst MANIFEST = \".wemind-inherited.json\";\n\ntype Manifest = { agent?: string; dirs: string[]; files: string[] };\n\nfunction readManifest(dir: string): Manifest {\n try {\n const j = JSON.parse(fs.readFileSync(path.join(dir, MANIFEST), \"utf-8\"));\n return {\n agent: typeof j?.agent === \"string\" ? j.agent : undefined,\n dirs: Array.isArray(j?.dirs) ? j.dirs.filter((x: unknown) => typeof x === \"string\") : [],\n files: Array.isArray(j?.files) ? j.files.filter((x: unknown) => typeof x === \"string\") : [],\n };\n } catch {\n return { dirs: [], files: [] };\n }\n}\n\nfunction writeManifest(dir: string, m: Manifest): void {\n try {\n fs.writeFileSync(path.join(dir, MANIFEST), JSON.stringify(m, null, 2), \"utf-8\");\n } catch {\n /* best-effort */\n }\n}\n\n/** 删掉一个链接本身(不碰它指向的真实目录)。 */\nfunction removeLink(link: string): void {\n try {\n fs.unlinkSync(link);\n } catch {\n try {\n fs.rmdirSync(link);\n } catch {\n /* 不存在 / 权限不足 */\n }\n }\n}\n\nfunction pointsTo(link: string, target: string): boolean {\n try {\n return path.resolve(fs.readlinkSync(link)) === path.resolve(target);\n } catch {\n return false;\n }\n}\n\n/**\n * `root` = 沙箱根(安全校验用),`dir` = 该 agent 的配置目录(通常是 root 或 root/<subdir>)。\n * 调用前 `dir` 不存在也没关系。\n */\nexport function syncInherited(root: string, dir: string, ag: ResolvedAgent): void {\n if (!ag.inherit || !ag.realHome) return;\n if (path.resolve(root) === path.resolve(ag.realHome)) return; // 安全:别把自己链给自己\n try {\n fs.mkdirSync(dir, { recursive: true });\n } catch {\n return;\n }\n const prev = readManifest(dir);\n const staleFiles = new Set(prev.agent !== ag.name ? prev.files : []);\n const keepDirs = new Set(ag.inheritDirs);\n const keepFiles = new Set(ag.inheritFiles);\n\n // 1) 清掉上一个 agent 有、这个 agent 不要的条目。\n for (const d of prev.dirs) {\n if (keepDirs.has(d)) continue;\n const link = path.join(dir, d);\n try {\n if (fs.lstatSync(link).isSymbolicLink()) removeLink(link);\n } catch {\n /* 不存在 */\n }\n }\n for (const f of prev.files) {\n if (keepFiles.has(f)) continue;\n try {\n fs.rmSync(path.join(dir, f), { force: true });\n } catch {\n /* ignore */\n }\n }\n\n // 2) 链入目录:同名链接指向别的真实目录(上一个 agent)时重建。\n for (const d of ag.inheritDirs) {\n const target = path.join(ag.realHome, d);\n const link = path.join(dir, d);\n if (!fs.existsSync(target)) continue;\n let present = false;\n try {\n fs.lstatSync(link);\n present = true;\n } catch {\n /* 不存在 */\n }\n if (present) {\n try {\n const st = fs.lstatSync(link);\n if (st.isSymbolicLink()) {\n if (pointsTo(link, target)) continue; // 已指向当前 agent 的真实目录\n removeLink(link); // 指向上一个 agent → 重建\n } else if (st.isDirectory()) {\n if (fs.readdirSync(link).length === 0) {\n fs.rmdirSync(link); // 空目录 → 删掉,下面改建 junction\n } else {\n // 非空真实目录(如 kiro 自建 agents 脚手架):补拷真实目录里缺失的文件(不删已有)\n for (const f of fs.readdirSync(target)) {\n const dst = path.join(link, f);\n const src = path.join(target, f);\n try {\n if (!fs.existsSync(dst) && fs.statSync(src).isFile()) fs.copyFileSync(src, dst);\n } catch {\n /* ignore */\n }\n }\n continue;\n }\n } else continue;\n } catch {\n continue;\n }\n }\n try {\n fs.symlinkSync(target, link, process.platform === \"win32\" ? \"junction\" : \"dir\");\n } catch {\n /* 权限不足等:best-effort 忽略 */\n }\n }\n\n // 3) 单文件记忆:真实目录有就拷;上一个 agent 留下的同名文件视为过期,用当前 agent 的覆盖。\n for (const f of ag.inheritFiles) {\n const src = path.join(ag.realHome, f);\n const dst = path.join(dir, f);\n try {\n if (fs.existsSync(src) && (!fs.existsSync(dst) || staleFiles.has(f)))\n fs.copyFileSync(src, dst);\n } catch {\n /* ignore */\n }\n }\n\n writeManifest(dir, { agent: ag.name, dirs: [...ag.inheritDirs], files: [...ag.inheritFiles] });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { ensureKiroV3Permissions, type ResolvedAgent } from \"./brain-agents.js\";\nimport { getAgent } from \"./agent-slot.js\";\nimport {\n readMcpServers,\n withMcpServers,\n parseJsonc,\n toAcpMcpServers,\n isWechatMcpServer,\n} from \"./agent-mcp.js\";\nimport { copyProfilePatch } from \"./profile-model.js\";\nimport { syncInherited } from \"./sandbox-inherit.js\";\n\n/**\n * brain 的 MCP 沙箱:brain 跑在独立配置目录下,默认不加载任何 MCP。\n * 通过对话指令(加载/卸载/列出)动态调整「激活集」,从全局 MCP 目录取定义写入沙箱。\n * DATA_DIR 从 core 导入(支持 WECHAT_DATA_DIR 覆盖,便于多账号隔离)。\n *\n * 按 agent 适配(见 brain-agents.ts 里的 agent 配置与 docs/接入-OpenCode.md):\n * - 配置目录:kiro = 沙箱根(KIRO_HOME);opencode = <沙箱>/opencode(XDG_CONFIG_HOME)。\n * - MCP 文件:kiro = settings/mcp.json({mcpServers});opencode = opencode.json(mcp.servers)。\n * kiro 行为与改造前完全一致(默认值即 kiro 约定)。\n *\n * agent 不再存成模块常量(改造前是 import 时解析的 `AG`):每个函数显式收 `ag`,\n * 或在入口用 `getAgent(\"brain\")` 取一次当前大脑——热切换后沙箱会写到新 agent 的配置目录。\n */\n\n/** brain 专用沙箱根目录(由 agent 的 homeEnvVar 指向它)。与 agent 无关。 */\nexport const BRAIN_HOME =\n process.env.WECHAT_BRAIN_HOME?.trim() || path.join(DATA_DIR, \"brain-home\");\n\n/** 该 agent 在沙箱里的「配置目录」(kiro=沙箱根;opencode=<沙箱>/opencode)。 */\nfunction brainConfigDir(ag: ResolvedAgent): string {\n return path.join(BRAIN_HOME, ag.configSubdir);\n}\n\n/** MCP 配置文件(相对配置目录,按 agent 的 mcpConfigFile)。 */\nfunction brainMcpJson(ag: ResolvedAgent): string {\n return path.join(brainConfigDir(ag), ag.mcpConfigFile);\n}\n\n/** 激活集持久化文件(与 agent 无关:切脑后再切回,激活的 MCP 仍是那套)。 */\nconst ACTIVE_FILE = path.join(DATA_DIR, \"brain-mcps.json\");\n\n/** 「目录」:可选 MCP 的定义来源。默认取所用 agent 真实配置目录(realHome)下的\n * catalogFile(kiro: settings/mcp.json / opencode: opencode.json);可用 `WEMIND_MCP_CATALOG` / `WECHAT_MCP_CATALOG` 覆盖为任意路径。 */\nfunction catalogPath(ag: ResolvedAgent): string {\n return (\n process.env.WEMIND_MCP_CATALOG?.trim() ||\n process.env.WECHAT_MCP_CATALOG?.trim() ||\n path.join(ag.realHome || path.join(os.homedir(), \".kiro\"), ag.catalogFile)\n );\n}\n\n/** 永不允许 brain 加载(防它把消息发回自己)。 */\nconst FORBIDDEN = new Set([\"wechat\"]);\n\n/** 名字命中禁用集,或定义本身就是微信 MCP(改名也拦得住)。 */\nfunction forbiddenServer(name: string, def: unknown): boolean {\n return FORBIDDEN.has(name) || isWechatMcpServer(def);\n}\n\n/**\n * 让 brain 继承所用 agent 真实配置目录的「知识/记忆/技能」:\n * - 目录式(inheritDirs,如 kiro 的 steering/memories/skills、opencode 的 skills/agents/commands)用目录 junction(Windows)/ symlink 指向真实目录;\n * - 单文件式(inheritFiles,如 opencode 的 AGENTS.md)直接拷入配置目录。\n * 使微信里的大脑和你日常使用读到同样的记忆、规则、技能。\n * **MCP(settings/opencode.json 的 mcp 段)与 brain 自有会话(sessions)不照搬**:MCP 必须走沙箱(排除 wechat,防它把消息发回自己),会话不能混。\n */\nfunction mirrorRealKiro(ag: ResolvedAgent): void {\n syncInherited(BRAIN_HOME, brainConfigDir(ag), ag);\n}\n\n/** 宽松读取(JSONC):Gemini 的 settings.json 允许注释,严格 JSON.parse 会把合法配置当损坏。 */\nfunction readJson(file: string): Record<string, unknown> | null {\n try {\n return parseJsonc(fs.readFileSync(file, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n/**\n * 把 agent 的凭据文件同步进沙箱(如 DSH 的 `.credentials.yaml`、Qoder 的 `.auth/user`)。\n * - `newer`:首次复制;真实 home 的 token 刷新后覆盖沙箱;沙箱自己刷新过则保留沙箱版本。\n * - `always`:内容不同即覆盖(标识类文件,如 Qoder 的 `.auth/machine_id`——agent 在缺凭据时会自造一个,它比真实文件新,「新者胜」会把错的留下,导致永远认证失败)。\n * 这是敏感文件,仅放在账号沙箱内;Windows 无 POSIX 600,建议专用账号 + 目录 ACL。\n */\nfunction syncCredentialFile(ag: ResolvedAgent, file: string, mode: \"newer\" | \"always\"): void {\n if (!ag.realHome) return;\n const src = path.join(ag.realHome, file);\n const dst = path.join(brainConfigDir(ag), file);\n try {\n if (!fs.existsSync(src)) return;\n fs.mkdirSync(path.dirname(dst), { recursive: true });\n if (mode === \"always\") {\n const same = fs.existsSync(dst) && fs.readFileSync(src).equals(fs.readFileSync(dst));\n if (!same) fs.copyFileSync(src, dst);\n return;\n }\n const s = fs.statSync(src);\n const d = fs.existsSync(dst) ? fs.statSync(dst) : null;\n if (!d || s.mtimeMs > d.mtimeMs + 1000) fs.copyFileSync(src, dst);\n } catch {\n /* best-effort */\n }\n}\n\nfunction syncCredentials(ag: ResolvedAgent): void {\n for (const f of ag.credentialFiles) syncCredentialFile(ag, f, \"newer\");\n for (const f of ag.identityFiles) syncCredentialFile(ag, f, \"always\");\n}\n\n/** 把预设的默认设置项补进沙箱 settings.json:只补缺失键(用户显式设过就保留用户的值),真实配置不动。 */\nfunction applySettingsDefaults(ag: ResolvedAgent): void {\n if (Object.keys(ag.settingsDefaults).length === 0) return;\n const file = path.join(brainConfigDir(ag), \"settings.json\");\n const cur = readJson(file) ?? {};\n let changed = false;\n for (const [k, v] of Object.entries(ag.settingsDefaults)) {\n if (cur[k] === undefined) {\n cur[k] = v;\n changed = true;\n }\n }\n if (!changed) return;\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(cur, null, 2), \"utf-8\");\n } catch {\n /* best-effort */\n }\n}\n\nfunction loadCatalog(ag: ResolvedAgent): Record<string, unknown> {\n return readMcpServers(readJson(catalogPath(ag)), ag.mcpFormat);\n}\n\n/** 目录中可选的 server 名(排除禁用项)。 */\nexport function catalogNames(): string[] {\n const ag = getAgent(\"brain\");\n const cat = loadCatalog(ag);\n return Object.keys(cat).filter((n) => !forbiddenServer(n, cat[n]));\n}\n\nexport function loadActive(): string[] {\n const j = readJson(ACTIVE_FILE);\n const a = j?.active;\n return Array.isArray(a) ? (a as string[]).filter((x) => typeof x === \"string\") : [];\n}\n\nfunction saveActive(list: string[]): void {\n fs.mkdirSync(DATA_DIR, { recursive: true });\n fs.writeFileSync(ACTIVE_FILE, JSON.stringify({ active: list }, null, 2), \"utf-8\");\n}\n\n/** 按激活集从目录取定义,写入沙箱 MCP 配置(默认空)。保留配置里其它键(如 opencode 的 model)。\n * 配了 `mcpViaAcp` 的 agent(DSH)不写文件——激活集经 activeMcpServers() 转成 ACP mcpServers 随 session 下发。 */\nfunction applySandbox(ag: ResolvedAgent): void {\n if (ag.mcpViaAcp) return;\n const cat = loadCatalog(ag);\n const active = loadActive().filter((n) => cat[n] && !forbiddenServer(n, cat[n]));\n const servers: Record<string, unknown> = {};\n for (const n of active) servers[n] = cat[n];\n const file = brainMcpJson(ag);\n const existing = readJson(file) ?? {};\n const next = withMcpServers(existing, servers, ag.mcpFormat);\n // opencode 的免审批只能走配置(`opencode acp` 不接受 --auto,官方 issue #52216):默认给 \"allow\";\n // 用户自己在 opencode.json 里写了 permission(含 deny)就以用户的为准,不覆盖。\n if (ag.mcpFormat === \"opencode\" && next.permission === undefined) next.permission = \"allow\";\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(next, null, 2), \"utf-8\");\n}\n\n/** brain 当前应挂载的 ACP mcpServers(配了 mcpViaAcp 的 agent 用):激活集 → catalog 定义 → ACP 声明。\n * 跳过不可转换的坏条目并记日志——官方 DSH 的 session/new 遇到起不来的 MCP 会回滚整个会话。\n * **必须传正在启动的那个 agent**:热切换时槽位还指着旧大脑,用默认值会把旧 agent 的 MCP 目录发给新大脑。 */\nexport function activeMcpServers(ag: ResolvedAgent = getAgent(\"brain\")) {\n const cat = loadCatalog(ag);\n const active = loadActive().filter((n) => cat[n] && !forbiddenServer(n, cat[n]));\n const picked: Record<string, unknown> = {};\n for (const n of active) picked[n] = cat[n];\n const { servers, skipped } = toAcpMcpServers(picked, { exclude: FORBIDDEN });\n if (skipped.length)\n console.error(`[wemind/wechat-daemon/mcp] 跳过无法转换的 MCP:${skipped.join(\", \")}`);\n return servers;\n}\n\n/** kiro:继承真实 settings/ 下除 mcp.json 外的文件(cli.json 含模型/免确认等),让大脑与日常使用同款配置。 */\nfunction syncKiroSettings(ag: ResolvedAgent): void {\n const realSettings = path.join(ag.realHome!, \"settings\");\n const brainSettings = path.join(brainConfigDir(ag), \"settings\");\n try {\n fs.mkdirSync(brainSettings, { recursive: true });\n } catch {\n /* ignore */\n }\n let names: string[] = [];\n try {\n names = fs.readdirSync(realSettings);\n } catch {\n /* 真实配置目录不存在:v3 仍需要沙箱里的默认 permissions.yaml */\n }\n for (const f of names) {\n if (f === \"mcp.json\") continue; // MCP 必须用沙箱 curated 版本(防 wechat 回环)\n try {\n const src = path.join(realSettings, f);\n if (fs.statSync(src).isFile()) fs.copyFileSync(src, path.join(brainSettings, f));\n } catch {\n /* best-effort */\n }\n }\n ensureKiroV3Permissions(ag, brainSettings);\n}\n\n/** opencode:把用户全局 opencode.json(c)(去掉 mcp 段)作为基底写进沙箱,继承 model 等设置;mcp 仍由 applySandbox 权威覆盖。 */\nfunction syncOpencodeBase(ag: ResolvedAgent): void {\n const src = [\"opencode.json\", \"opencode.jsonc\"]\n .map((f) => path.join(ag.realHome!, f))\n .find((f) => fs.existsSync(f));\n if (!src) return;\n let parsed: Record<string, unknown> | null = null;\n try {\n const raw = fs.readFileSync(src, \"utf-8\");\n parsed = src.endsWith(\".jsonc\")\n ? parseJsonc(raw)\n : (JSON.parse(raw) as Record<string, unknown>);\n } catch {\n return;\n }\n if (!parsed || typeof parsed !== \"object\") return;\n delete (parsed as Record<string, unknown>).mcp; // MCP 必须走沙箱 curated(防 wechat 回环)\n const file = brainMcpJson(ag);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(parsed, null, 2), \"utf-8\");\n}\n\n/** 「设置与 MCP 同文件」的 agent:以真实 settings.json 为基底生成沙箱设置。\n * 剥离必须由沙箱权威决定的键(`ag.settingsStrip`,默认 DEFAULT_SETTINGS_STRIP):\n * - `mcpServers`:MCP 只能来自沙箱 curated(防 wechat 回环);\n * - `enabledPlugins`:插件可自带 MCP,且沙箱不继承 `plugins/`;\n * - `providers` / `model`:自定义供应商(BYOK)含明文密钥,`model` 又可能指向已剥离的供应商。\n * gemini 只剥 `mcpServers`——它的 model/security 是用户自己的选择,必须原样保留。\n * 并强制 `general.enableAutoUpdate=false`:否则每个沙箱都会各下一份约 210 MB 的 CLI。 */\nfunction settingsBase(ag: ResolvedAgent): Record<string, unknown> {\n let parsed: unknown = null;\n try {\n parsed = parseJsonc(fs.readFileSync(path.join(ag.realHome!, ag.settingsBase!), \"utf-8\"));\n } catch {\n /* 缺失/损坏 → 空基底 */\n }\n const base =\n parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? { ...(parsed as Record<string, unknown>) }\n : {};\n for (const k of ag.settingsStrip) delete base[k];\n const general = base.general;\n base.general = {\n ...(general && typeof general === \"object\" && !Array.isArray(general)\n ? (general as Record<string, unknown>)\n : {}),\n enableAutoUpdate: false,\n };\n return base;\n}\n\nfunction syncSettingsBase(ag: ResolvedAgent): void {\n const dir = brainConfigDir(ag);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(brainMcpJson(ag), JSON.stringify(settingsBase(ag), null, 2), \"utf-8\");\n}\n\n/** 继承真实 profile 的用户层(DSH:`profiles/<名>/cordis.patch.yml`,桌面端写 provider 目录与默认模型)。\n * **原样复制**——这份文件是用户自己的配置,WeMind 只镜像、不合成(`- id: acp` 这类覆盖由用户自己维护,\n * 缺失时 `wemind brain` 会提示怎么补;见 `profile-model.ts` 的 `acpOverrideHint`),这样「终端 dsh」与「微信大脑」行为一致。\n * 按内容同步(桌面端更新后覆盖);**唯独剔除用户自己挂的微信 MCP 条目**(`stripWechatMcpEntries`):\n * 沙箱里的 MCP 只能是 WeMind 的激活集(`wechat` 永远不在里面),否则 agent 会拿同一个 bot token 起第二个消费者。 */\nfunction syncProfilePatch(ag: ResolvedAgent): void {\n if (!ag.realProfile || !ag.realHome || !ag.inherit) return;\n if (path.resolve(BRAIN_HOME) === path.resolve(ag.realHome)) return; // 沙箱就是真实目录时不自我覆盖\n const src = path.join(ag.realHome, \"profiles\", ag.realProfile, \"cordis.patch.yml\");\n try {\n if (!fs.existsSync(src)) return;\n const removed = copyProfilePatch(ag.realHome, ag.realProfile, BRAIN_HOME);\n if (removed.length) {\n console.error(\n `[wemind/wechat-daemon/mcp] 真实 profile 里的微信 MCP 已从大脑沙箱副本剔除(防回环,真实文件未改):${removed.join(\", \")}`,\n );\n }\n } catch {\n /* best-effort */\n }\n}\n\n/** 继承真实配置目录的「非 MCP」设置(模型/免确认等),让大脑与日常使用同款配置;mcp 段保持沙箱权威。\n * 配了 `mcpViaAcp` 的 agent 也仍有「profile 用户层」可继承(DSH 的 provider 目录/默认模型),只是 MCP 不写文件。 */\nfunction syncSettings(ag: ResolvedAgent): void {\n syncProfilePatch(ag);\n if (ag.mcpViaAcp) return;\n if (ag.settingsBase && ag.realHome) {\n syncSettingsBase(ag); // 不依赖 inherit:沙箱硬化(关自动更新)与隔离始终生效\n return;\n }\n if (!ag.inherit || !ag.realHome) return;\n if (ag.mcpFormat === \"opencode\") syncOpencodeBase(ag);\n else syncKiroSettings(ag);\n}\n\n/**\n * 启动时确保沙箱存在并与激活集一致。\n * `forAgent` 可显式指定 agent(切换流程启动新大脑用:启动新大脑时要先备好**它**的沙箱,此刻槽位还指着旧大脑)。\n */\nexport function ensureBrainHome(forAgent?: ResolvedAgent): void {\n const ag = forAgent ?? getAgent(\"brain\");\n syncSettings(ag); // 先同步「非 MCP」设置(模型/免确认等),让大脑与日常使用同款配置\n applySandbox(ag); // 再写 curated mcp(权威,保持 MCP 隔离——防它把消息发回自己)\n mirrorRealKiro(ag); // 继承真实配置目录的记忆/steering/skills(sessions 仍隔离)\n syncCredentials(ag); // 登录态/凭据(如 DSH 的 .credentials.yaml、Qoder 的 .auth/*)\n applySettingsDefaults(ag); // 预设默认设置项(如 pi 的 quietStartup:关掉 pi-acp 当消息发出来的启动横幅)\n}\n\nexport interface McpChangeResult {\n /** 指令是否执行成功;失败时调用方不应重启大脑。 */\n ok: boolean;\n /** 激活集是否真的变了;没变就不必重启 ACP 进程。 */\n changed: boolean;\n /** 给用户的答复原文。 */\n text: string;\n}\n\nexport function addMcp(name: string): McpChangeResult {\n const ag = getAgent(\"brain\");\n const cat = loadCatalog(ag);\n if (forbiddenServer(name, cat[name]))\n return { ok: false, changed: false, text: `不允许加载「${name}」(防它把消息发回自己)。` };\n if (!cat[name])\n return {\n ok: false,\n changed: false,\n text: `未找到 MCP「${name}」。可选:${catalogNames().join(\", \") || \"(无)\"}`,\n };\n // 预校验:坏条目不进激活集(官方 DSH 的 session/new 会因起不来的 MCP 回滚整会话)\n if (ag.mcpViaAcp && !toAcpMcpServers({ [name]: cat[name] }).servers.length)\n return {\n ok: false,\n changed: false,\n text: `MCP「${name}」的定义无法转换(需要 command 或 url),已拒绝加载。`,\n };\n const a = loadActive();\n const changed = !a.includes(name);\n if (changed) {\n a.push(name);\n saveActive(a);\n }\n applySandbox(ag);\n return {\n ok: true,\n changed,\n text: `已加载 MCP「${name}」。当前激活:${a.join(\", \")}(下一条消息生效)`,\n };\n}\n\nexport function removeMcp(name: string): McpChangeResult {\n const a = loadActive();\n if (!a.includes(name)) return { ok: false, changed: false, text: `「${name}」当前未激活。` };\n const next = a.filter((x) => x !== name);\n saveActive(next);\n applySandbox(getAgent(\"brain\"));\n return {\n ok: true,\n changed: true,\n text: `已卸载 MCP「${name}」。当前激活:${next.join(\", \") || \"(空)\"}`,\n };\n}\n\nexport function listMcp(): string {\n const avail = catalogNames();\n const active = loadActive();\n return `可选 MCP:${avail.join(\", \") || \"(无)\"}\\n已激活:${active.join(\", \") || \"(空,默认不加载)\"}`;\n}\n","// 进程身份校验:pid 会复用(尤其**重启后**),只凭「pid 还活着」(process.kill(pid, 0))就杀进程、或判定「daemon 在跑」,可能**误杀别人的进程**(陈旧 pid 文件 + pid 被复用)或**误判**(自启看似成功、其实压根没起来)。这里改用「命令行里有没有我们自己的特征串」来确认身份。\n//\n// 原则:查不到命令行 → 一律判否(保守,宁可不杀)。\n// 成本:只有 Windows 需要经 PowerShell 查 CIM(约 1~2s),所以调用方**先 isAlive() 过滤**——正常路径(陈旧 pid 早就不存在了)零开销,只有「确实有活着的 pid」时才付这笔查询成本。\nimport { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/** pid 是否活着(零成本,仅探活;不代表是我们的进程)。 */\nexport function isAlive(pid: number | null | undefined): boolean {\n if (pid === null || pid === undefined || !Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** 归一化:小写 + 反斜杠统一成正斜杠,便于跨平台、跨相对/绝对路径匹配。 */\nfunction normalize(s: string): string {\n return s.replaceAll(\"\\\\\", \"/\").toLowerCase();\n}\n\n/**\n * 取文件名。**不依赖平台**:`/` 与 `\\` 都当分隔符——POSIX 的 `path.basename` 不认 `\\`,会把 Windows 风格路径整条留下(CI 上踩过)。\n */\nfunction baseName(p: string): string {\n const i = Math.max(p.lastIndexOf(\"/\"), p.lastIndexOf(\"\\\\\"));\n return i >= 0 ? p.slice(i + 1) : p;\n}\n\n/** 某条命令行是否命中「任一 marker 组合」(组合内每个 token 都必须出现、大小写与斜杠不敏感)。 */\nexport function matchesMarkers(line: string | null | undefined, markerSets: string[][]): boolean {\n if (!line) return false;\n const hay = normalize(line);\n return markerSets.some((set) => set.length > 0 && set.every((t) => hay.includes(normalize(t))));\n}\n\nfunction parsePipePairs(stdout: string, out: Map<number, string>): void {\n for (const raw of stdout.split(/\\r?\\n/)) {\n const i = raw.indexOf(\"|\");\n if (i <= 0) continue;\n const pid = Number.parseInt(raw.slice(0, i).trim(), 10);\n if (Number.isInteger(pid) && pid > 0) out.set(pid, raw.slice(i + 1));\n }\n}\n\n/**\n * 批量查询 pid → 命令行。查不到(不存在 / 无权限 / 平台不支持)的 pid 不会出现在结果里。\n * 只查询「活着的」pid(调用方传一堆陈旧 pid 也不会白白起进程)。\n */\nexport function commandLines(pids: Array<number | null | undefined>): Map<number, string> {\n const out = new Map<number, string>();\n const list = [...new Set(pids.filter((p): p is number => isAlive(p)))];\n if (!list.length) return out;\n try {\n if (process.platform === \"win32\") {\n // wmic 已被新版 Windows 移除 → 用系统自带的 Windows PowerShell 5.1 + CIM;一次查询多个 pid。\n const ps = path.join(\n process.env.SystemRoot ?? \"C:\\\\Windows\",\n \"System32\",\n \"WindowsPowerShell\",\n \"v1.0\",\n \"powershell.exe\",\n );\n const filter = list.map((p) => `ProcessId=${p}`).join(\" or \");\n const script = `Get-CimInstance Win32_Process -Filter \"${filter}\" | ForEach-Object { \"$($_.ProcessId)|$($_.CommandLine)\" }`;\n const r = spawnSync(ps, [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script], {\n encoding: \"utf8\",\n windowsHide: true,\n timeout: 15000,\n });\n parsePipePairs(r.stdout ?? \"\", out);\n } else if (process.platform === \"linux\") {\n for (const p of list) {\n try {\n out.set(p, fs.readFileSync(`/proc/${p}/cmdline`, \"utf-8\").replaceAll(\"\\0\", \" \").trim());\n } catch {\n /* 读不到就当查不到 */\n }\n }\n } else {\n // darwin 等:用 ps\n for (const p of list) {\n const r = spawnSync(\"ps\", [\"-o\", \"command=\", \"-p\", String(p)], {\n encoding: \"utf8\",\n timeout: 5000,\n });\n const line = (r.stdout ?? \"\").trim();\n if (line) out.set(p, line);\n }\n }\n } catch {\n /* 查询失败 → 返回空(判否,保守) */\n }\n return out;\n}\n\n/** 该 pid 是否「活着**且确实是我们自己的**进程」(命令行命中 markerSets 之一)。 */\nexport function isOurProcess(pid: number | null | undefined, markerSets: string[][]): boolean {\n if (!isAlive(pid)) return false;\n return matchesMarkers(commandLines([pid]).get(pid as number), markerSets);\n}\n\n/**\n * 「同账号已有另一个我们自己的进程」判定:返回那个 pid,否则 null。\n * 排除自己(同一个 pid 写进文件时不算冲突);陈旧 pid / pid 复用不算(交给 isOurProcess 的身份校验)。\n * `check` 仅为单测可注入,生产路径用 `isOurProcess`。\n */\nexport function otherOurProcessPid(\n pid: number | null | undefined,\n selfPid: number,\n markerSets: string[][],\n check: (pid: number, markerSets: string[][]) => boolean = isOurProcess,\n): number | null {\n if (pid === null || pid === undefined || pid === selfPid) return null;\n return check(pid, markerSets) ? pid : null;\n}\n\n/**\n * 我们自己的 CLI / wrapper 进程特征:命令行里同时含 `wemind` + `cli.js`。\n *\n * 这串必须同时覆盖**四种**安装方式(少一种 → 对应场景下 stop/list/单实例判断全失效):\n * - 源码仓库(绝对路径):`<node> D:\\repo\\WeMind\\packages\\wechat-daemon\\dist\\cli.js daemon`(由 marker 组合里的 `wemind` 命中仓库名 WeMind;注意大小写不敏感比较在下面已处理);\n * - 源码仓库(**相对路径**):`<node> packages/wechat-daemon/dist/cli.js daemon`(命令行里既没有仓库名也没有包名,只有源码目录名 → 靠第三组 marker `wechat-daemon` + `cli.js` 命中)。没有这一组时,「在仓库根目录手跑 CLI + daemon」会互相认不出(daemon 明明活着,CLI 却说没运行,控制桥也拒绝发送);\n * - 全局/本地安装:`<node> …/node_modules/@wemind/cli/dist/cli.js daemon`(npm 的 bin shim 最终 exec 的正是这个路径;Windows 上可能走 `…\\@wemind\\cli\\dist\\cli.js` 短路径。两种形态都命中 marker 里的 `wemind`——它同时是 scope 名与命令名);\n * - 源码开发期的 PowerShell wrapper:`pwsh -File …\\scripts\\run-daemon.ps1`。\n *\n * 注意**不能**用 `wechat-daemon` **替代** `wemind`:源码目录名在发布版里不存在(包目录叫 `@wemind/cli`),只有它会让「装出来的 wemind」永远匹配不上、list 永远显示「已停止」。它只作为**追加**的一组,补上「相对路径启动」这一种形态;并要求 `dist` 一起出现,把「别的项目里恰好有个叫 wechat-daemon 的目录」这类误判(这串最终用于**杀进程**,宁可漏也要少误)。\n */\nexport const CLI_PID_MARKERS: string[][] = [\n [\"wemind\", \"cli.js\"],\n [\"wechat-daemon\", \"dist\", \"cli.js\"],\n [\"run-daemon.ps1\"],\n];\n\n/**\n * 大脑进程特征:`<bin> <args…>`(如 `opencode acp`、`kiro-cli acp`)。\n * 取 bin 的**文件名**并去掉扩展名,兼顾 shell=true(命令行是原始命令)与 `--bin` 绝对路径两种情况;args 是关键区分位——用户自己的 `opencode serve`(本机 OpenCode 服务)不含 `acp`,不会被误判。\n * bin 为空(配置异常)时返回空数组 → matchesMarkers 恒判否,绝不误杀。\n */\nexport function brainPidMarkers(bin: string, args: string[]): string[][] {\n const name = baseName(bin ?? \"\").replace(/\\.(exe|cmd|bat|ps1)$/i, \"\");\n if (!name) return []; // bin 为空 / 只剩目录(配置异常)→ 恒判否,绝不误杀\n return [[name, ...args].filter((t) => t !== \"\")];\n}\n","import fs from \"node:fs\";\n\nimport { writeJsonAtomic } from \"./atomic-json.js\";\nimport { brainPidMarkers } from \"./proc-identity.js\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\n\n/**\n * ACP 进程的「身份记录」文件(`brain.pid` / `workers.pid.json`)。\n *\n * 为什么从裸 pid 改成 JSON 记录:裸 pid 只说明「有个进程活着」,说明不了「它是不是我们起的、用的是哪个 agent」。\n * `manage.ts` 过去用**配置里**的 agent 算进程特征串(bin+args)来判定/杀进程,这在三种情况下会失效:\n * ① `wemind brain set X` 之后(配置已变、还跑着旧 agent 的进程);\n * ② 热切换大脑的窗口内;\n * ③ **多任务/多 agent 并存**时,live worker 永远不等于单个配置值(多 worker 之后这是常态)。\n * 于是把「起进程时**真实用的** bin/args」写进记录,`manage.ts` 直接按记录算特征串,不再猜。\n *\n * 兼容:旧文件是裸整数(`12345`)→ 读成一条 `legacy: true` 的记录(无 bin/args),\n * 调用方回退到「按配置算特征串」的旧行为;旧版 CLI 读新文件会拿到 NaN → 显示「已停止」且**不杀**(安全侧,记 CHANGELOG)。\n */\n\nexport type PidState = \"busy\" | \"idle\";\n\nexport type PidRecord = {\n pid: number;\n /** agent 名。 */\n agent: string;\n /** 起进程时真实用的二进制与参数(算特征串用)。 */\n bin: string;\n args: string[];\n shell: boolean;\n /** 任务 worker 专有:当前执行的任务编号。 */\n taskId?: number;\n /** 池中状态(worker);brain 恒为 busy。 */\n state: PidState;\n startedAt: number;\n /** 最近一次写入磁盘时间(状态变化时刷新)。 */\n writtenAt: number;\n /** 来自旧版「裸 pid」文件:没有 bin/args,判定需回退到配置。 */\n legacy?: boolean;\n};\n\n/** 读 pid 记录文件:兼容「裸整数」「单个 JSON 对象」「JSON 数组」三种形态;读不到/损坏 → 空数组。 */\nexport function readPidRecords(file: string): PidRecord[] {\n let raw: string;\n try {\n raw = fs.readFileSync(file, \"utf-8\").trim();\n } catch {\n return [];\n }\n if (!raw) return [];\n if (/^\\d+$/.test(raw)) {\n const pid = Number(raw);\n return pid > 0\n ? [\n {\n pid,\n agent: \"\",\n bin: \"\",\n args: [],\n shell: false,\n state: \"busy\",\n startedAt: 0,\n writtenAt: 0,\n legacy: true,\n },\n ]\n : [];\n }\n let j: unknown;\n try {\n j = JSON.parse(raw);\n } catch {\n return [];\n }\n const arr = Array.isArray(j) ? j : [j];\n const out: PidRecord[] = [];\n for (const e of arr as Array<Record<string, unknown>>) {\n const pid = Number(e?.pid);\n if (!Number.isInteger(pid) || pid <= 0) continue;\n const rec: PidRecord = {\n pid,\n agent: typeof e.agent === \"string\" ? e.agent : \"\",\n bin: typeof e.bin === \"string\" ? e.bin : \"\",\n args: Array.isArray(e.args)\n ? (e.args as unknown[]).filter((x): x is string => typeof x === \"string\")\n : [],\n shell: e.shell === true,\n state: e.state === \"idle\" ? \"idle\" : \"busy\",\n startedAt: Number(e.startedAt) || 0,\n writtenAt: Number(e.writtenAt) || 0,\n };\n const tid = Number(e.taskId);\n if (Number.isInteger(tid) && tid > 0) rec.taskId = tid;\n out.push(rec);\n }\n return out;\n}\n\n/** 原子写记录文件;空数组 = 没有进程 = 删除文件。单条写对象、多条写数组(读侧都认)。 */\nexport function writePidRecords(file: string, recs: PidRecord[]): void {\n try {\n if (!recs.length) {\n fs.unlinkSync(file);\n return;\n }\n } catch {\n /* 文件不存在 → 继续写 */\n }\n try {\n const body = recs.length === 1 ? recs[0] : recs;\n writeJsonAtomic(file, body);\n } catch {\n /* best-effort:记录写失败不该影响起进程 */\n }\n}\n\n/** 由「刚 spawn 出来的 agent」构造一条记录。 */\nexport function pidRecordOf(\n pid: number,\n ag: Pick<ResolvedAgent, \"name\" | \"bin\" | \"args\" | \"shell\">,\n extra: { taskId?: number; state?: PidState } = {},\n): PidRecord {\n const now = Date.now();\n const rec: PidRecord = {\n pid,\n agent: ag.name,\n bin: ag.bin,\n args: ag.args,\n shell: ag.shell,\n state: extra.state ?? \"busy\",\n startedAt: now,\n writtenAt: now,\n };\n if (extra.taskId !== undefined) rec.taskId = extra.taskId;\n return rec;\n}\n\n/** 记录对应的进程特征串;`legacy`(无 bin)→ 空数组,调用方回退配置。 */\nexport function recordMarkers(rec: PidRecord): string[][] {\n return rec.bin ? brainPidMarkers(rec.bin, rec.args) : [];\n}\n","// 摘掉 VS Code 集成终端注入的调试引导——只用于**脱离式/常驻**的 spawn(见 watchdog-runner.ts)。\n//\n// VS Code 的「智能自动附加」会给终端里每个 node 进程注入两个变量:\n// NODE_OPTIONS=--require <…>/ms-vscode.js-debug/bootloader.js\n// VSCODE_INSPECTOR_OPTIONS={\"deferredMode\":true,\"autoAttachMode\":\"smart\",\"aaPatterns\":[\"<你的仓库>/**\",…],…}\n// 被注入的进程会**等调试器接上才执行主脚本**。终端里前台的命令 VS Code 会接上,所以看着一切正常;但 `wemind start` 脱离出去的后台守护进程/daemon 不会被接——进程活着却一行 JS 都不跑:`daemon.log` 不更新、`wrapper.pid` 不写、daemon 根本起不来,答复却说「已启动」(2026-10-29 实测)。\n//\n// 修法:脱离式 spawn 时把 VS Code 自己注入的那段 `--require` 摘掉,其余 `NODE_OPTIONS` 原样保留(用户可能设了 `--max-old-space-size` 之类);`VSCODE_INSPECTOR_OPTIONS` 一并删——它只对那个 bootloader 有意义。\n\n/** 匹配 VS Code 注入的 `--require <路径>/ms-vscode.js-debug/…/bootloader.js`(兼容 `--require=` 写法与带引号的路径)。 */\nconst VSCODE_REQUIRE =\n /--require(?:=|\\s+)(?:\"[^\"]*ms-vscode\\.js-debug[^\"]*bootloader\\.js\"|'[^']*ms-vscode\\.js-debug[^']*bootloader\\.js'|[^\\s\"']*ms-vscode\\.js-debug[^\\s\"']*bootloader\\.js)/gi;\n\n/** 返回摘掉 VS Code 调试引导的**新**环境对象(不修改入参;没有注入时原样返回一份拷贝)。 */\nexport function stripVscodeDebugEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const out: NodeJS.ProcessEnv = { ...env };\n const opts = out.NODE_OPTIONS;\n if (opts) {\n const next = opts.replace(VSCODE_REQUIRE, \" \");\n if (next !== opts) {\n // 只有真摘过东西才规整空白,其余情况保留用户原文(避免动到带空格的引号参数)。\n const kept = next.trim().replace(/\\s+/g, \" \");\n if (kept) out.NODE_OPTIONS = kept;\n else delete out.NODE_OPTIONS;\n }\n }\n delete out.VSCODE_INSPECTOR_OPTIONS;\n return out;\n}\n","import {\n spawn,\n type ChildProcessWithoutNullStreams,\n type SpawnOptionsWithoutStdio,\n} from \"node:child_process\";\n\nimport { stripVscodeDebugEnv } from \"./debug-env.js\";\n\n/*\n * 启动 ACP agent 进程的**统一入口**。\n *\n * 为什么不是 `spawn(bin, args[], { shell: true })`:\n * Node 20+ 对这种写法报 DEP0190——`shell:true` 时 `args` 只是用空格拼进命令串、不做任何转义(既是注入面,也会在 daemon.log 里每次冷启动刷两行弃用警告)。\n * 正确做法是**自己把整条命令拼成一个字符串**再交给 shell,参数由我们逐条加引号。\n * 仓库里的 pm2-runner.ts 出于同样原因用了同样的写法。\n *\n * 为什么仍然要经 shell:\n * Windows 上 npm shim(opencode / npx / gemini)都是 `.cmd`,而 Node 20+ **禁止**无 shell 直接 spawn `.cmd`/`.bat`(EINVAL,CVE-2024-27980 之后的安全限制),所以只能让 cmd.exe 去解析 PATH。POSIX 上同一份代码走 `/bin/sh -c`,正好覆盖两侧。\n *\n * 注:既然经 shell,cmd 的元字符语义就生效(参数必须逐条加引号;`%VAR%` 仍会被展开),这与改动前的行为一致,不引入新语义。\n */\n\n/** 启动所需的最小 agent 描述(`ResolvedAgent` 的子集,避免反向依赖 brain-agents)。 */\nexport type SpawnableAgent = {\n bin: string;\n args: readonly string[];\n shell: boolean;\n};\n\n/** cmd 需要加引号的字符:空白 + 元字符(不加引号时 cmd 会自行解释:`&` 断命令、`|` 管道…)。 */\nconst CMD_NEEDS_QUOTE = /[\\s\"&|<>^()%!]/;\n\n/**\n * cmd.exe(MS CRT)参数转义:无空白/元字符则裸写;否则用双引号包裹,\n * 并按其规则处理引号前的前导反斜杠与结尾反斜杠。\n * 注:引号挡得住元字符,但挡不住 `%VAR%` 展开(cmd 命令行无法转义 `%`),与改动前行为一致。\n */\nexport function quoteCmdArg(arg: string): string {\n if (arg === \"\") return '\"\"';\n if (!CMD_NEEDS_QUOTE.test(arg)) return arg;\n return `\"${arg.replace(/(\\\\*)\"/g, '$1$1\\\\\"').replace(/(\\\\+)$/, \"$1$1\")}\"`;\n}\n\n/** POSIX sh 参数转义:安全字符集裸写,否则单引号包裹(内部单引号写成 `'\\''`)。 */\nexport function quoteShArg(arg: string): string {\n if (arg === \"\") return \"''\";\n if (!/[^\\w@%+=:,./-]/.test(arg)) return arg;\n return `'${arg.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** 把 `bin + args` 拼成一条交给 shell 的命令行(`platform` 仅为可测性暴露)。 */\nexport function buildShellLine(\n bin: string,\n args: readonly string[],\n platform: NodeJS.Platform = process.platform,\n): string {\n const quote = platform === \"win32\" ? quoteCmdArg : quoteShArg;\n return [bin, ...args].map(quote).join(\" \");\n}\n\n/**\n * Windows 经 shell 启动时的子进程环境:设置 `NoDefaultCurrentDirectoryInExePath=1`,禁止 cmd/CreateProcess\n * 从**当前目录**解析裸命令名。worker 的 cwd 是任务目录(`--cwd`,可能指向用户项目),里面若有一个同名\n * `.cmd`,就会被当成要启动的 agent——这是提权/劫持面。POSIX 原样返回,不引入 Windows 专用变量。\n * 导出仅供单测。\n */\nexport function shellEnv(\n platform: NodeJS.Platform,\n env: NodeJS.ProcessEnv | undefined,\n): NodeJS.ProcessEnv | undefined {\n if (platform !== \"win32\") return env;\n return { ...(env ?? process.env), NoDefaultCurrentDirectoryInExePath: \"1\" };\n}\n\n/**\n * 交给 ACP 子进程的环境:摘掉 VS Code 注入的调试引导(见 `debug-env.ts`)。\n *\n * 为什么子进程也要摘:前台 `wemind daemon` 在 VS Code 集成终端里启动时,daemon 自身被接上、跑得好好的,\n * 但它 spawn 的 agent(多为 node 脚本)会继承 `NODE_OPTIONS=--require …/bootloader.js`,等一个永远不会来的调试器,\n * 表现为 `initialize` 迟迟不回。与 watchdog 的脱离式 spawn 是同一个注入,处理方式相同。\n * 导出仅供单测。\n */\nexport function spawnEnv(env: NodeJS.ProcessEnv | undefined): NodeJS.ProcessEnv {\n return stripVscodeDebugEnv(env ?? process.env);\n}\n\n/**\n * 启动 agent:`shell=true` 的走「整串命令行 + shell」(见文件头注释),其余直接 spawn。\n * 默认 `windowsHide: true`(不闪控制台窗口),其余选项透传。\n */\nexport function spawnAgent(\n agent: SpawnableAgent,\n opts: SpawnOptionsWithoutStdio = {},\n): ChildProcessWithoutNullStreams {\n const base: SpawnOptionsWithoutStdio = {\n windowsHide: true,\n ...opts,\n env: spawnEnv(opts.env),\n };\n if (!agent.shell) return spawn(agent.bin, agent.args, base);\n const shellOpts: SpawnOptionsWithoutStdio = { ...base, shell: true };\n const env = shellEnv(process.platform, base.env);\n if (env !== base.env) shellOpts.env = env;\n return spawn(buildShellLine(agent.bin, agent.args), shellOpts);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\nimport { writeJsonAtomic } from \"./atomic-json.js\";\n\n/**\n * brainstorm/worker 共用的 session 映射键规则:`<agent>:<key>`。\n * 换大脑后各 agent 的会话互不覆盖,切回原大脑仍能 session/load 回原上下文。\n */\nexport function sessionKey(agent: string, key: string): string {\n return `${agent}:${key}`;\n}\n\n/** 对话大脑的当前会话表路径(`<agent>:<userId>` → sessionId)。 */\nexport function brainSessionMapPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"brain-sessions.json\");\n}\n\n/** 任务 worker 的当前会话表路径(`<agent>:<taskKey>` → sessionId)。 */\nexport function workerSessionMapPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"worker-sessions.json\");\n}\n\n/**\n * 严格读当前会话表:缺失 → 空表;其它读/解析/格式错误一律抛出,由调用方决定备份或降级。\n * 只保留字符串值,**不做格式迁移**(迁移归 normalizeSessionMap)。\n */\nexport function readSessionMapStrict(file: string): Record<string, string> {\n let text: string;\n try {\n text = fs.readFileSync(file, \"utf-8\");\n } catch (e) {\n if ((e as NodeJS.ErrnoException)?.code === \"ENOENT\") return {};\n throw e;\n }\n const raw: unknown = JSON.parse(text);\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n throw new Error(`会话表 ${file} 不是 JSON 对象`);\n }\n const map: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof v === \"string\" && v) map[k] = v;\n }\n return map;\n}\n\n/** 读当前会话表(只读场景);缺失/损坏/非对象 → 空表(幂等、不抛)。写入前的读取请用 readSessionMapStrict + backupCorruptSessionMap。 */\nexport function readSessionMap(file: string): Record<string, string> {\n try {\n return readSessionMapStrict(file);\n } catch {\n return {};\n }\n}\n\n/**\n * 会话表损坏时的兜底:把原文件改名备份为 `<file>.corrupt-<时间戳>`,返回备份路径;改名失败返回 undefined。\n * 备份保证后续写入不会把可能还能人工恢复的内容整张覆盖。\n */\nexport function backupCorruptSessionMap(file: string): string | undefined {\n const backup = `${file}.corrupt-${Date.now()}`;\n try {\n fs.renameSync(file, backup);\n return backup;\n } catch {\n return undefined;\n }\n}\n\n/** 原子写当前会话表(唯一临时文件 + rename):这个文件在关键路径上(ensureSession → session/load),半截 JSON 等于「所有会话丢失」。 */\nexport function writeSessionMap(file: string, map: Record<string, string>): void {\n writeJsonAtomic(file, map);\n}\n\n/** 清掉某个键的当前会话记录(如「删除会话」删的正是当前那条);返回是否确有改动。 */\nexport function clearSessionPointer(file: string, key: string): boolean {\n const map = readSessionMap(file);\n if (!(key in map)) return false;\n delete map[key];\n writeSessionMap(file, map);\n return true;\n}\n\n/**\n * 把磁盘上的映射归一成 `<agent>:<key>` 形式。\n * - 已带前缀的条目原样保留(其它 agent 的会话不动);\n * - 旧格式(裸 key)由「当时那个 agent」写入、已无从得知是哪个 → 归给当前 agent;若同名的新格式条目已存在,则保留新格式(它的归属确定),只丢掉裸条目。\n * migrated=true 时调用方应立即回写,避免下次换 agent 时旧条目再被继承一次。\n */\nexport function normalizeSessionMap(\n raw: unknown,\n agent: string,\n): { map: Record<string, string>; migrated: boolean } {\n const map: Record<string, string> = {};\n const legacy: Array<[string, string]> = [];\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return { map, migrated: false };\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof v !== \"string\") continue;\n if (k.includes(\":\")) map[k] = v;\n else legacy.push([k, v]);\n }\n for (const [k, v] of legacy) {\n const nk = sessionKey(agent, k);\n if (!map[nk]) map[nk] = v;\n }\n return { map, migrated: legacy.length > 0 };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\nimport { writeJsonAtomic } from \"./atomic-json.js\";\nimport {\n brainSessionMapPath,\n clearSessionPointer,\n readSessionMap,\n sessionKey,\n} from \"./session-map.js\";\n\n/**\n * 会话登记表(`<数据目录>/session-registry.json`):跨大脑记住「这个人有哪些会话」,\n * 让「会话列表 / 恢复会话 / 重命名 / 删除」有东西可列——`brain-sessions.json` 只存**当前那一个**会话的 id,\n * 一开新会话,旧的就失去引用、无处可恢复。\n *\n * 为什么与 `brain-sessions.json` 分开:\n * - 当前会话表在关键路径上(ensureSession → session/load)且已背了一层旧格式迁移(见 session-map.ts);\n * 把它的值从 string 改成 object 要再迁一次,还会波及 worker 的孪生代码。\n * - 登记表是**派生元数据**:损坏/丢失只应导致「列表空」,绝不能影响恢复。\n * 因此一致性的规则是:**当前会话表唯一权威地表示「当前是哪个」**;登记表只供列表,且自己**不存 current**\n * (渲染时才与当前会话记录比对);`删除会话` 若是当前那条,则同时清当前会话记录(下一次消息自然开新的)。\n *\n * 写入方只有 daemon(或 CLI 经控制桥,见 control-bridge.ts),避免多写者互相覆盖。\n * 所有写入走 tmp + rename;所有读取失败都降级为空表,只记日志、不抛——它不该有能力拖垮 daemon。\n */\n\nexport type SessionEntry = {\n /** 所属大脑(agent 名,小写)。 */\n agent: string;\n /** 会话属于谁(微信 userId);登记表按人隔离,别人的会话列不出来。 */\n userId: string;\n /** ACP sessionId。 */\n sessionId: string;\n /** 用户起的名字(可寻址:`恢复会话 项目A`);命名后不会被淘汰。 */\n name?: string;\n /** 首条用户消息的前若干字,作列表里的摘要。 */\n title?: string;\n /** 成功回合数(列表里的「轮次」)。 */\n turns: number;\n createdAt: number;\n lastUsedAt: number;\n /** 来自当前会话表回填(功能上线前就存在的会话,没有真实时间/轮次)。 */\n backfilled?: boolean;\n};\n\nexport type SessionRegistry = { version: number; entries: SessionEntry[] };\n\nexport const REGISTRY_VERSION = 1;\n\n/** 每个 (agent, userId) 的条数上限;超出先淘汰「未命名且最旧」的。 */\nexport const MAX_ENTRIES_PER_KEY = Number(process.env.WEMIND_SESSION_LOG_MAX) || 50;\n\n/** 列表分页大小。 */\nexport const PAGE_SIZE = Number(process.env.WEMIND_SESSION_PAGE_SIZE) || 10;\n\nexport function registryPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"session-registry.json\");\n}\n\n/** 归一化一条记录;缺 agent/userId/sessionId 视为无效(丢弃)。 */\nfunction normEntry(e: any, now: number): SessionEntry | null {\n if (!e || typeof e !== \"object\" || Array.isArray(e)) return null;\n const agent = typeof e.agent === \"string\" ? e.agent.trim().toLowerCase() : \"\";\n const userId = typeof e.userId === \"string\" ? e.userId.trim() : \"\";\n const sessionId = typeof e.sessionId === \"string\" ? e.sessionId.trim() : \"\";\n if (!agent || !userId || !sessionId) return null;\n const num = (v: unknown, d: number): number =>\n typeof v === \"number\" && Number.isFinite(v) && v >= 0 ? v : d;\n const out: SessionEntry = {\n agent,\n userId,\n sessionId,\n turns: Math.trunc(num(e.turns, 0)),\n createdAt: num(e.createdAt, now),\n lastUsedAt: num(e.lastUsedAt, now),\n };\n if (typeof e.name === \"string\" && e.name.trim()) out.name = e.name.trim();\n if (typeof e.title === \"string\" && e.title.trim()) out.title = e.title.trim();\n if (e.backfilled === true) out.backfilled = true;\n return out;\n}\n\n/** 读登记表;缺失/损坏 → 空表(幂等、不抛)。 */\nexport function loadRegistry(dir: string = DATA_DIR, now: number = Date.now()): SessionRegistry {\n let j: any;\n try {\n j = JSON.parse(fs.readFileSync(registryPath(dir), \"utf-8\"));\n } catch {\n return { version: REGISTRY_VERSION, entries: [] };\n }\n const entries: SessionEntry[] = [];\n if (j && typeof j === \"object\" && Array.isArray(j.entries)) {\n for (const e of j.entries) {\n const n = normEntry(e, now);\n if (n) entries.push(n);\n }\n }\n return { version: REGISTRY_VERSION, entries };\n}\n\n/** 原子写登记表。写失败只记日志、**不抛**:登记表是派生元数据,绝不能因为一次磁盘问题拖垮一个回合或一次启动。 */\nexport function saveRegistry(reg: SessionRegistry, dir: string = DATA_DIR): void {\n try {\n writeJsonAtomic(registryPath(dir), reg);\n } catch (e) {\n console.error(\"[wemind/wechat-daemon] session-registry save failed:\", String(e));\n }\n}\n\n// ---- 纯函数(可直接单测) ----\n\n/** 最近使用在前;同刻按创建时间。 */\nexport function sortEntries(list: SessionEntry[]): SessionEntry[] {\n return [...list].sort(\n (a, b) =>\n b.lastUsedAt - a.lastUsedAt ||\n b.createdAt - a.createdAt ||\n a.sessionId.localeCompare(b.sessionId),\n );\n}\n\n/**\n * 按 (agent, userId) 分组做条数裁剪:超出上限时先淘汰「未命名且最旧」的;\n * 仍然超出才动有名字的。`keep`(当前会话)永不淘汰。\n * 非本组的条目原样保留。\n */\nexport function capEntries(\n list: SessionEntry[],\n agent: string,\n userId: string,\n max: number,\n keep: string[] = [],\n): SessionEntry[] {\n const a = agent.trim().toLowerCase();\n const group = list.filter((e) => e.agent === a && e.userId === userId);\n if (group.length <= max) return list;\n const keepSet = new Set(keep);\n const oldestFirst = [...group].sort(\n (x, y) => x.lastUsedAt - y.lastUsedAt || x.createdAt - y.createdAt,\n );\n const dropped = new Set<string>();\n for (const pass of [true, false]) {\n for (const e of oldestFirst) {\n if (group.length - dropped.size <= max) break;\n if (keepSet.has(e.sessionId) || dropped.has(e.sessionId)) continue;\n if (pass && e.name) continue; // 第一轮只淘汰未命名的\n dropped.add(e.sessionId);\n }\n }\n return list.filter((e) => !(e.agent === a && e.userId === userId && dropped.has(e.sessionId)));\n}\n\n/** 插入/更新一条记录(同 agent+userId+sessionId 视为同一条),并做裁剪。 */\nexport function upsertEntry(\n list: SessionEntry[],\n entry: SessionEntry,\n opts: { max?: number; keep?: string[] } = {},\n): SessionEntry[] {\n const next = list.filter(\n (e) =>\n !(e.agent === entry.agent && e.userId === entry.userId && e.sessionId === entry.sessionId),\n );\n next.push(entry);\n return capEntries(next, entry.agent, entry.userId, opts.max ?? MAX_ENTRIES_PER_KEY, [\n entry.sessionId,\n ...(opts.keep ?? []),\n ]);\n}\n\nexport type RefHit =\n | { ok: true; entry: SessionEntry; index: number }\n | { ok: false; error: string };\n\n/**\n * 解析「会话引用」:序号(1 起)> 名字(先精确后忽略大小写)> id 精确 > id 前缀。\n * 歧义不猜——给出前 5 个候选让用户挑。\n */\nexport function resolveRef(ref: string, list: SessionEntry[]): RefHit {\n const r = (ref ?? \"\").trim();\n if (!r) return { ok: false, error: \"需要指定会话(序号 / 名字 / id 前缀)。\" };\n if (!list.length) return { ok: false, error: \"还没有历史会话。\" };\n if (/^\\d+$/.test(r)) {\n const n = Number(r);\n if (n >= 1 && n <= list.length) return { ok: true, entry: list[n - 1], index: n };\n return { ok: false, error: `序号超范围:共 ${list.length} 条。` };\n }\n const brief = (e: SessionEntry): string => e.name ?? e.title ?? e.sessionId.slice(0, 8);\n const byName = list.filter((e) => e.name === r);\n if (byName.length === 1)\n return { ok: true, entry: byName[0], index: list.indexOf(byName[0]) + 1 };\n if (byName.length > 1)\n return {\n ok: false,\n error: `有 ${byName.length} 条会话都叫「${r}」,请用序号:${byName\n .slice(0, 5)\n .map((e) => list.indexOf(e) + 1)\n .join(\"、\")}。`,\n };\n const lower = r.toLowerCase();\n const byNameI = list.filter((e) => e.name?.toLowerCase() === lower);\n if (byNameI.length === 1)\n return { ok: true, entry: byNameI[0], index: list.indexOf(byNameI[0]) + 1 };\n if (byNameI.length > 1)\n return {\n ok: false,\n error: `有 ${byNameI.length} 条会话都叫「${r}」,请用序号:${byNameI\n .slice(0, 5)\n .map((e) => list.indexOf(e) + 1)\n .join(\"、\")}。`,\n };\n const exact = list.find((e) => e.sessionId === r);\n if (exact) return { ok: true, entry: exact, index: list.indexOf(exact) + 1 };\n if (r.length >= 2) {\n const hits = list.filter((e) => e.sessionId.startsWith(r));\n if (hits.length === 1) return { ok: true, entry: hits[0], index: list.indexOf(hits[0]) + 1 };\n if (hits.length > 1)\n return {\n ok: false,\n error: `id 前缀「${r}」匹配到 ${hits.length} 条:${hits.slice(0, 5).map(brief).join(\"、\")}。`,\n };\n }\n return { ok: false, error: `没有找到会话「${r}」(序号 / 名字 / id 前缀)。` };\n}\n\n/** 会话名校验:合法返回 null,否则返回一句面向用户的原因。 */\nexport function validateSessionName(name: string): string | null {\n const n = (name ?? \"\").trim();\n if (!n) return \"名字不能为空。\";\n if (n.length > 20) return \"名字最多 20 个字。\";\n if (/^\\d+$/.test(n)) return \"名字不能是纯数字(会和列表序号混淆)。\";\n if (/[\\r\\n\\t]/.test(n)) return \"名字不能包含换行/制表符。\";\n return null;\n}\n\nfunction pad(n: number): string {\n return n < 10 ? `0${n}` : String(n);\n}\n\n/** 列表里的时间:同年只给月日时分,跨年补年份;没有真实时间(回填)的给「时间未知」。 */\nexport function formatWhen(ts: number, now: number = Date.now()): string {\n if (!ts) return \"时间未知\";\n const d = new Date(ts);\n const hm = `${pad(d.getHours())}:${pad(d.getMinutes())}`;\n const md = `${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n return d.getFullYear() === new Date(now).getFullYear()\n ? `${md} ${hm}`\n : `${d.getFullYear()}-${md}`;\n}\n\n/** 列表里的标签:命名 > 首条消息摘要 > 短 id。 */\nexport function labelOf(e: SessionEntry, max = 24): string {\n const raw = e.name ? `「${e.name}」` : e.title ? e.title : `(未命名 ${e.sessionId.slice(0, 8)})`;\n return raw.length > max ? `${raw.slice(0, max - 1)}…` : raw;\n}\n\n/**\n * 列表里的标题来源:用户消息的头 30 字(换行/多空格折叠成一个空格)。\n * 空文本返回 undefined(纯图片消息没有可当标题的文字)——留给下一次有文字的回合再写。\n */\nexport function titleOf(text: string, max = 30): string | undefined {\n const t = (text ?? \"\").replace(/\\s+/g, \" \").trim();\n if (!t) return undefined;\n return t.length > max ? `${t.slice(0, max)}…` : t;\n}\n\n/**\n * 渲染会话列表(微信侧)。`verbose` 追加完整 sessionId——CLI 侧有地方,微信侧靠序号/名字即可。\n */\nexport function renderSessionList(\n entries: SessionEntry[],\n opts: {\n agent: string;\n page?: number;\n pageSize?: number;\n currentSessionId?: string;\n others?: number;\n now?: number;\n verbose?: boolean;\n },\n): string {\n const pageSize = opts.pageSize ?? PAGE_SIZE;\n const total = entries.length;\n if (!total) {\n return `📋 当前大脑(${opts.agent})还没有历史会话。\\n发一条消息就会建第一条;「新会话 名字」可新建并命名。`;\n }\n const pages = Math.max(1, Math.ceil(total / pageSize));\n const page = Math.min(Math.max(1, Math.trunc(opts.page ?? 1)), pages);\n const slice = entries.slice((page - 1) * pageSize, page * pageSize);\n const lines = [`📋 会话列表(${opts.agent},共 ${total} 条,第 ${page}/${pages} 页)`];\n slice.forEach((e, i) => {\n const idx = (page - 1) * pageSize + i + 1;\n const cur = e.sessionId === opts.currentSessionId ? \" ← 当前\" : \"\";\n const id = opts.verbose ? ` · id=${e.sessionId}` : \"\";\n lines.push(\n ` ${idx}. ${labelOf(e)} · ${formatWhen(e.lastUsedAt, opts.now)} · ${e.turns} 轮${cur}${id}`,\n );\n });\n if (pages > 1) lines.push(`(下一页:「会话列表 ${page + 1}」)`);\n lines.push(`回复「恢复会话 <序号|名字>」切过去;「重命名会话 3 新名字」「删除会话 3」亦可。`);\n if (opts.others) lines.push(`另有 ${opts.others} 条属于其它大脑的会话(切回该大脑后可恢复)。`);\n return lines.join(\"\\n\");\n}\n\n// ---- 读写入口(daemon / CLI 经桥调用) ----\n\n/** 当前大脑下、本人的会话 + 其它大脑的条数 + 当前会话记录。 */\nexport function listSessions(\n agent: string,\n userId: string,\n dir: string = DATA_DIR,\n): { entries: SessionEntry[]; others: number; currentSessionId?: string } {\n const a = agent.trim().toLowerCase();\n const all = loadRegistry(dir).entries;\n const entries = sortEntries(all.filter((e) => e.agent === a && e.userId === userId));\n const others = all.filter((e) => e.agent !== a && e.userId === userId).length;\n return { entries, others, currentSessionId: currentSessionIdOf(a, userId, dir) };\n}\n\n/** 当前会话表里该人当前的会话 id(登记表不存 current,渲染时才比对)。 */\nexport function currentSessionIdOf(\n agent: string,\n userId: string,\n dir: string = DATA_DIR,\n): string | undefined {\n return readSessionMap(brainSessionMapPath(dir))[sessionKey(agent.trim().toLowerCase(), userId)];\n}\n\n/** 新建会话时登记(`turns` 从 0 起);已存在则只补 name/keep,不动时间与轮次。 */\nexport function ensureEntry(\n agent: string,\n userId: string,\n sessionId: string,\n opts: { name?: string; keep?: string[]; dir?: string; now?: number } = {},\n): void {\n const a = agent.trim().toLowerCase();\n const now = opts.now ?? Date.now();\n const reg = loadRegistry(opts.dir, now);\n const hit = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === sessionId,\n );\n if (hit) {\n let changed = false;\n if (opts.name && hit.name !== opts.name) {\n hit.name = opts.name;\n changed = true;\n }\n if (changed) saveRegistry(reg, opts.dir);\n return;\n }\n const entry: SessionEntry = {\n agent: a,\n userId,\n sessionId,\n turns: 0,\n createdAt: now,\n lastUsedAt: now,\n };\n if (opts.name) entry.name = opts.name;\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: upsertEntry(reg.entries, entry, { keep: opts.keep }),\n };\n saveRegistry(next, opts.dir);\n}\n\n/**\n * 每完成一个成功回合调用:`lastUsedAt` 刷新、轮次 +1、标题(首条消息摘要)仅首次写入。\n * `turns` 传绝对值时直接覆盖(恢复旧会话后的首次校准用)。\n */\nexport function touchEntry(\n agent: string,\n userId: string,\n sessionId: string,\n opts: { title?: string; turns?: number; keep?: string[]; dir?: string; now?: number } = {},\n): void {\n const a = agent.trim().toLowerCase();\n const now = opts.now ?? Date.now();\n const reg = loadRegistry(opts.dir, now);\n const hit = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === sessionId,\n );\n if (!hit) {\n const entry: SessionEntry = {\n agent: a,\n userId,\n sessionId,\n turns: opts.turns ?? 1,\n createdAt: now,\n lastUsedAt: now,\n };\n if (opts.title) entry.title = opts.title;\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: upsertEntry(reg.entries, entry, { keep: opts.keep }),\n };\n saveRegistry(next, opts.dir);\n return;\n }\n hit.lastUsedAt = now;\n hit.turns = opts.turns ?? hit.turns + 1;\n if (opts.title && !hit.title) hit.title = opts.title;\n hit.backfilled = undefined;\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: upsertEntry(reg.entries, hit, { keep: opts.keep }),\n };\n saveRegistry(next, opts.dir);\n}\n\nexport type SessionOpResult =\n | { ok: true; entry: SessionEntry; clearedPointer: boolean }\n | { ok: false; error: string };\n\n/**\n * 只刷新「最近使用」:把一条旧会话切回当前(`恢复会话`、切大脑后 load 回来的那条)时用。\n * 不动轮次、不写标题——它不是「又聊了一轮」。未登记时补一条(turns 0),\n * 否则「直接 load 一个登记表里没有的 id」会变成列表里看不见的会话。\n */\nexport function markUsed(\n agent: string,\n userId: string,\n sessionId: string,\n opts: { dir?: string; now?: number } = {},\n): void {\n const a = agent.trim().toLowerCase();\n const now = opts.now ?? Date.now();\n const reg = loadRegistry(opts.dir, now);\n const hit = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === sessionId,\n );\n const entry: SessionEntry = hit ?? {\n agent: a,\n userId,\n sessionId,\n turns: 0,\n createdAt: now,\n lastUsedAt: now,\n };\n entry.lastUsedAt = now;\n entry.backfilled = undefined; // 现在有真实的使用时间了\n saveRegistry({ version: REGISTRY_VERSION, entries: upsertEntry(reg.entries, entry) }, opts.dir);\n}\n\n/** 改名(按引用解析);命名后不会被条数裁剪淘汰。 */\nexport function renameSession(\n agent: string,\n userId: string,\n ref: string,\n name: string,\n dir: string = DATA_DIR,\n): SessionOpResult {\n const bad = validateSessionName(name);\n if (bad) return { ok: false, error: bad };\n const a = agent.trim().toLowerCase();\n const { entries } = listSessions(a, userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return { ok: false, error: hit.error };\n const reg = loadRegistry(dir);\n const target = reg.entries.find(\n (e) => e.agent === a && e.userId === userId && e.sessionId === hit.entry.sessionId,\n );\n const entry = target ?? hit.entry;\n entry.name = name.trim();\n saveRegistry({ version: REGISTRY_VERSION, entries: upsertEntry(reg.entries, entry) }, dir);\n return { ok: true, entry, clearedPointer: false };\n}\n\n/**\n * 删除登记项。删的若是当前会话,**同时清当前会话记录**(下一条消息自然开新的)——当前会话记录不清会让「删掉的会话」继续被使用。\n * agent 侧的历史归档不动(ACP 无可验证的 session/delete,如实告知用户)。\n */\nexport function deleteSession(\n agent: string,\n userId: string,\n ref: string,\n opts: { dir?: string } = {},\n): SessionOpResult {\n const a = agent.trim().toLowerCase();\n const dir = opts.dir;\n const { entries, currentSessionId } = listSessions(a, userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return { ok: false, error: hit.error };\n const reg = loadRegistry(dir);\n const next: SessionRegistry = {\n version: REGISTRY_VERSION,\n entries: reg.entries.filter(\n (e) => !(e.agent === a && e.userId === userId && e.sessionId === hit.entry.sessionId),\n ),\n };\n saveRegistry(next, dir);\n const clearedPointer =\n currentSessionId === hit.entry.sessionId\n ? clearSessionPointer(brainSessionMapPath(dir ?? DATA_DIR), sessionKey(a, userId))\n : false;\n return { ok: true, entry: hit.entry, clearedPointer };\n}\n\n/**\n * 一次性回填:把当前会话表里存在、登记表里还没有的会话补进去(功能上线前的老会话)。\n * 只由 daemon 启动时调用(幂等);之后会话的登记走 ensureEntry/touchEntry。\n */\nexport function syncFromPointerMap(dir: string = DATA_DIR, now: number = Date.now()): number {\n const map = readSessionMap(brainSessionMapPath(dir));\n const reg = loadRegistry(dir, now);\n let added = 0;\n for (const [k, sid] of Object.entries(map)) {\n const i = k.indexOf(\":\");\n if (i <= 0 || !sid) continue;\n const agent = k.slice(0, i).trim().toLowerCase();\n const userId = k.slice(i + 1).trim();\n if (!agent || !userId) continue;\n if (reg.entries.some((e) => e.agent === agent && e.userId === userId && e.sessionId === sid))\n continue;\n reg.entries.push({\n agent,\n userId,\n sessionId: sid,\n turns: 0,\n createdAt: now,\n lastUsedAt: now,\n backfilled: true,\n });\n added++;\n }\n if (added) saveRegistry(reg, dir);\n return added;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { writeJsonAtomic } from \"./atomic-json.js\";\nimport { formatWhen } from \"./session-registry.js\";\n\n/**\n * 会话交接(`交接` / `wemind brain handoff`):换大脑**不自动迁移上下文**(各 agent 的会话互不相通),\n * 要接着聊就显式交接一次——让旧大脑把上下文写成一段摘要,存盘,然后**作为下一条用户消息的前缀**带进新大脑。\n *\n * 为什么是「前缀」而不是「单独占一个回合」:单独发摘要等于让新大脑先空转一轮(多一次模型调用、多占微信额度),\n * 而且那轮它的回复用户并不想看。作为前缀带上是零额外成本的:用户本来就要说下一句话,摘要只是挂在前面。\n *\n * 三条克制规则(都由 `takeHandoffFor` 钉死):\n * - **只给本人用**:给 A 生成的摘要绝不带上 B 的消息;\n * - **切回来源大脑不带上**:那是它自己的上下文,塞回去等于对着本人复述;\n * - **有 TTL**(默认 24h):过期摘要不带上——几天前的上下文塞给新大脑只会污染判断。\n *\n * 消费语义:注入的那一回合**成功**之后才删(见 index.ts)——失败就留着,下次还能用。\n * 文本处理全是纯函数(`handoffPrefix`/`stripHandoffPrefix`),可单测;文件读写只有 save/load/clear。\n */\n\nexport const HANDOFF_VERSION = 1;\n\n/** 摘要有效期:超过就不再带上(可通过 `WEMIND_HANDOFF_TTL_MS` 调整)。 */\nexport const HANDOFF_TTL_MS = Number(process.env.WEMIND_HANDOFF_TTL_MS) || 24 * 3600_000;\n\n/** 前缀与用户新消息之间的分隔行;带上与剥离靠同一常量,两边不会写岔。 */\nexport const HANDOFF_SEP = \"——以下才是用户的新消息——\";\n\n/** 前缀开头,`stripHandoffPrefix` 据此确认「这确实是我们的前缀」而不是用户自己发的正文。 */\nexport const HANDOFF_HEAD = \"【交接摘要 · 来自 \";\n\nexport type Handoff = {\n version: number;\n /** 由哪个大脑写的(agent 名,小写)。 */\n fromAgent: string;\n /** 给谁写的(微信 userId)。 */\n userId: string;\n /** 摘要正文(Markdown)。 */\n text: string;\n createdAt: number;\n expiresAt: number;\n};\n\nexport function handoffPath(dir: string = DATA_DIR): string {\n return path.join(dir, \"handoff.json\");\n}\n\n/** 读摘要:缺失/损坏/字段不全/已过期 → null(带不带只该由「有没有可用的」决定,不该抛)。 */\nexport function loadHandoff(dir: string = DATA_DIR, now: number = Date.now()): Handoff | null {\n let j: any;\n try {\n j = JSON.parse(fs.readFileSync(handoffPath(dir), \"utf-8\"));\n } catch {\n return null;\n }\n if (!j || typeof j !== \"object\" || Array.isArray(j)) return null;\n const fromAgent = typeof j.fromAgent === \"string\" ? j.fromAgent.trim().toLowerCase() : \"\";\n const userId = typeof j.userId === \"string\" ? j.userId.trim() : \"\";\n const text = typeof j.text === \"string\" ? j.text.trim() : \"\";\n if (!fromAgent || !userId || !text) return null;\n const num = (v: unknown, d: number): number =>\n typeof v === \"number\" && Number.isFinite(v) && v > 0 ? v : d;\n const h: Handoff = {\n version: HANDOFF_VERSION,\n fromAgent,\n userId,\n text,\n createdAt: num(j.createdAt, now),\n expiresAt: num(j.expiresAt, now + HANDOFF_TTL_MS),\n };\n return h.expiresAt > now ? h : null;\n}\n\n/** 原子写摘要(唯一临时文件 + rename)。 */\nexport function saveHandoff(h: Handoff, dir: string = DATA_DIR): void {\n writeJsonAtomic(handoffPath(dir), h);\n}\n\n/** 删摘要(注入的那回合成功后调用);返回是否确有文件。 */\nexport function clearHandoff(dir: string = DATA_DIR): boolean {\n try {\n fs.unlinkSync(handoffPath(dir));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 判断这份摘要要不要带给 `agent` 的这次对话(纯函数,调用方负责读盘与消费)。\n * 返回正文表示「要带上」,null 表示「不带上」——四种不带上的情形各是一句能对用户解释的话。\n */\nexport function takeHandoffFor(\n h: Handoff | null,\n agent: string,\n userId: string,\n now: number = Date.now(),\n): string | null {\n if (!h) return null;\n const text = h.text?.trim();\n if (!text) return null;\n if (h.userId !== userId) return null;\n if (h.fromAgent === agent.trim().toLowerCase()) return null; // 切回来源大脑:那是它自己的上下文\n if (h.expiresAt <= now) return null;\n return text;\n}\n\n/** 交接摘要的生成指令(固定模板:四节、无前后缀,便于新大脑直接当上下文读)。 */\nexport const HANDOFF_PROMPT = [\n \"请把**现在这段对话**整理成一份交接摘要,写给另一个 AI 助手看(它看不到我们的聊天记录,也不会问你)。\",\n \"只要下面四节,每节几条要点;不要客套、不要复述我的原话、不要写「以下是」这类前后缀:\",\n \"1. 目标:我们在做什么、做到哪一步;\",\n \"2. 已确认的结论与决定(含我明确表达过的偏好与约束);\",\n \"3. 未决问题:还没定的事、等我回答的问题;\",\n \"4. 关键事实:文件路径、命令、报错信息、外部资源等硬信息(有多少写多少,别概括掉)。\",\n \"直接输出 Markdown 摘要正文。\",\n].join(\"\\n\");\n\n/** 带上摘要用的前缀(以分隔行结尾,调用方把用户新消息接在后面)。 */\nexport function handoffPrefix(fromAgent: string, text: string, now: number = Date.now()): string {\n return `${HANDOFF_HEAD}${fromAgent},${formatWhen(now, now)}】\\n${text}\\n${HANDOFF_SEP}\\n`;\n}\n\n/**\n * 从作为前缀带上后的消息里取回用户真正说的那句(登记表的标题用)。\n * 只在确实是我们的前缀时才剥(开头对得上 `HANDOFF_HEAD`),用户自己打那行字不会被误伤。\n */\nexport function stripHandoffPrefix(message: string): string {\n const m = message ?? \"\";\n if (!m.startsWith(HANDOFF_HEAD)) return m;\n const i = m.indexOf(HANDOFF_SEP);\n if (i < 0) return m;\n return m.slice(i + HANDOFF_SEP.length).trim();\n}\n","/**\n * ACP 反向请求的统一应答器。\n *\n * agent 会在回合中发回 `session/request_permission`(工具审批)、`fs/*`、`terminal/*` 等请求;\n * 不回应它们,agent 会一直等到自己的超时,表现为「这条消息卡住」。brain、worker、`brain check`\n * 三处都必须用同一套语义,否则会出现「微信能跑、自检超时」或「worker 与 brain 行为不同」的漂移。\n *\n * 安全默认:权限请求一律拒绝;`WEMIND_ACP_AUTO_ALLOW=1` 时只放行选项里明确带\n * `allow_once` / `allow_always` 的那一个(「每次自动点允许」,不放宽沙箱)。未知 method 回 `-32601`。\n */\nexport type AcpWriter = (payload: Record<string, unknown>) => void;\n\nexport function handleReverseRequest(\n frame: Record<string, any>,\n write: AcpWriter,\n log?: (message: string) => void,\n): void {\n const method = String(frame.method);\n log?.(`← reverse request ${method}#${frame.id}`);\n if (method === \"session/request_permission\") {\n const options = Array.isArray(frame.params?.options) ? frame.params.options : [];\n const allow =\n process.env.WEMIND_ACP_AUTO_ALLOW === \"1\"\n ? options.find((o: any) => o?.kind === \"allow_once\" || o?.kind === \"allow_always\")\n : undefined;\n const result = allow\n ? { outcome: { outcome: \"selected\", optionId: allow.optionId } }\n : { outcome: { outcome: \"cancelled\" } };\n write({ jsonrpc: \"2.0\", id: frame.id, result });\n log?.(`permission ${allow ? \"allowed\" : \"rejected\"}`);\n return;\n }\n // 未知反向请求也要回错误,否则对端会挂起(哪怕它并不认识这个错误码)。\n write({\n jsonrpc: \"2.0\",\n id: frame.id,\n error: { code: -32601, message: \"method not supported\" },\n });\n}\n","/**\n * 等 ACP agent 的异步帧流安静下来(`session/load` 之后的历史回放用)。\n *\n * gemini 的 `loadSession` 先返回、再异步 `streamHistory`(源码里不 await):回放帧会在下一轮 `prompt` 前到达,\n * 混进本轮答复(实测 load 返回约 11ms 后开始回放)。恢复会话后先等「连续 quietMs 没有新帧」再提问;\n * `maxMs` 封顶,避免 agent 持续发帧(心跳/usage)时把回合卡死。\n */\nexport async function waitForFrameQuiet(opts: {\n seq: () => number;\n quietMs?: number;\n maxMs?: number;\n sleep?: (ms: number) => Promise<void>;\n now?: () => number;\n}): Promise<{ frames: number; timedOut: boolean }> {\n const quietMs = opts.quietMs ?? 200;\n const maxMs = opts.maxMs ?? 3000;\n const sleep = opts.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n const now = opts.now ?? Date.now;\n const start = now();\n const first = opts.seq();\n let seen = first;\n while (now() - start < maxMs) {\n await sleep(quietMs);\n const cur = opts.seq();\n if (cur === seen) return { frames: cur - first, timedOut: false };\n seen = cur;\n }\n return { frames: seen - first, timedOut: true };\n}\n","import { handleReverseRequest } from \"./acp-reverse.js\";\nimport { waitForFrameQuiet } from \"./acp-quiet.js\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\nimport { LineDecoder } from \"./line-decoder.js\";\n\n/**\n * ACP 连接的公共层(brain / worker 共用):JSON-RPC 帧处理、请求超时、回合超时、模型与模式设置、回放静默。\n *\n * 进程生命周期与会话持久化**不在这里**:brain 是常驻单例 + 按用户 `brain-sessions.json`;worker 是每任务一条连接 + `worker-sessions.json`。\n * 两边真正会「各写一份然后漂移」的是协议细节(帧分类、空闲/首帧/硬上限、`session/set_config_mode` 等),所以只抽这一层。\n * 进程退出/重建时由调用方清自己的状态;这里只清 transport 与当前回合的拼接缓冲(`clearAcpTurn`)。\n */\n\nexport type AcpProgressEvent =\n | { type: \"tool\"; kind?: string; purpose?: string; title?: string; detail?: string }\n | { type: \"answer\" }\n | { type: \"thinking\" }\n | { type: \"activity\" };\n\n/** 工具调用里能直接读懂的短参数:不同 agent 的 rawInput 形状不统一,按常见键取第一个。 */\nconst TOOL_DETAIL_KEYS = [\n \"command\",\n \"cmd\",\n \"path\",\n \"file_path\",\n \"filePath\",\n \"query\",\n \"pattern\",\n \"url\",\n \"description\",\n] as const;\n\n/** 进度里的工具描述要短:折行压成空格,超过 60 字截断,避免把整段命令/JSON 塞进微信。 */\nfunction clipDetail(s: string): string | undefined {\n const text = s.replace(/\\s+/g, \" \").trim();\n if (!text) return undefined;\n return text.length > 60 ? `${text.slice(0, 59)}…` : text;\n}\n\nfunction toolDetail(rawInput: unknown): string | undefined {\n if (!rawInput || typeof rawInput !== \"object\") return undefined;\n const rec = rawInput as Record<string, unknown>;\n for (const key of TOOL_DETAIL_KEYS) {\n const v = rec[key];\n if (typeof v === \"string\") {\n const detail = clipDetail(v);\n if (detail) return detail;\n }\n }\n return undefined;\n}\n\nexport type AcpPending = {\n resolve: (v: unknown) => void;\n reject: (e: Error) => void;\n clear: () => void;\n method: string;\n};\n\n/** 一条连接的帧流状态:最近收帧时间 + 累计收帧序号(首帧/回放判定用)。 */\nexport type AcpTransportState = { lastFrameAt: number; frameSeq: number };\n\n/** 当前回合的拼接缓冲与进度状态。 */\nexport type AcpTurnState = {\n assembling: Map<string, string[]>;\n currentOnProgress: ((ev: AcpProgressEvent) => void) | null;\n answerStarted: boolean;\n thoughtStarted: boolean;\n seenUpdateTypes: Set<string>;\n seenTools: Set<string>;\n};\n\nexport type AcpHost = {\n pending: Map<number, AcpPending>;\n turn: AcpTurnState;\n state: AcpTransportState;\n hasProcess(): boolean;\n /** 没有进程时 request/prompt 的拒绝文案(brain: no acp process;worker: no worker process)。 */\n noProcessMessage: string;\n nextId(): number;\n write(payload: Record<string, unknown>): void;\n log(message: string): void;\n};\n\nexport function newTurnState(): AcpTurnState {\n return {\n assembling: new Map(),\n currentOnProgress: null,\n answerStarted: false,\n thoughtStarted: false,\n seenUpdateTypes: new Set(),\n seenTools: new Set(),\n };\n}\n\n/** 进程重建:丢掉当前回合的拼接缓冲与进度回调(不碰磁盘 session 映射)。 */\nexport function clearAcpTurn(turn: AcpTurnState): void {\n turn.assembling.clear();\n turn.currentOnProgress = null;\n}\n\nexport function beginAcpTurn(\n turn: AcpTurnState,\n sessionId: string,\n onProgress?: (ev: AcpProgressEvent) => void,\n): void {\n turn.assembling.set(sessionId, []);\n turn.currentOnProgress = onProgress ?? null;\n turn.answerStarted = false;\n turn.thoughtStarted = false;\n turn.seenUpdateTypes.clear();\n turn.seenTools.clear();\n}\n\nexport function acpTurnText(turn: AcpTurnState, sessionId: string): string {\n return (turn.assembling.get(sessionId) ?? []).join(\"\").trim();\n}\n\nexport function acpTurnChunks(turn: AcpTurnState, sessionId: string): number {\n return (turn.assembling.get(sessionId) ?? []).length;\n}\n\nexport function endAcpTurn(turn: AcpTurnState, sessionId: string): void {\n turn.currentOnProgress = null;\n turn.assembling.delete(sessionId);\n}\n\n/** 喂一段 stdout:按行解析 JSON,非 JSON 日志行跳过;解析出的帧交给 `onFrame`。 */\nexport function feedAcpStdout(\n decoder: LineDecoder,\n chunk: Buffer,\n onFrame: (f: Record<string, any>) => void,\n): void {\n for (const line of decoder.push(chunk)) {\n let f: Record<string, any>;\n try {\n f = JSON.parse(line);\n } catch {\n continue; // 有的 agent 会往 stdout 混日志行(DSH 实测),非 JSON 直接跳过\n }\n onFrame(f);\n }\n}\n\n/**\n * 处理一帧:响应 → 兑现 pending;反向请求 → 走共用应答器;`session/update` → 进度与文本拼接。\n * 调用方负责把本函数挂到 stdout 解析后的每一帧上。\n */\nexport function handleAcpFrame(f: Record<string, any>, host: AcpHost): void {\n host.state.lastFrameAt = Date.now();\n host.state.frameSeq += 1;\n if (typeof f.id === \"number\" && (f.result !== undefined || f.error !== undefined)) {\n const p = host.pending.get(f.id);\n if (!p) return;\n p.clear();\n host.pending.delete(f.id);\n if (f.error) {\n host.log(`← ${p.method}#${f.id} ERROR ${JSON.stringify(f.error).slice(0, 200)}`);\n p.reject(new Error(JSON.stringify(f.error)));\n } else p.resolve(f.result);\n return;\n }\n // agent -> client 的反向请求(带 method + id);必须应答,否则会挂到超时\n if (f.method && f.id !== undefined) {\n handleReverseRequest(\n f,\n (payload) => host.write(payload as Record<string, unknown>),\n (message) => host.log(message),\n );\n return;\n }\n const turn = host.turn;\n if (f.method === \"session/update\") {\n const u = f.params?.update;\n if (u?.sessionUpdate) turn.seenUpdateTypes.add(String(u.sessionUpdate)); // 诊断\n // 任意 session/update 帧都视为“活动” → 让 daemon 静默刷新心跳基线(流式期间不误报“仍在处理”)\n if (turn.currentOnProgress) {\n try {\n turn.currentOnProgress({ type: \"activity\" });\n } catch {\n /* ignore */\n }\n }\n if (u?.sessionUpdate === \"agent_message_chunk\" && u.content?.type === \"text\") {\n const arr = turn.assembling.get(f.params.sessionId);\n if (arr) arr.push(String(u.content.text ?? \"\"));\n if (!turn.answerStarted && turn.currentOnProgress) {\n turn.answerStarted = true;\n try {\n turn.currentOnProgress({ type: \"answer\" });\n } catch {\n /* ignore */\n }\n }\n } else if (u?.sessionUpdate === \"agent_thought_chunk\") {\n if (!turn.thoughtStarted && turn.currentOnProgress) {\n turn.thoughtStarted = true;\n try {\n turn.currentOnProgress({ type: \"thinking\" });\n } catch {\n /* ignore */\n }\n }\n } else if (\n (u?.sessionUpdate === \"tool_call\" || u?.sessionUpdate === \"tool_call_update\") &&\n turn.currentOnProgress\n ) {\n const tid = String(u.toolCallId ?? u.title ?? \"\"); // 每个工具调用只上报一次\n if (tid && !turn.seenTools.has(tid)) {\n turn.seenTools.add(tid);\n const purpose =\n typeof u.rawInput?.__tool_use_purpose === \"string\"\n ? clipDetail(u.rawInput.__tool_use_purpose)\n : undefined;\n try {\n turn.currentOnProgress({\n type: \"tool\",\n kind: u.kind,\n purpose,\n title: u.title,\n detail: toolDetail(u.rawInput),\n });\n } catch {\n /* ignore */\n }\n }\n }\n }\n}\n\n/** 普通请求(initialize / session/new / load / resume / set_*)。 */\nexport function requestAcp(\n host: AcpHost,\n method: string,\n params: unknown,\n timeoutMs: number,\n): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!host.hasProcess()) {\n reject(new Error(host.noProcessMessage));\n return;\n }\n const id = host.nextId();\n host.log(`→ ${method}#${id}`);\n const t = setTimeout(() => {\n host.pending.delete(id);\n reject(new Error(`${method} timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n host.pending.set(id, { resolve, reject, clear: () => clearTimeout(t), method });\n host.write({ jsonrpc: \"2.0\", id, method, params });\n });\n}\n\nexport type PromptTimeouts = { idleMs: number; capMs: number; firstFrameMs: number };\n\n/**\n * `session/prompt` 专用:空闲超时(`idleMs` 内无任何帧才判卡死)+ 首帧超时 + 硬上限(`capMs`)。\n * 只要 agent 还在出帧(工具进度/正文),就不打断——避免误杀“慢但在干活”的长回合。\n */\nexport function promptAcp(host: AcpHost, params: unknown, timeouts: PromptTimeouts): Promise<any> {\n return new Promise((resolve, reject) => {\n if (!host.hasProcess()) {\n reject(new Error(host.noProcessMessage));\n return;\n }\n const id = host.nextId();\n const startedAt = Date.now();\n host.state.lastFrameAt = Date.now(); // 重置空闲基线:从本次 prompt 发出算起,避免用上一回合的旧时间戳误判\n const startSeq = host.state.frameSeq; // 记录起点,用于判定本回合是否出过帧\n host.log(\n `→ session/prompt#${id} (idle=${timeouts.idleMs}ms cap=${timeouts.capMs}ms firstFrame=${timeouts.firstFrameMs}ms)`,\n );\n const iv = setInterval(() => {\n const idle = Date.now() - host.state.lastFrameAt;\n const total = Date.now() - startedAt;\n const noFrameYet = host.state.frameSeq === startSeq; // 本回合从未出过帧\n const firstFrameTimeout = noFrameYet && total > timeouts.firstFrameMs;\n if (firstFrameTimeout || idle > timeouts.idleMs || total > timeouts.capMs) {\n if (host.pending.delete(id)) {\n clearInterval(iv);\n const why = firstFrameTimeout\n ? \"no-first-frame\"\n : idle > timeouts.idleMs\n ? \"idle\"\n : \"cap\";\n reject(new Error(`session/prompt ${why} timeout (idle=${idle}ms total=${total}ms)`));\n }\n }\n }, 5000);\n host.pending.set(id, {\n resolve,\n reject,\n clear: () => clearInterval(iv),\n method: \"session/prompt\",\n });\n host.write({ jsonrpc: \"2.0\", id, method: \"session/prompt\", params });\n });\n}\n\n/** 按配置选模型路由(如 DSH 的 deepseek-account);没有 model 或模型已随启动参数传入的 agent 跳过。 */\nexport async function applyAcpModel(\n host: AcpHost,\n ag: ResolvedAgent,\n sessionId: string,\n timeoutMs: number,\n): Promise<void> {\n if (!ag.model || ag.modelArg) return;\n try {\n await requestAcp(\n host,\n \"session/set_config_option\",\n { sessionId, configId: \"model\", value: ag.model },\n timeoutMs,\n );\n host.log(`model set ${ag.model}`);\n } catch (e) {\n host.log(`model set failed (${String(e)})`);\n throw e;\n }\n}\n\n/** 从 `session/new|load|resume` 的回包里取可用模式 id;agent 不报 `modes` 时返回 null(=不校验,照样尝试)。 */\nexport function availableAcpModes(res: any): string[] | null {\n const list = res?.modes?.availableModes;\n if (!Array.isArray(list)) return null;\n const ids = list.map((m: any) => String(m?.id ?? m)).filter(Boolean);\n return ids.length ? ids : null;\n}\n\n/** 会话建立/恢复后切到预设声明的模式(免审批:claude-code 的 bypassPermissions、codex 的 agent-full-access)。\n * **失败只记日志、不抛**:模式不可用(如 claude-code 以 root 跑却没有 IS_SANDBOX)时进程仍可用,\n * 审批请求继续由应答器处理(默认拒绝,WEMIND_ACP_AUTO_ALLOW=1 放行);抛错反而会把整轮打成失败。 */\nexport async function applyAcpSessionMode(\n host: AcpHost,\n ag: ResolvedAgent,\n sessionId: string,\n modes: string[] | null = null,\n timeoutMs: number,\n): Promise<void> {\n if (!ag.sessionMode) return;\n if (modes && !modes.includes(ag.sessionMode)) {\n host.log(\n `session mode ${ag.sessionMode} 不在 agent 报告的可用模式 [${modes.join(\",\")}] 里——跳过;审批请求仍由应答器处理`,\n );\n return;\n }\n try {\n await requestAcp(host, \"session/set_mode\", { sessionId, modeId: ag.sessionMode }, timeoutMs);\n host.log(`session mode set ${ag.sessionMode}`);\n } catch (e) {\n host.log(`session mode set failed (${String(e)})——继续;审批请求仍由应答器处理`);\n }\n}\n\n/** 恢复会话后,agent 可能还在异步回放历史(gemini 的 loadSession 里 streamHistory 不 await):\n * 先等帧流安静下来再开始本轮提问,否则回放的历史会被拼进答复。 */\nexport async function drainAcpReplay(host: AcpHost, ag: ResolvedAgent): Promise<void> {\n if (!ag.replayOnLoad) return;\n const before = host.state.frameSeq;\n const r = await waitForFrameQuiet({ seq: () => host.state.frameSeq });\n const drained = host.state.frameSeq - before;\n if (drained > 0) host.log(`drained ${drained} replay frame(s)${r.timedOut ? \" (cap)\" : \"\"}`);\n}\n","import { StringDecoder } from \"node:string_decoder\";\n\n/**\n * 按行切分 stdout 的增量解码器。\n * `Buffer.toString()` 逐 chunk 解码会在 UTF-8 多字节字符跨 chunk 时插入 U+FFFD——中文回复静默损坏;`StringDecoder` 会把不完整的字节序列留到下一段。\n * ACP 帧是换行分隔的 JSON,这里只负责「凑齐整行」,JSON 解析仍由调用方做(非 JSON 日志行要能跳过)。\n */\nexport class LineDecoder {\n private decoder = new StringDecoder(\"utf8\");\n private buf = \"\";\n\n /** 喂一段 stdout,返回本次能完整切出的行(已 trim,空行丢弃)。 */\n push(chunk: Buffer): string[] {\n this.buf += this.decoder.write(chunk);\n const lines: string[] = [];\n let i: number;\n while ((i = this.buf.indexOf(\"\\n\")) >= 0) {\n const line = this.buf.slice(0, i).trim();\n this.buf = this.buf.slice(i + 1);\n if (line) lines.push(line);\n }\n return lines;\n }\n\n /** 进程重建时丢弃半截行与未完成的多字节序列。 */\n reset(): void {\n this.decoder = new StringDecoder(\"utf8\");\n this.buf = \"\";\n }\n}\n","import { spawnSync, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { BRAIN_HOME, activeMcpServers } from \"./mcp-sandbox.js\";\nimport { getAgent } from \"./agent-slot.js\";\nimport { acpArgsHint, agentEnv, prepareAgent, type ResolvedAgent } from \"./brain-agents.js\";\nimport { pidRecordOf, writePidRecords } from \"./pid-record.js\";\nimport { spawnAgent } from \"./spawn-agent.js\";\nimport {\n backupCorruptSessionMap,\n brainSessionMapPath,\n normalizeSessionMap,\n readSessionMapStrict,\n sessionKey,\n writeSessionMap,\n} from \"./session-map.js\";\nimport { ensureEntry, markUsed, titleOf, touchEntry } from \"./session-registry.js\";\nimport { stripHandoffPrefix } from \"./handoff.js\";\nimport {\n applyAcpModel,\n applyAcpSessionMode,\n availableAcpModes,\n acpTurnChunks,\n acpTurnText,\n beginAcpTurn,\n clearAcpTurn,\n drainAcpReplay,\n endAcpTurn,\n feedAcpStdout,\n handleAcpFrame,\n newTurnState,\n promptAcp,\n requestAcp,\n type AcpHost,\n type AcpPending,\n type AcpProgressEvent,\n} from \"./acp-connection.js\";\nimport { LineDecoder } from \"./line-decoder.js\";\n\n/**\n * ACP 大脑:维护一个常驻的 ACP agent 进程(JSON-RPC over stdio,换行分隔)。\n * 通过标准 ACP 协议驱动,agent 可配置(默认 kiro-cli,见 brain-agents.ts),任意兼容 ACP 的 CLI 均可接入。\n * - 每用户一个 ACP session(原生多轮记忆);session 映射持久化到磁盘。\n * - 自愈:session/prompt 超时/失败 → 杀进程重建;重建后用 session/load 恢复上下文。\n * - 诊断日志([wemind/wechat-daemon/acp] 前缀,进 daemon.log):发出请求、收帧、超时时已收 chunk 数与最后收帧时间差。\n * 跑在 brain 沙箱目录下(默认空 MCP),通过 agent 配置的环境变量把该目录传给 agent。\n * 协议细节(帧处理、请求/回合超时、模型与模式、回放静默)与 worker 共用 `acp-connection.ts`;本模块只保留常驻单例与会话持久化。\n *\n * **agent 显式传参**:本模块不再持有「当前 agent」常量(改造前是 import 时解析的 `AGENT`,\n * 于是热切换只能重启整个 daemon)。入口 `askBrain`/`newSession` 取一次槽位(`getAgent(\"brain\")`),\n * 之后 `ensureProcess(ag)` / `ensureSession(ag,…)` / `applyAcpModel(host, ag, …)` / `promptAcp(host, …)` 一路显式传下去——\n * 过程内不再读槽位,「握手到一半被换掉 agent」在结构上不可能发生(不需要锁)。\n */\n\nfunction dlog(...a: unknown[]): void {\n console.error(\"[wemind/wechat-daemon/acp]\", ...a);\n}\n\nconst REQ_TIMEOUT = 30_000; // initialize / session/new / session/load\nconst PROMPT_IDLE_MS = Number(process.env.WECHAT_BRAIN_IDLE_MS) || 120_000; // 多久没收到任何帧才算\"卡死\"。重任务单次慢抓取可静默较久(直连 kiro-cli 无此限制),放宽到 2min 兜底真死\nconst PROMPT_CAP_MS = Number(process.env.WECHAT_BRAIN_CAP_MS) || 900_000; // 单回合硬上限 15min。实测重任务(联网核实)需 ~6.5min;context_token 无时间 TTL(官方),长回合用本消息 token 仍可送达。卡死由 idle/首帧超时兜底\nconst BRAIN_PID_FILE = path.join(DATA_DIR, \"brain.pid\"); // 身份记录(JSON,见 pid-record.ts);跨入口用同一文件名\n\n/** 首帧超时:env 覆盖 > 配置覆盖(DSH 实测首帧 1–4s,兜底 60s)> 默认 25s。\n * 按 agent 算而不是模块常量——热切换后新 agent 的启动快慢与旧的不同。 */\nfunction firstFrameMs(ag: ResolvedAgent): number {\n return Number(process.env.WECHAT_BRAIN_FIRST_FRAME_MS) || ag.firstFrameMs || 25_000;\n}\n\nlet child: ChildProcessWithoutNullStreams | null = null;\nlet initialized = false;\nlet idCounter = 0;\nconst pending = new Map<number, AcpPending>();\nconst userSessions = new Map<string, string>(); // 本进程已建立的 session\nconst turn = newTurnState(); // 当前回合的拼接缓冲与进度状态\nconst rpcState = { lastFrameAt: 0, frameSeq: 0 }; // 最近收帧时间 + 累计序号(首帧/回放判定)\nconst stdoutLines = new LineDecoder();\nconst hintedSessions = new Set<string>(); // 已带上「可发图」约定提示的 session(每会话首轮带上一次)\ntype ProgressEvent = AcpProgressEvent;\n\nconst host: AcpHost = {\n pending,\n turn,\n state: rpcState,\n hasProcess: () => !!child,\n noProcessMessage: \"no acp process\",\n nextId: () => ++idCounter,\n write: (payload) => {\n child?.stdin.write(JSON.stringify(payload) + \"\\n\");\n },\n log: (message) => dlog(message),\n};\n\n// ---- 并发门(切脑用)----\n// 一回合是一段长异步(可能几分钟):热切换必须先**等在跑的回合结束**,绝不在整回合上持锁。\n// 反过来,切换期间新回合要在门口等(有界),否则会与启动新大脑时**双起进程**(后台守护进程/重试也走这条路)。\nlet turnDepth = 0; // 已入队/正在跑的 ACP 操作数(提问 / 新建会话 / 恢复会话)\nlet opChain: Promise<void> = Promise.resolve(); // ACP 操作串行链:同一时刻只有一个操作在碰进程\nlet switching = false; // 正在切换大脑\nconst SWITCH_WAIT_MS = Number(process.env.WEMIND_SWITCH_TIMEOUT_MS) || 120_000; // 回合等切换的上限,与切换自身的启动新大脑的上限一致\n\nexport function isTurnInFlight(): boolean {\n return turnDepth > 0;\n}\n\n/**\n * 所有会碰 ACP 进程的操作(提问 / 新建 / 恢复)都从这里排队:同一时刻只有一个在跑。\n * 微信回合与控制桥是两条独立入口,没有这道门时 `session/new` 会插进 `session/prompt` 中间,\n * 或在恢复会话时把正在跑的回合的 `userSessions` 改掉。\n * `turnDepth` 在**入队时**就加:切脑的 `drainTurn` 要连排队中的操作一起等,不能只看正在跑的那个。\n */\nfunction withBrainOp<T>(fn: () => Promise<T>): Promise<T> {\n turnDepth += 1;\n const run = opChain.then(fn, fn).finally(() => {\n turnDepth -= 1;\n });\n opChain = run.then(\n () => {},\n () => {},\n );\n return run;\n}\n\n/** 控制桥请求的等待上限:CLI 已经放弃后,排队中的操作不再执行(见 control-bridge.ts 的 expiresAt)。 */\nfunction expired(deadline?: number): boolean {\n return deadline !== undefined && Date.now() > deadline;\n}\nconst EXPIRED_MSG = \"请求已超时(CLI 已不再等待),本次未执行\";\n\n/** 切换流程开始时调用:此后新回合在门口等,直到 `endSwitch()`。 */\nexport function beginSwitch(): void {\n switching = true;\n}\n\nexport function endSwitch(): void {\n switching = false;\n}\n\n/** 切换流程用:等在跑的回合结束(诚实失败——超时说明情况让用户重试,绝不半路砍掉回合)。 */\nexport async function drainTurn(timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (isTurnInFlight()) {\n if (Date.now() > deadline) return false;\n await new Promise((r) => setTimeout(r, 100));\n }\n return true;\n}\n\n/** 回合入口用:切换进行中就先等它完成(有界),再继续——避免与启动新大脑抢同一个 ACP 进程。 */\nasync function waitWhileSwitching(): Promise<void> {\n if (!switching) return;\n dlog(\"turn waits for in-flight brain switch…\");\n await drainSwitch(SWITCH_WAIT_MS);\n}\n\nasync function drainSwitch(timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (switching) {\n if (Date.now() > deadline) return false;\n await new Promise((r) => setTimeout(r, 100));\n }\n return true;\n}\n\nfunction isAlive(): boolean {\n return !!child && child.exitCode === null && !child.killed;\n}\n\n/** 杀进程(按 PID 进程树)并清空内部态;不清磁盘 session 映射(留给重建后 load)。 */\nfunction reset(): void {\n const c = child;\n child = null;\n initialized = false;\n if (c?.pid) {\n try {\n if (process.platform === \"win32\")\n spawnSync(\"taskkill\", [\"/PID\", String(c.pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n else c.kill();\n } catch {\n /* ignore */\n }\n }\n writePidRecords(BRAIN_PID_FILE, []); // 没有进程 = 没有记录\n for (const p of pending.values()) {\n p.clear();\n p.reject(new Error(\"acp process reset\"));\n }\n pending.clear();\n userSessions.clear();\n clearAcpTurn(turn);\n stdoutLines.reset();\n}\n\n// ---- 磁盘 session 映射(`<agent>:<userId>` -> sessionId),跨进程重建保上下文 ----\nconst keyOf = (ag: ResolvedAgent, userId: string): string => sessionKey(ag.name, userId);\n\nfunction loadSessionMap(ag: ResolvedAgent): Record<string, string> {\n const file = brainSessionMapPath(DATA_DIR);\n let raw: Record<string, string>;\n try {\n raw = readSessionMapStrict(file);\n } catch (e) {\n const backup = backupCorruptSessionMap(file);\n dlog(\n `session map unreadable (${String(e)}) → ${backup ? `backed up to ${backup}` : \"backup failed\"};按空表继续`,\n );\n return {};\n }\n const { map, migrated } = normalizeSessionMap(raw, ag.name);\n if (migrated) saveSessionMap(map); // 立即写入磁盘,避免下次换 agent 时旧条目再被“继承”一次\n return map;\n}\nfunction saveSessionMap(m: Record<string, string>): void {\n writeSessionMap(brainSessionMapPath(DATA_DIR), m); // 原子写:半截 JSON 等于所有会话丢失\n}\n\nasync function ensureProcess(ag: ResolvedAgent): Promise<void> {\n if (isAlive() && initialized) return;\n reset();\n const kept: string[] = [];\n const env = agentEnv(ag, BRAIN_HOME, kept); // 父进程 env → 沙箱目录 → 预设 env → 预设 envDefaults(父进程已设的不覆盖)\n if (kept.length) dlog(`env defaults kept from parent: ${kept.join(\",\")}`); // 部署方显式设置的审批类开关\n prepareAgent(ag); // 启动前落盘 agent 需要的派生配置(DSH 的 session-root patch)\n const proc = spawnAgent(ag, { cwd: BRAIN_HOME, env });\n child = proc;\n dlog(`spawn ${ag.name} acp pid=${proc.pid} bin=${ag.bin} args=[${ag.args.join(\" \")}]`);\n // 身份记录(JSON):manage.ts 据此算进程特征串与展示 live 状态,不必猜「配置里的 agent 是不是正在跑的那个」\n writePidRecords(BRAIN_PID_FILE, [pidRecordOf(proc.pid!, ag)]);\n proc.stdout.on(\"data\", (d) => feedAcpStdout(stdoutLines, d, (f) => handleAcpFrame(f, host)));\n proc.stderr.on(\"data\", () => {\n /* ignore */\n });\n // 只在\"退出的就是当前 child\"时才重置,避免旧进程迟到的 exit 误杀新进程\n proc.on(\"exit\", (code) => {\n // A:握手前就退出时,把「args 可能漏了 ACP 模式参数」直接写进日志(比只报一行 exited 好查)\n const hint = child === proc && !initialized ? acpArgsHint(ag) : \"\";\n dlog(`acp exited code=${code}${hint}`);\n if (child === proc) reset();\n });\n proc.on(\"error\", (e) => {\n dlog(`acp error ${String(e)}`);\n if (child === proc) reset();\n });\n try {\n await requestAcp(\n host,\n \"initialize\",\n { protocolVersion: 1, clientCapabilities: {} },\n REQ_TIMEOUT,\n );\n } catch (e) {\n // A:initialize 超时/失败最常见的原因是 args 覆盖漏了 ACP 子命令——把诊断提示附在同一行日志里\n dlog(`initialize failed: ${String(e)}${acpArgsHint(ag)}`);\n throw e;\n }\n initialized = true;\n dlog(\"initialized\");\n}\n\nasync function ensureSession(ag: ResolvedAgent, userId: string): Promise<string> {\n const existing = userSessions.get(userId);\n if (existing) return existing;\n // 配了 mcpViaAcp 的 agent(DSH):激活集随 session 下发;其余 agent 传空(走各自沙箱配置文件)\n const mcpServers = ag.mcpViaAcp ? activeMcpServers(ag) : [];\n const map = loadSessionMap(ag);\n const saved = map[keyOf(ag, userId)];\n if (saved) {\n // agent 指定 resume(DSH);其余仍先 load,失败再试 resume,最后 new\n const methods: Array<\"load\" | \"resume\"> =\n ag.restore === \"resume\" ? [\"resume\"] : [\"load\", \"resume\"];\n for (const method of methods) {\n let restored: any;\n try {\n restored = await requestAcp(\n host,\n `session/${method}`,\n { sessionId: saved, cwd: BRAIN_HOME, mcpServers },\n REQ_TIMEOUT,\n );\n await drainAcpReplay(host, ag);\n } catch (e) {\n dlog(`session/${method} failed (${String(e)})`);\n continue;\n }\n // 恢复已经成功:模型设置失败只记日志,不能退回 session/new——那会丢掉刚恢复的上下文。\n // (applyModel 自己会记 `model set failed`,这里只负责不让它把恢复判成失败。)\n try {\n await applyAcpModel(host, ag, saved, REQ_TIMEOUT);\n } catch {\n /* 已在 applyModel 内记日志 */\n }\n await applyAcpSessionMode(host, ag, saved, availableAcpModes(restored), REQ_TIMEOUT);\n userSessions.set(userId, saved);\n markUsed(ag.name, userId, saved); // 会话登记表:把它顶到「最近使用」(列表按此排序)\n dlog(`session/${method} ok ${saved} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`);\n return saved;\n }\n }\n const res = (await requestAcp(\n host,\n \"session/new\",\n { cwd: BRAIN_HOME, mcpServers },\n REQ_TIMEOUT,\n )) as {\n sessionId?: string;\n };\n if (!res?.sessionId) throw new Error(\"session/new returned no sessionId\");\n await applyAcpModel(host, ag, res.sessionId, REQ_TIMEOUT);\n await applyAcpSessionMode(host, ag, res.sessionId, availableAcpModes(res), REQ_TIMEOUT);\n userSessions.set(userId, res.sessionId);\n map[keyOf(ag, userId)] = res.sessionId;\n saveSessionMap(map);\n ensureEntry(ag.name, userId, res.sessionId); // 会话登记表:新会话进列表(turns 从 0 起)\n dlog(`session/new ok ${res.sessionId} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`);\n return res.sessionId;\n}\n\n/** 向大脑(配置的 ACP agent)提问(常驻 + 原生多轮)。失败即重建进程(下条用 session/load 恢复上下文)。 */\nexport async function askBrain(\n userId: string,\n message: string,\n onProgress?: (ev: ProgressEvent) => void,\n images?: Array<{ data: string; mimeType: string }>,\n opts: { deadline?: number } = {},\n): Promise<string> {\n return (await askBrainTurn(userId, message, onProgress, images, opts)).text;\n}\n\n/**\n * 同 `askBrain`,但把**实际答话的 agent** 一并返回。\n * 交接摘要必须记实际大脑:请求排队期间可能刚好切换完成,只读入口时的槽位会记错来源。\n */\nexport async function askBrainTurn(\n userId: string,\n message: string,\n onProgress?: (ev: ProgressEvent) => void,\n images?: Array<{ data: string; mimeType: string }>,\n opts: { deadline?: number } = {},\n): Promise<{ text: string; agent: string }> {\n await waitWhileSwitching(); // 切换进行中就先在门口等(有界):绝不在启动新大脑时双起进程\n return withBrainOp(async () => {\n if (expired(opts.deadline)) throw new Error(EXPIRED_MSG);\n const ag = getAgent(\"brain\"); // 排队期间可能刚切过脑——轮到本操作时才取槽位\n const text = await runTurn(ag, userId, message, onProgress, images);\n return { text, agent: ag.name };\n });\n}\n\nasync function runTurn(\n ag: ResolvedAgent,\n userId: string,\n message: string,\n onProgress?: (ev: ProgressEvent) => void,\n images?: Array<{ data: string; mimeType: string }>,\n): Promise<string> {\n await ensureProcess(ag);\n const sid = await ensureSession(ag, userId);\n beginAcpTurn(turn, sid, onProgress);\n const t0 = Date.now();\n const prompt: unknown[] = [];\n if (images?.length && !ag.images) {\n prompt.push({\n type: \"text\",\n text: \"(用户发来图片,但当前大脑不支持图片输入;请让用户改用文字,或切换到支持图片的大脑。不要臆测图片内容。)\",\n });\n }\n if (ag.images) {\n for (const im of images ?? [])\n prompt.push({ type: \"image\", mimeType: im.mimeType, data: im.data });\n }\n if (!hintedSessions.has(sid)) {\n hintedSessions.add(sid);\n prompt.push({\n type: \"text\",\n text: \"(系统提示:你正在通过微信和用户对话。需要给用户发图片时,在回复里用 Markdown 图片语法 ,系统会自动把它作为图片消息发出(你本身不会文生图,可发联网搜到的图、本地已有的图、或自己用代码渲染保存的图表)。重要:服务器从中国大陆下载该图,请选「可直接下载的直链」——优先 raw.githubusercontent.com 等原始文件直链;避免 wikipedia/wikimedia(大陆被墙)以及 pixabay/unsplash 等带热链保护、直链常返回 403 的图床。拿不准时优先把图先下载到本地再用本地绝对路径引用,或直接发链接让用户自己点。其余正常用文字/Markdown 回复。此提示只出现一次,无需理会本句。)\",\n });\n }\n prompt.push({ type: \"text\", text: message || \"(用户发来一张图片,请查看并回应)\" });\n try {\n await promptAcp(\n host,\n { sessionId: sid, prompt },\n {\n idleMs: PROMPT_IDLE_MS,\n capMs: PROMPT_CAP_MS,\n firstFrameMs: firstFrameMs(ag),\n },\n );\n const txt = acpTurnText(turn, sid);\n // 成功回合才登记:轮次 +1、刷新最近使用、首次写入标题(用户消息前 30 字;纯图片消息没有文字 → 留给下一次)\n // 标题取**剥掉交接前缀**后的正文:不然列表里全是「【交接摘要 · 来自 …】」,用户认不出哪条是哪条。\n touchEntry(ag.name, userId, sid, { title: titleOf(stripHandoffPrefix(message)) });\n dlog(\n `prompt ok ${Date.now() - t0}ms chunks=${acpTurnChunks(turn, sid)} updateTypes=[${[...turn.seenUpdateTypes].join(\",\")}]`,\n );\n return txt;\n } catch (e) {\n const chunks = acpTurnChunks(turn, sid);\n const sinceFrame = rpcState.lastFrameAt ? Date.now() - rpcState.lastFrameAt : -1;\n dlog(\n `prompt FAIL ${Date.now() - t0}ms chunks=${chunks} lastFrameAgo=${sinceFrame}ms err=${String(e)} -> rebuild`,\n );\n reset(); // 自愈:杀掉可能假死的进程,下条消息重建并 session/load 恢复\n throw e;\n } finally {\n endAcpTurn(turn, sid);\n }\n}\n\n/** 用户主动开新会话:丢弃旧 session(含磁盘映射),重建一个全新 session。 */\nexport async function newSession(userId: string, opts: { deadline?: number } = {}): Promise<void> {\n await waitWhileSwitching();\n await withBrainOp(async () => {\n if (expired(opts.deadline)) throw new Error(EXPIRED_MSG);\n const ag = getAgent(\"brain\");\n const map = loadSessionMap(ag);\n delete map[keyOf(ag, userId)]; // 只清当前 agent 的;换大脑前那个 agent 的上下文仍保留\n saveSessionMap(map);\n userSessions.delete(userId);\n await ensureProcess(ag);\n await ensureSession(ag, userId);\n });\n}\n\n/**\n * 「恢复会话」:把某条**已有的**会话切回当前,**严格恢复**——只认真接上的 load/resume,\n * 接不上就如实报错,**绝不**回退成 session/new(那会让用户以为上下文回来了,其实是一张白纸),\n * 也**不改**当前会话记录(当前会话原样保留,用户还能继续用)。\n * 成功才动磁盘:userSessions + 当前会话表(`<agent>:<userId>` → 这条)。\n */\nexport async function resumeSession(\n userId: string,\n sessionId: string,\n opts: { deadline?: number } = {},\n): Promise<{ ok: true } | { ok: false; problem: string }> {\n await waitWhileSwitching();\n return withBrainOp(async () => {\n if (expired(opts.deadline)) throw new Error(EXPIRED_MSG);\n const ag = getAgent(\"brain\");\n const short = sessionId.slice(0, 8);\n await ensureProcess(ag);\n const mcpServers = ag.mcpViaAcp ? activeMcpServers(ag) : [];\n const methods: Array<\"load\" | \"resume\"> =\n ag.restore === \"resume\" ? [\"resume\"] : [\"load\", \"resume\"];\n let lastErr = \"\";\n for (const method of methods) {\n try {\n const restored = await requestAcp(\n host,\n `session/${method}`,\n { sessionId, cwd: BRAIN_HOME, mcpServers },\n REQ_TIMEOUT,\n );\n await drainAcpReplay(host, ag);\n await applyAcpModel(host, ag, sessionId, REQ_TIMEOUT);\n await applyAcpSessionMode(host, ag, sessionId, availableAcpModes(restored), REQ_TIMEOUT);\n userSessions.set(userId, sessionId);\n const map = loadSessionMap(ag);\n map[keyOf(ag, userId)] = sessionId;\n saveSessionMap(map);\n dlog(`resume ok ${sessionId}`);\n return { ok: true };\n } catch (e) {\n lastErr = String(e);\n dlog(`resume session/${method} failed (${lastErr})`);\n }\n }\n return {\n ok: false,\n problem: `接不上会话 ${short}(该 agent 不支持恢复,或这条会话已被它自己清理)。当前会话没变,可以继续用;要开新的就发「新会话」。${lastErr ? `\\n(原因:${lastErr.slice(0, 120)})` : \"\"}`,\n };\n });\n}\n\n/**\n * 切换流程用:为新 agent 备好进程与(触发者)会话——启动成功才算「新大脑真的能用 ACP 说话」。\n * 会话走既有 ensureSession:该 agent 里有本人的旧会话就 load/resume 恢复上下文,没有就新建。\n * `userId` 为空(CLI 触发、还不知道是谁在聊)时只验进程能握手。\n */\nexport async function warmup(ag: ResolvedAgent, userId: string): Promise<string> {\n await ensureProcess(ag);\n if (!userId) return \"\";\n return ensureSession(ag, userId);\n}\n\n/** MCP 激活集变更后调用:重启 acp 进程(重建后会 session/load 恢复上下文)。 */\nexport function restart(): void {\n reset();\n}\n\n/**\n * MCP 激活集变更后用:切换进行中先等它结束(有界),再重启——否则会把正在预热的新大脑杀在半路。\n * 返回 false 表示切换在等待上限内仍未结束,这次没有重启(避免留下一个半截的新大脑)。\n */\nexport async function restartForMcpChange(): Promise<boolean> {\n if (switching && !(await drainSwitch(SWITCH_WAIT_MS))) return false;\n restart();\n return true;\n}\n\n/** 当前大脑是否接受图片输入(配置里的能力;默认 true)。 */\nexport function supportsImages(): boolean {\n return getAgent(\"brain\").images;\n}\n\n/** 当前 agent 的静默期心跳覆盖(默认 undefined → 15s)。按回合取:热切换后心跳随之改变。 */\nexport function heartbeatMs(): number | undefined {\n return getAgent(\"brain\").heartbeatMs;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { writeJsonAtomic } from \"./atomic-json.js\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\n\n/**\n * daemon 启动/热切换成功后,把「这一刻真正生效的对话大脑」写进 `<账号目录>/brain-runtime.json`。\n *\n * 为什么还需要这个文件:`brain.pid` 只在 **ACP 子进程真的 spawn 之后**才有内容,而大脑是懒启动的——\n * 刚启动、还没收到消息时没有 pid 记录。此前 `wemind list` 的 `agent=` 只能显示 `brain.json`(有意「不看\n * 当前 shell 的环境变量」),于是用 `WEMIND_BRAIN=cline` 启动的 daemon 会一直显示成配置里的 deepseek,\n * 让人以为切换没生效。\n *\n * 这个文件记录的是 **daemon 进程自己的解析结果**(含来源 env/file/auto),与 CLI 端环境变量无关;\n * daemon 没运行时 `manage.ts` 不会采信它(`stop` 也会清掉),所以不会把旧状态当成现在时。\n */\nexport const BRAIN_RUNTIME_FILE = \"brain-runtime.json\";\n\nexport type BrainRuntime = {\n /** 实际生效的 agent 名(如 `cline`)。 */\n agent: string;\n /** 选择来源:环境变量 / brain.json / 自动探测。 */\n source: \"env\" | \"file\" | \"auto\";\n bin: string;\n args: string[];\n startedAt: number;\n};\n\n/** 写入运行时大脑(原子替换;失败静默——显示用,不该影响收发)。 */\nexport function writeBrainState(dir: string, ag: ResolvedAgent): void {\n const body: BrainRuntime = {\n agent: ag.name,\n source: ag.source,\n bin: ag.bin,\n args: ag.args,\n startedAt: Date.now(),\n };\n try {\n writeJsonAtomic(path.join(dir, BRAIN_RUNTIME_FILE), body);\n } catch {\n /* best-effort */\n }\n}\n\n/** 读运行时大脑;没有/损坏/字段不对 → null(调用方按「没有运行时信息」处理)。 */\nexport function readBrainState(dir: string): BrainRuntime | null {\n try {\n const j = JSON.parse(fs.readFileSync(path.join(dir, BRAIN_RUNTIME_FILE), \"utf-8\")) as Record<\n string,\n unknown\n >;\n if (typeof j?.agent !== \"string\" || !j.agent) return null;\n const src = j.source;\n const source: BrainRuntime[\"source\"] =\n src === \"env\" || src === \"auto\" ? src : src === \"file\" ? \"file\" : \"file\";\n return {\n agent: j.agent,\n source,\n bin: typeof j.bin === \"string\" ? j.bin : \"\",\n args: Array.isArray(j.args)\n ? (j.args as unknown[]).filter((x): x is string => typeof x === \"string\")\n : [],\n startedAt: Number(j.startedAt) || 0,\n };\n } catch {\n return null;\n }\n}\n\n/** 停止 daemon 时清掉,避免下次「已停止」还显示上一次的运行时大脑。 */\nexport function clearBrainState(dir: string): void {\n try {\n fs.unlinkSync(path.join(dir, BRAIN_RUNTIME_FILE));\n } catch {\n /* 不存在即可 */\n }\n}\n","import { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\nimport { getAgent, setAgent } from \"./agent-slot.js\";\nimport { resolvePresetByName, type ResolvedAgent } from \"./brain-agents.js\";\nimport { loadBrainConfig, updateBrainConfig } from \"./brain-config.js\";\nimport { ensureBrainHome } from \"./mcp-sandbox.js\";\nimport { writeBrainState } from \"./brain-state.js\";\nimport { beginSwitch, drainTurn, endSwitch, restart, warmup } from \"./acp-brain.js\";\n\n/**\n * 运行时切换对话大脑。\n *\n * 为什么需要它:agent 过去是模块级常量(进程启动时冻结),切脑只能 `wemind brain set X --restart`——\n * 重启整个 daemon,打断在途任务与回合。现在槽位可变(agent-slot.ts),于是「切脑」可以是一等命令。\n *\n * 流程与取舍(每一步都在防一种具体的坏结果):\n * 1. 解析目标 agent:**绕开环境变量**(用户此刻的明确意图该压过一次性 env 覆盖),不可用直接答复失败,不 spawn。\n * 2. `beginSwitch()` → `drainTurn()`:等在跑的回合结束(默认 30s)。超时**诚实失败**,绝不半路砍回合丢答案。\n * 3. `ensureBrainHome(新 agent)` → `restart()`(杀旧进程)→ `warmup()`(initialize + session/load|new,硬上限 120s)。\n * 4. 成功才写 brain.json + `setAgent()` —— **先落盘再换槽位**,于是「退回」不需要撤销任何东西:\n * 失败路径只做 `restart()`(清掉半截进程与 pid 记录),槽位仍指旧大脑,下一条消息懒重建它(上下文从磁盘 load 回来)。\n * 顺序不能反:先换槽位再写盘,写盘失败会留下「槽位已是新大脑、答复却说已退回」的撒谎状态。\n * 5. 同一目标的并发请求复用同一次切换;不同目标直接拒绝(不排队,避免「连点两下切到 A」这类歧义)。\n *\n * 已知边界:`WEMIND_BRAIN` 环境变量优先级高于 brain.json,命令绕开了它(进程内真的切了),\n * 但**重启后会回到 env 指的大脑** —— 答复里如实警告,不装作没这回事。\n */\n\nconst DRAIN_MS = Number(process.env.WEMIND_SWITCH_DRAIN_MS) || 30_000;\nconst TIMEOUT_MS = Number(process.env.WEMIND_SWITCH_TIMEOUT_MS) || 120_000;\n\nexport type SwitchOutcome =\n | {\n ok: true;\n agent: ResolvedAgent;\n /** 是否真的换了(false = 本来就是它)。 */\n changed: boolean;\n ms: number;\n /** 面向用户的答复(含 env 覆盖等警告)。 */\n notice: string;\n }\n | { ok: false; problem: string };\n\nlet inFlight: { target: string; promise: Promise<SwitchOutcome> } | null = null;\n\n/** 正在切换的目标(没有则 null)——`/ai` 与答复文案用。 */\nexport function switchInFlightTarget(): string | null {\n return inFlight?.target ?? null;\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number, msg: string): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const t = setTimeout(() => reject(new Error(msg)), ms);\n t.unref?.();\n p.then(\n (v) => {\n clearTimeout(t);\n resolve(v);\n },\n (e) => {\n clearTimeout(t);\n reject(e);\n },\n );\n });\n}\n\n/**\n * 环境变量若压着命令结果,答复里必须说清楚(否则用户会以为切换没生效,或下次重启后莫名回到旧的)。\n *\n * 不止 `WEMIND_BRAIN`:`WEMIND_BRAIN_BIN` / `_ARGS` / `_EXTRA_ARGS` / `_MODEL` 等覆盖同样在重启后重新生效。\n * 切换命令本身用 `ignoreEnv` 绕开了它们(进程内真的切了),但下次 daemon 启动仍会按环境重解析——不说清楚就是静默漂移。\n */\nconst BRAIN_ENV_OVERRIDES = [\n \"WEMIND_BRAIN\",\n \"WEMIND_BRAIN_BIN\",\n \"WEMIND_BRAIN_ARGS\",\n \"WEMIND_BRAIN_EXTRA_ARGS\",\n \"WEMIND_BRAIN_HOME_ENV\",\n \"WEMIND_BRAIN_SHELL\",\n \"WEMIND_BRAIN_INHERIT\",\n \"WEMIND_BRAIN_REAL_HOME\",\n \"WEMIND_BRAIN_INHERIT_DIRS\",\n] as const;\n\nfunction envNote(key: string): string {\n const set = BRAIN_ENV_OVERRIDES.filter((name) => {\n const value = process.env[name]?.trim();\n if (!value) return false;\n // 已经指向本次目标:不算「压着命令结果」,不必为它报警\n return !(name === \"WEMIND_BRAIN\" && value.toLowerCase() === key);\n });\n if (!set.length) return \"\";\n const brain = process.env.WEMIND_BRAIN?.trim();\n const back =\n brain && brain.toLowerCase() !== key\n ? `**重启后会回到 WEMIND_BRAIN=${brain}**`\n : `重启后这些覆盖仍会套在 ${key} 上`;\n const list = set.map((name) => `${name}=${process.env[name]?.trim()}`).join(\";\");\n return `\\n⚠️ 环境变量 ${list} 仍在生效(优先级高于 brain.json):本次已切到 ${key},但${back}。想长期稳定,请去掉这些环境变量。`;\n}\n\nfunction sameAgent(a: ResolvedAgent, b: ResolvedAgent): boolean {\n return (\n a.name === b.name &&\n a.bin === b.bin &&\n a.args.join(\"\\u0000\") === b.args.join(\"\\u0000\") &&\n // 模型也算「是不是同一个」:`brain set --model` 只写盘,运行时应用新模型的唯一途径就是走一次真切换\n // (restart → session/load → applyModel)。漏比 model 会让「改完再 switch」回「无需切换」,旧模型一直用下去。\n a.model === b.model\n );\n}\n\n/**\n * 预检一个切换目标:名字 → 可用 agent,并告诉你「是不是本来就已经是它」。\n * **不 spawn、不碰磁盘**(只读配置),所以调用方可以在发 ack 之前先判一次——\n * 避免对「未知 agent / 已装不上」也回一句「正在切换」,再补一条失败。\n */\nexport function preflightSwitch(target: string, dir: string = DATA_DIR): PreflightResult {\n const key = (target ?? \"\").trim().toLowerCase();\n if (!key) return { ok: false, problem: \"需要指定 agent 名(或 `auto` 重新自动探测)。\" };\n const r = resolvePresetByName(key, loadBrainConfig(dir), dir);\n if (!r.ok) return { ok: false, problem: r.problem };\n const current = getAgent(\"brain\");\n return { ok: true, key, agent: r.agent, current, already: sameAgent(current, r.agent) };\n}\n\nexport type PreflightResult =\n | { ok: true; key: string; agent: ResolvedAgent; current: ResolvedAgent; already: boolean }\n | { ok: false; problem: string };\n\n/**\n * 切到某个 agent(`auto` 表示重新自动探测)。`userId` 是启动新大脑时按谁恢复会话(缺省不建会话)。\n * `persist=false`:只在本进程生效、不写 brain.json(CLI 的「仅本次」场景)。\n */\nexport async function switchBrain(\n target: string,\n opts: { userId?: string; dir?: string; persist?: boolean } = {},\n): Promise<SwitchOutcome> {\n const key = (target ?? \"\").trim().toLowerCase();\n if (!key) return { ok: false, problem: \"需要指定 agent 名(或 `auto` 重新自动探测)。\" };\n if (inFlight) {\n if (inFlight.target === key) return inFlight.promise; // 同一目标:复用同一次切换(重复请求不会切两遍)\n return { ok: false, problem: `正在切到「${inFlight.target}」,等它完成后再试。` };\n }\n const promise = runSwitch(key, opts);\n inFlight = { target: key, promise };\n try {\n return await promise;\n } finally {\n if (inFlight?.promise === promise) inFlight = null;\n }\n}\n\nasync function runSwitch(\n key: string,\n opts: { userId?: string; dir?: string; persist?: boolean },\n): Promise<SwitchOutcome> {\n const dir = opts.dir ?? DATA_DIR;\n const pre = preflightSwitch(key, dir);\n if (!pre.ok) return { ok: false, problem: pre.problem };\n const target = pre.agent;\n const current = pre.current;\n const note = envNote(key);\n\n if (pre.already) {\n // 已经是它:不重启进程(免得白白打断上下文),只把选择落实到磁盘\n if (opts.persist !== false) updateBrainConfig({ agent: key }, dir);\n writeBrainState(dir, target); // 运行时身份:list 显示实际生效的大脑\n return {\n ok: true,\n agent: target,\n changed: false,\n ms: 0,\n notice: `当前对话大脑已经是「${target.name}」,无需切换。${note}`,\n };\n }\n\n beginSwitch();\n try {\n const idle = await drainTurn(DRAIN_MS);\n if (!idle)\n return {\n ok: false,\n problem: `当前有回合正在进行(等了 ${Math.round(DRAIN_MS / 1000)} 秒仍未结束),这次切换没有执行——稍后再发一次即可。`,\n };\n\n const t0 = Date.now();\n try {\n ensureBrainHome(target); // 先备沙箱(只往新 agent 的配置目录里加东西,失败退回无需撤销)\n restart(); // 杀掉旧进程;旧会话在磁盘上,切回来时 session/load 恢复\n await withTimeout(warmup(target, opts.userId ?? \"\"), TIMEOUT_MS, warmupTimeoutMsg());\n // 先落盘再更新槽位:写盘失败时槽位仍是旧大脑,答复「已退回原来那个」才是真的。\n // 反过来先 setAgent 再写盘,写失败会留下「槽位是新大脑、磁盘是旧大脑」且答复撒谎的状态。\n if (opts.persist !== false) updateBrainConfig({ agent: key }, dir);\n setAgent(\"brain\", target);\n writeBrainState(dir, target); // 运行时身份:list 显示实际生效的大脑(best-effort)\n const ms = Date.now() - t0;\n return {\n ok: true,\n agent: target,\n changed: true,\n ms,\n notice: `✅ 已切换对话大脑:${current.name} → ${target.name}(${(ms / 1000).toFixed(1)}s)。${note}`,\n };\n } catch (e) {\n restart(); // 清掉半截进程与 pid 记录\n const why = e instanceof Error ? e.message : String(e);\n return {\n ok: false,\n problem: `切换失败,已退回原来那个「${current.name}」:${why}\\n下一条消息会自动重启旧大脑(上下文从磁盘恢复),不用手动处理。`,\n };\n }\n } finally {\n endSwitch();\n }\n}\n\nfunction warmupTimeoutMsg(): string {\n return `新大脑启动超时(${Math.round(TIMEOUT_MS / 1000)}s 内没有完成 initialize/会话)`;\n}\n","/**\n * daemon.log 轮转与保留。\n *\n * 三个常驻 runner 都把 stdout/stderr 写进账号目录的 `daemon.log`;此前没有任何轮转,\n * 长期运行(或崩溃循环被 `WEMIND_MAX_RESTARTS` 拦下之前)会把日志无限撑大。\n * 这里提供一套不依赖外部模块的轮转:`daemon.log` → `daemon.log.1`(最新)… `daemon.log.<keep>`。\n *\n * 轮转只能用 `appendFileSync` 这种「每次打开→写→关闭」的写法:Windows 上无法重命名一个\n * 仍被写入进程持有的文件。内置 watchdog 因此把子进程 stdout/stderr 接成管道,由它逐块追加,\n * 轮转时文件没有打开句柄;`scripts/run-daemon.ps1` 则在每次重拉前(子进程已退出)轮转。\n */\nimport fs from \"node:fs\";\n\nexport type LogLimits = { maxBytes: number; keep: number };\n\n/** 默认单文件上限 10MB;保留 3 份(`.1` 最新,`.3` 最旧)。 */\nexport const DEFAULT_LOG_MAX_BYTES = 10 * 1024 * 1024;\nexport const DEFAULT_LOG_KEEP = 3;\n\n/** 读 `WEMIND_LOG_MAX_MB` / `WEMIND_LOG_KEEP`;非法值回落默认,keep=0 表示轮转时直接删掉旧文件(不留历史)。 */\nexport function logLimitsFromEnv(env: NodeJS.ProcessEnv = process.env): LogLimits {\n const mb = Number(env.WEMIND_LOG_MAX_MB);\n const maxBytes =\n Number.isFinite(mb) && mb > 0 ? Math.floor(mb * 1024 * 1024) : DEFAULT_LOG_MAX_BYTES;\n const keepRaw = Number(env.WEMIND_LOG_KEEP);\n const keep = Number.isInteger(keepRaw) && keepRaw >= 0 ? keepRaw : DEFAULT_LOG_KEEP;\n return { maxBytes, keep };\n}\n\n/**\n * 文件达到上限就轮转;返回是否真的轮转过。文件不存在/还小返回 false。\n * 先删最旧的 `.keep`,再把 `.keep-1 → .keep` … `.1 → .2`、`daemon.log → .1` 依次改名;\n * 任何一步失败(权限/文件被占用)向上抛,由调用方决定记日志还是放弃。\n */\nexport function rotateLogFile(file: string, limits: LogLimits): boolean {\n let size: number;\n try {\n size = fs.statSync(file).size;\n } catch {\n return false; // 还没写过日志\n }\n if (size < limits.maxBytes) return false;\n const keep = Math.max(0, Math.floor(limits.keep));\n if (keep === 0) {\n fs.rmSync(file, { force: true });\n return true;\n }\n fs.rmSync(`${file}.${keep}`, { force: true });\n for (let i = keep - 1; i >= 1; i--) {\n try {\n fs.renameSync(`${file}.${i}`, `${file}.${i + 1}`);\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e;\n }\n }\n fs.renameSync(file, `${file}.1`);\n return true;\n}\n\n/**\n * 逐块追加并自动轮转。内置 watchdog 用它接管子进程的 stdout/stderr:\n * 写文件不用持久的 fd,因此 Windows 上也能在子进程运行期间改名。\n */\nexport class RotatingLog {\n private bytes = 0;\n\n constructor(\n private readonly file: string,\n private readonly limits: LogLimits,\n ) {\n try {\n this.bytes = fs.statSync(file).size;\n } catch {\n this.bytes = 0;\n }\n this.rotateIfNeeded();\n }\n\n /** 追加一块输出;写失败只记到 stderr(不能因为日志问题拖垮 watchdog 的重拉循环)。 */\n write(chunk: string | Buffer): void {\n const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n try {\n fs.appendFileSync(this.file, buf);\n this.bytes += buf.length;\n } catch (e) {\n console.error(\"[wemind/wechat-daemon/wrapper] 写 daemon.log 失败:\", String(e));\n return;\n }\n this.rotateIfNeeded();\n }\n\n writeLine(text: string): void {\n this.write(text.endsWith(\"\\n\") ? text : `${text}\\n`);\n }\n\n private rotateIfNeeded(): void {\n if (this.bytes < this.limits.maxBytes) return;\n try {\n if (!rotateLogFile(this.file, this.limits)) return;\n } catch (e) {\n // 轮转失败不致命:继续往当前文件追加,下次写满再试。\n console.error(\"[wemind/wechat-daemon/wrapper] daemon.log 轮转失败:\", String(e));\n return;\n }\n this.bytes = 0;\n // 轮转标记直接追加,不经过 write():否则 maxBytes 极小时会「写完标记立刻再轮转」无限递归。\n try {\n const marker = `[wemind/wechat-daemon/wrapper] daemon.log 达到 ${Math.round(this.limits.maxBytes / 1024 / 1024)}MB,已轮转(保留 ${this.limits.keep} 份)\\n`;\n fs.appendFileSync(this.file, marker);\n this.bytes += Buffer.byteLength(marker);\n } catch {\n /* 标记写失败不影响轮转本身 */\n }\n }\n}\n","// 自带后台守护进程(方案 b):`wemind start --runner builtin` 用它常驻,不依赖 pm2。\n// - runWatchdog:后台守护进程循环本体(由 `cli.js _watchdog` 调用),单实例守卫 + 循环 spawn daemon、崩溃 5s 重拉。\n// - watchdogStart:从 manage 脱离式启动后台守护进程。\n// 单实例用 wrapper.pid + isOurProcess() 身份校验(不只看 pid 存活,防重启后 pid 复用误判);竞态由「start 前先 stop」进一步压低。\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DATA_DIR, accountDir } from \"@wemind/wechat-ilink-core\";\nimport { BRAIN_CONFIG_EXIT_CODE } from \"./brain-agents.js\";\nimport { stripVscodeDebugEnv } from \"./debug-env.js\";\nimport { logLimitsFromEnv, RotatingLog } from \"./log-rotate.js\";\nimport { CLI_PID_MARKERS, otherOurProcessPid } from \"./proc-identity.js\";\n\nconst RESTART_DELAY_MS = 5000;\n/** 连续崩溃上限(与 pm2 的默认 max_restarts 对齐):只统计「短暂运行就崩」的连续次数。 */\nconst DEFAULT_MAX_RESTARTS = 50;\n/** daemon 跑满这么久视为稳定,连续崩溃计数清零(避免几个月里偶发崩溃累积到上限后不再重拉)。 */\nconst RESTART_STABLE_MS = 10 * 60 * 1000;\n\n/**\n * 连续崩溃计数:`prev` 是上一次的计数,`ranMs` 是这次 daemon 活了多久。\n * 跑满稳定期就清零;超过上限返回 stop=true——后台守护进程应停止重拉,等人看日志,而不是无限刷。\n */\nexport function nextRestartBudget(\n prev: number,\n ranMs: number,\n max: number = DEFAULT_MAX_RESTARTS,\n): { crashes: number; stop: boolean } {\n const base = ranMs >= RESTART_STABLE_MS ? 0 : prev;\n const crashes = base + 1;\n return { crashes, stop: crashes > max };\n}\n/**\n * daemon 发现同账号已有另一个实例在跑时用它退出。\n * 这不是配置错误,重拉无益——另一个实例已经在消费同一个 bot token;后台守护进程/pm2 都应停止重拉。\n */\nexport const DAEMON_ALREADY_RUNNING_EXIT_CODE = 75;\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nfunction readPidFile(file: string): number | null {\n try {\n const n = Number.parseInt(fs.readFileSync(file, \"utf-8\").trim(), 10);\n return Number.isInteger(n) && n > 0 ? n : null;\n } catch {\n return null;\n }\n}\n\n/** 单实例守卫:已有存活**且确认是我们**的后台守护进程返回 false;否则写自己 pid、返回 true。 */\nfunction acquireSingleton(wrapperPidFile: string): boolean {\n const old = readPidFile(wrapperPidFile);\n // 身份校验:陈旧 pid + 重启后 pid 复用时会误判「已有实例」→ 后台守护进程直接退出、daemon 静默不起。\n if (otherOurProcessPid(old, process.pid, CLI_PID_MARKERS)) return false;\n fs.writeFileSync(wrapperPidFile, String(process.pid), \"ascii\");\n return true;\n}\n\n/** 后台守护进程循环本体(由 `cli.js _watchdog` 调用,常驻):单实例守卫 → 循环拉起 daemon、退出 5s 重拉。 */\nexport async function runWatchdog(): Promise<void> {\n const dataDir = DATA_DIR; // 本进程已设 WECHAT_ACCOUNT,core 据此解析到账号目录\n fs.mkdirSync(dataDir, { recursive: true });\n const log = path.join(dataDir, \"daemon.log\");\n const wrapperPidFile = path.join(dataDir, \"wrapper.pid\");\n\n if (!acquireSingleton(wrapperPidFile)) {\n fs.appendFileSync(\n log,\n `[wemind/wechat-daemon/wrapper] 已有实例在跑,本次退出。${new Date().toISOString()}\\n`,\n );\n return;\n }\n // 拿到单实例后才碰日志轮转:另一个实例可能正在写,不能去改名它的文件。\n // 子进程 stdout/stderr 走管道、由 RotatingLog 逐块追加——Windows 上运行中也能轮转(文件没有常驻句柄)。\n const rotator = new RotatingLog(log, logLimitsFromEnv());\n const cleanup = () => {\n try {\n if (readPidFile(wrapperPidFile) === process.pid) fs.unlinkSync(wrapperPidFile);\n } catch {\n /* ignore */\n }\n };\n process.on(\"exit\", cleanup);\n process.on(\"SIGINT\", () => {\n cleanup();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n cleanup();\n process.exit(0);\n });\n\n const cliPath = process.argv[1]; // 当前 cli.js(本进程即 `node cli.js _watchdog`)\n const maxRestarts =\n Number(process.env.WEMIND_MAX_RESTARTS) > 0\n ? Number(process.env.WEMIND_MAX_RESTARTS)\n : DEFAULT_MAX_RESTARTS;\n let crashes = 0;\n for (;;) {\n const startedAt = Date.now();\n rotator.writeLine(`[wemind/wechat-daemon/wrapper] start daemon ${new Date().toISOString()}`);\n const child: ChildProcess = spawn(process.execPath, [cliPath, \"daemon\"], {\n // WECHAT_ACCOUNT 已在本进程环境中;同时摘掉 VS Code 终端注入的调试引导(后台守护进程若本身被注入过,daemon 不能再被带坏,见 debug-env.ts)\n env: stripVscodeDebugEnv(process.env),\n stdio: [\"ignore\", \"pipe\", \"pipe\"], // stdout/stderr 由 rotator 合并进 daemon.log(可轮转)\n windowsHide: true, // 不弹出控制台窗口(Windows):日志已重定向到 daemon.log,无需可见窗口\n });\n child.stdout?.on(\"data\", (chunk: Buffer) => rotator.write(chunk));\n child.stderr?.on(\"data\", (chunk: Buffer) => rotator.write(chunk));\n const code = await new Promise<number>((resolve) => {\n let exitCode = -1;\n child.on(\"exit\", (c) => {\n exitCode = c ?? -1;\n });\n // close 在 stdio 全部关闭后触发:先收完最后一块输出,再写「daemon exited」,否则末尾日志可能丢。\n child.on(\"close\", () => resolve(exitCode));\n child.on(\"error\", () => resolve(-1));\n });\n if (code === DAEMON_ALREADY_RUNNING_EXIT_CODE) {\n // 同账号已有另一个 daemon 在消费同一个 bot token:本后台守护进程再拉只会每 5s 撞一次,停在这里。\n rotator.writeLine(\n `[wemind/wechat-daemon/wrapper] 同账号已有 daemon 在跑(exit ${code}),本后台守护进程退出。${new Date().toISOString()}`,\n );\n return;\n }\n if (code === BRAIN_CONFIG_EXIT_CODE) {\n // 大脑配置错误:重拉无益(需人工修),停在后台守护进程循环外,避免每 5s 刷一条。\n rotator.writeLine(\n `[wemind/wechat-daemon/wrapper] 大脑配置错误(exit ${code}),已停止自动重启。修好后执行:wemind start <账号>`,\n );\n return;\n }\n const plan = nextRestartBudget(crashes, Date.now() - startedAt, maxRestarts);\n crashes = plan.crashes;\n if (plan.stop) {\n rotator.writeLine(\n `[wemind/wechat-daemon/wrapper] 连续崩溃 ${crashes} 次(上限 ${maxRestarts}),已停止自动重启。看 daemon.log 排查;修好后执行:wemind start <账号>${new Date().toISOString()}`,\n );\n return;\n }\n rotator.writeLine(\n `[wemind/wechat-daemon/wrapper] daemon exited (code=${code}), restart in 5s ${new Date().toISOString()}`,\n );\n await sleep(RESTART_DELAY_MS);\n }\n}\n\n/** 从 manage 脱离式启动某账号的后台守护进程(方案 b:builtin runner)。 */\nexport function watchdogStart(key: string, cliPath: string): { ok: boolean; msg: string } {\n const child = spawn(process.execPath, [cliPath, \"_watchdog\"], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: true,\n // 脱离出的后台守护进程拿不到 VS Code 的调试器,带着 bootloader 注入会被「等调试器」卡死 → 摘掉(见 debug-env.ts)\n env: stripVscodeDebugEnv({ ...process.env, WECHAT_ACCOUNT: key }),\n });\n child.unref();\n return {\n ok: true,\n msg: `已启动自带后台守护进程(账号 ${key},pid ${child.pid})。日志:${path.join(accountDir(key), \"daemon.log\")}`,\n };\n}\n","import { ACP_AGENT_PRESETS } from \"./brain-agents.js\";\n\n/**\n * 微信指令表:**解析与帮助是同一张表**(一处改动不会让「帮助里写的」和「真正认的」不一致)。\n *\n * 为什么独立成模块:`manage.ts`(CLI 侧)要用它渲染 `wemind help`,而 `manage.ts` **不能** import `index.ts`\n * (那会把 daemon、长轮询、账户状态全拖进每次 CLI 调用)。所以这里保持**无副作用**:不读环境、不碰磁盘、\n * 不在模块加载时解析任何东西——只做纯字符串匹配。\n *\n * 解析规则(与既有 parseControl/parseTaskControl 一致,**整串锚定**):\n * - 「整串锚定」意味着 `帮助我写代码`、`mcp 是什么`、`任务很重要` 都是**普通消息**(照旧转给大脑);\n * - `任务 <agent 名>:正文` 里的「冒号前」必须**恰好是一个已知 agent 名**才算指定 agent,\n * 所以 `任务 明天:去买菜`、`> nav: 去某地` 仍是普通内容(不会因为带冒号就被误当成指定 agent);\n * - `#<数字> 正文` 只在 `#`/`#` 后跟纯数字时才寻址(`#abc 你好` 是普通消息;`#1 号文件在哪` 说的是「1 号文件」,\n * 数字后紧跟「号」也按普通消息,见 task.reply 的 match);编号与正文之间允许空格或全角冒号(`#2:继续`),\n * 但**不允许半角冒号**(`#1:30 开会` 是时间,不是给 1 号任务回话);\n * - 中文写法与斜杠写法**等价**;`/ai` 家族是统一入口(`/ai`、`/ai brain X`、`/ai task X`)。\n */\n\nexport type ChatCommand =\n /** mcp 列表(沿用旧 kind 名,历史测试与调用方不受影响)。 */\n | { kind: \"list\" }\n /** 新会话(可命名)。 */\n | { kind: \"new\"; name?: string }\n | { kind: \"add\"; name: string }\n | { kind: \"remove\"; name: string }\n /** 会话列表(分页)。 */\n | { kind: \"sessions\"; page?: number }\n /** 恢复(切回)某个旧会话。 */\n | { kind: \"resume\"; ref: string }\n | { kind: \"rename\"; ref: string; name: string }\n | { kind: \"delete\"; ref: string }\n /** AI 总览:可选 agent + 当前对话大脑 / 默认任务大脑 / 池中进程。 */\n | { kind: \"ai\" }\n /** 切换对话大脑(无参 = 显示当前)。 */\n | { kind: \"brain\"; target?: string }\n /** 切默认任务大脑(本次运行内一直生效;不写 brain.json)。 */\n | { kind: \"workerBrain\"; target?: string }\n /** 生成切换用摘要并写入磁盘。 */\n | { kind: \"handoff\" }\n | { kind: \"help\" };\n\nexport type TaskCommand =\n /** 派发任务 / 给任务回话(可选指定 agent)。 */\n | { kind: \"toWorker\"; text: string; agentToken?: string }\n /** 结束任务(无 id = 唯一那条;`all` = 全部)。 */\n | { kind: \"stop\"; id?: number | \"all\" }\n /** 向指定编号的任务回话。 */\n | { kind: \"reply\"; id: number; text: string }\n /** 任务列表。 */\n | { kind: \"tasks\" };\n\n/** owner-only 的指令:非登录者发送 → 短提示拒绝,**不**转发给大脑(避免大脑替用户执行越权动作)。 */\nexport const OWNER_ONLY_KINDS: ReadonlySet<ChatCommand[\"kind\"]> = new Set([\n \"ai\",\n \"brain\",\n \"workerBrain\",\n \"handoff\",\n]);\n\nexport function isOwnerOnly(cmd: ChatCommand): boolean {\n return OWNER_ONLY_KINDS.has(cmd.kind);\n}\n\nexport type CommandSection = \"session\" | \"task\" | \"brain\" | \"mcp\" | \"misc\";\n\nexport type CommandSpec<C> = {\n id: string;\n section: CommandSection;\n /** 仅登录者可用(兜底校验:微信平台只发送登录者的消息,正常不会触发)。 */\n ownerOnly?: boolean;\n /** 帮助里的中文写法。 */\n zh: string;\n /** 等价的斜杠写法(无则省略)。 */\n slash?: string;\n /** 等价的 CLI 命令(`wemind help` 与文档用)。 */\n cli?: string;\n brief: string;\n match: (t: string) => C | null;\n};\n\n/** 可作为 agent 名的集合(小写):`任务 <agent 名>:正文` 的判定表;`auto` 也算(自动探测)。 */\nfunction agentTokenOk(token: string): boolean {\n const k = token.trim().toLowerCase();\n return k === \"auto\" || Object.prototype.hasOwnProperty.call(ACP_AGENT_PRESETS, k);\n}\n\n/** 中文里常见的「页」写法:`会话列表 2` / `会话列表 第2页` / `会话列表 2 页`。 */\nconst PAGE_SUFFIX = String.raw`(?:\\s*第?\\s*(\\d+)\\s*页?)?`;\n\nexport const COMMAND_SPECS: CommandSpec<ChatCommand>[] = [\n {\n id: \"mcp.list\",\n section: \"mcp\",\n zh: \"mcp\",\n slash: \"/mcp\",\n brief: \"列出可选/已激活的 MCP\",\n match: (t) => (/^(?:\\/?mcp|列出\\s*mcp|mcp\\s*list)$/i.test(t) ? { kind: \"list\" } : null),\n },\n {\n id: \"mcp.add\",\n section: \"mcp\",\n zh: \"加载 <名字>\",\n brief: \"加载一个 MCP(下条消息生效)\",\n match: (t) => {\n const m = t.match(/^(?:加载|load)\\s+(\\S+)$/i);\n return m ? { kind: \"add\", name: m[1] } : null;\n },\n },\n {\n id: \"mcp.remove\",\n section: \"mcp\",\n zh: \"卸载 <名字>\",\n brief: \"卸载一个 MCP\",\n match: (t) => {\n const m = t.match(/^(?:卸载|unload)\\s+(\\S+)$/i);\n return m ? { kind: \"remove\", name: m[1] } : null;\n },\n },\n {\n id: \"session.new\",\n section: \"session\",\n zh: \"新会话 [名字]\",\n slash: \"/new [名字]\",\n cli: \"wemind session new [名字] [<key|label>]\",\n brief: \"清空上下文重开(可命名)\",\n match: (t) => {\n const m = t.match(/^(?:新会话|重置|清空上下文|\\/new|new\\s*session)(?:\\s+([\\s\\S]+))?$/i);\n if (!m) return null;\n const name = m[1]?.trim();\n return name ? { kind: \"new\", name } : { kind: \"new\" };\n },\n },\n {\n id: \"session.list\",\n section: \"session\",\n zh: \"会话列表 [页]\",\n slash: \"/sessions [页]\",\n cli: \"wemind session list [--page N] [<key|label>]\",\n brief: \"列出本人会话(← 当前 标当前)\",\n match: (t) => {\n const m = t.match(\n new RegExp(\n String.raw`^(?:会话列表|查看会话|我的会话|\\/sessions?)` + PAGE_SUFFIX + \"$\",\n \"i\",\n ),\n );\n if (!m) return null;\n const page = m[1] ? Number(m[1]) : undefined;\n return page ? { kind: \"sessions\", page } : { kind: \"sessions\" };\n },\n },\n {\n id: \"session.resume\",\n section: \"session\",\n zh: \"恢复会话 <序号|名字|id前缀>\",\n slash: \"/resume <同上>\",\n cli: \"wemind session resume <ref> [<key|label>]\",\n brief: \"切回旧会话(失败明确报错,不静默新建)\",\n match: (t) => {\n const m = t.match(/^(?:恢复会话|回到会话|切换会话|\\/resume)\\s+([\\s\\S]+)$/i);\n return m ? { kind: \"resume\", ref: m[1].trim() } : null;\n },\n },\n {\n id: \"session.rename\",\n section: \"session\",\n zh: '重命名会话 <序号|名字|\"带空格的名字\"> <新名字>',\n slash: \"/rename <同上>\",\n cli: \"wemind session rename <ref> <名字> [<key|label>]\",\n brief: \"给会话起名字(命名后不会被淘汰;旧名字带空格时用引号括起来)\",\n match: (t) => {\n // 旧名字可能含空格:用 \"\" / “” / 「」 括起来才能和后面的新名字分开(§F81)。序号、单段名字照旧不用引号。\n const m = t.match(\n /^(?:重命名会话|会话重命名|会话改名|\\/rename)\\s+(?:\"([^\"]+)\"|“([^”]+)”|「([^」]+)」|(\\S+))\\s+([\\s\\S]+)$/i,\n );\n if (!m) return null;\n const ref = (m[1] ?? m[2] ?? m[3] ?? m[4]).trim();\n const name = m[5].trim();\n return ref && name ? { kind: \"rename\", ref, name } : null;\n },\n },\n {\n id: \"session.delete\",\n section: \"session\",\n zh: \"删除会话 <序号|名字|id前缀>\",\n slash: \"/delete <同上>\",\n cli: \"wemind session delete <ref> [<key|label>]\",\n brief: \"只删登记(agent 侧的历史记录不动)\",\n match: (t) => {\n const m = t.match(/^(?:删除会话|删会话|\\/delete|\\/del)\\s+([\\s\\S]+)$/i);\n return m ? { kind: \"delete\", ref: m[1].trim() } : null;\n },\n },\n {\n id: \"agent.overview\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"AI / AI 列表\",\n slash: \"/ai\",\n cli: \"wemind brain\",\n brief: \"看可选 agent 与当前配置\",\n match: (t) => (/^(?:ai|ai\\s*列表|列出\\s*ai|\\/ai)$/i.test(t) ? { kind: \"ai\" } : null),\n },\n {\n id: \"agent.brain\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"切换大脑 [agent 名|auto]\",\n slash: \"/brain [agent 名|auto]、/ai brain [agent 名|auto]\",\n cli: \"wemind brain switch <agent 名|auto>\",\n brief: \"换对话大脑,不用重启(失败自动退回原来那个)\",\n match: (t) => {\n const m = t.match(/^(?:切换大脑|ai\\s*大脑|\\/brain|\\/ai\\s+brain)(?:\\s+([\\s\\S]+))?$/i);\n if (!m) return null;\n const target = m[1]?.trim();\n return target ? { kind: \"brain\", target } : { kind: \"brain\" };\n },\n },\n {\n id: \"agent.workerBrain\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"切换任务大脑 [agent 名|auto]\",\n slash: \"/worker [agent 名|auto]、/ai task [agent 名|auto]\",\n cli: \"wemind brain switch <agent 名> --worker\",\n brief: \"设默认任务大脑(本次运行内一直生效,不写 brain.json)\",\n match: (t) => {\n const m = t.match(\n /^(?:切换任务大脑|任务大脑|ai\\s*任务|\\/worker|\\/ai\\s+task)(?:\\s+([\\s\\S]+))?$/i,\n );\n if (!m) return null;\n const target = m[1]?.trim();\n return target ? { kind: \"workerBrain\", target } : { kind: \"workerBrain\" };\n },\n },\n {\n id: \"brain.handoff\",\n section: \"brain\",\n ownerOnly: true,\n zh: \"交接\",\n slash: \"/handoff\",\n cli: \"wemind brain handoff\",\n brief: \"生成当前会话摘要,切换后给新大脑接手\",\n match: (t) => (/^(?:交接|会话交接|\\/handoff)$/i.test(t) ? { kind: \"handoff\" } : null),\n },\n {\n id: \"misc.help\",\n section: \"misc\",\n zh: \"帮助\",\n slash: \"/help\",\n cli: \"wemind help\",\n brief: \"这张表\",\n match: (t) => (/^(?:帮助|指令|命令|\\/help|\\/h)$/i.test(t) ? { kind: \"help\" } : null),\n },\n];\n\nexport const TASK_COMMAND_SPECS: CommandSpec<TaskCommand>[] = [\n {\n id: \"task.dispatch\",\n section: \"task\",\n zh: \"任务 [<agent 名>:]正文\",\n slash: \"/task [<agent 名>] 正文\",\n cli: 'wemind task \"正文\" [--agent <名字>] [--cwd 目录] [<key|label>]',\n brief: \"派发新任务(有活跃任务时发给它);带 agent 名就由它执行(只影响这一条)\",\n match: (t) => {\n // ① `任务:正文` / `task: 正文`(沿用旧写法,不带 agent 名)\n let m = t.match(/^(?:任务|task)[::]\\s*([\\s\\S]+)$/i);\n if (m) return { kind: \"toWorker\", text: m[1].trim() };\n // ② `任务 <agent 名>:正文` —— 冒号前必须恰好是已知 agent 名,否则整串都是正文\n m = t.match(/^(?:任务|task)\\s+(\\S+?)[::]\\s*([\\s\\S]+)$/i);\n if (m) {\n if (agentTokenOk(m[1]))\n return { kind: \"toWorker\", text: m[2].trim(), agentToken: m[1].toLowerCase() };\n return { kind: \"toWorker\", text: t.replace(/^(?:任务|task)\\s+/i, \"\").trim() };\n }\n // ③ `/task <agent 名> 正文`(斜杠形式用空格分隔,agent 名同样必须命中)\n m = t.match(/^\\/task\\s+(\\S+)\\s+([\\s\\S]+)$/i);\n if (m && agentTokenOk(m[1]))\n return { kind: \"toWorker\", text: m[2].trim(), agentToken: m[1].toLowerCase() };\n m = t.match(/^\\/task\\s+([\\s\\S]+)$/i);\n if (m) return { kind: \"toWorker\", text: m[1].trim() };\n // ④ `> 正文` 快捷别名(沿用旧写法)\n m = t.match(/^>\\s*([\\s\\S]+)$/);\n if (m) return { kind: \"toWorker\", text: m[1].trim() };\n return null;\n },\n },\n {\n id: \"task.reply\",\n section: \"task\",\n zh: \"#<编号> 正文\",\n slash: \"/reply <编号> 正文\",\n cli: 'wemind task-reply <编号> \"正文\" [<key|label>]',\n brief: \"给指定编号的任务回话(回任务都用它;无前缀消息走大脑)\",\n match: (t) => {\n const m = t.match(/^(?:[##](\\d+)[\\s:]+|\\/reply\\s+(\\d+)\\s+)([\\s\\S]+)$/i);\n if (!m) return null;\n // `#1 号文件在哪` 说的是「1 号文件」,不是给 1 号任务回话 —— 数字后面紧跟「号」时按普通消息处理。\n if (m[1] !== undefined && m[3].startsWith(\"号\")) return null;\n return { kind: \"reply\", id: Number(m[1] ?? m[2]), text: m[3].trim() };\n },\n },\n {\n id: \"task.list\",\n section: \"task\",\n zh: \"任务列表\",\n slash: \"/tasks\",\n cli: \"wemind task-list [<key|label>]\",\n brief: \"编号 / agent / 状态 / 轮次 / 已运行\",\n match: (t) => (/^(?:任务列表|任务清单|列出任务|\\/tasks?)$/i.test(t) ? { kind: \"tasks\" } : null),\n },\n {\n id: \"task.stop\",\n section: \"task\",\n zh: \"结束任务 [编号]、结束全部任务\",\n slash: \"/stop [编号|全部]\",\n cli: \"wemind task-stop [编号|全部] [<key|label>]\",\n brief: \"结束任务(进程留着复用)\",\n match: (t) => {\n if (/^(?:结束全部任务|停止全部任务|\\/stop\\s+(?:all|全部))$/i.test(t))\n return { kind: \"stop\", id: \"all\" };\n const m = t.match(/^(?:结束任务|停止任务|\\/stop)(?:\\s*(\\d+))?$/i);\n if (!m) return null;\n return m[1] ? { kind: \"stop\", id: Number(m[1]) } : { kind: \"stop\" };\n },\n },\n];\n\n/** 解析一条微信指令(命中 → daemon 自己处理,不转发大脑);不命中 → null(普通消息)。 */\nexport function parseChatCommand(text: string): ChatCommand | null {\n const t = (text ?? \"\").trim();\n if (!t) return null;\n for (const spec of COMMAND_SPECS) {\n const hit = spec.match(t);\n if (hit) return hit;\n }\n return null;\n}\n\n/** 解析「任务/worker 相关」指令。先于普通消息判定,后于 parseChatCommand。 */\nexport function parseTaskCommand(text: string): TaskCommand | null {\n const t = (text ?? \"\").trim();\n if (!t) return null;\n for (const spec of TASK_COMMAND_SPECS) {\n const hit = spec.match(t);\n if (hit) return hit;\n }\n return null;\n}\n\nconst SECTION_TITLE: Record<CommandSection, string> = {\n session: \"会话(各自只管自己的)\",\n task: \"任务\",\n brain: \"大脑(仅登录者)\",\n mcp: \"MCP\",\n misc: \"\",\n};\n\n/** 渲染帮助只需这些字段(两种指令表的公共形状)。 */\ntype HelpSpec = {\n section: CommandSection;\n ownerOnly?: boolean;\n zh: string;\n slash?: string;\n cli?: string;\n brief: string;\n};\n\nfunction specLines(specs: HelpSpec[], mode: \"wechat\" | \"cli\"): string[] {\n const out: string[] = [];\n for (const s of specs) {\n if (mode === \"cli\") {\n out.push(s.cli ? ` ${s.cli} — ${s.brief}` : ` (无 CLI)${s.zh} — ${s.brief}`);\n } else {\n const alt = s.slash ? ` | ${s.slash}` : \"\";\n out.push(` ${s.zh}${alt} — ${s.brief}`);\n }\n }\n return out;\n}\n\n/**\n * 渲染帮助。`role` 非 owner 时**隐藏** owner-only 指令(只留一行说明),避免「看得见、发过去被拒」的挫败。\n * `mode=cli` 时同一张表换成 CLI 写法(`wemind help` 用);`sections` 可只取几个分区\n * (CLI 帮助里 MCP 是微信侧才有的操作,列进去只会是「(无 CLI)」噪音)。\n */\nexport function renderHelp(\n role: \"owner\" | \"user\" = \"owner\",\n mode: \"wechat\" | \"cli\" = \"wechat\",\n opts: { sections?: CommandSection[] } = {},\n): string {\n const chat = COMMAND_SPECS.filter((s) => role === \"owner\" || !s.ownerOnly);\n const tasks = TASK_COMMAND_SPECS;\n const head =\n mode === \"cli\"\n ? \"📖 WeMind 指令(CLI 写法;微信里中文/斜杠等价)\"\n : \"📖 WeMind 指令(中文与斜杠写法等价,任选一种)\";\n const lines: string[] = [head];\n const sections =\n opts.sections ?? ([\"session\", \"task\", \"brain\", \"mcp\", \"misc\"] as CommandSection[]);\n for (const section of sections) {\n const specs = section === \"task\" ? tasks : chat.filter((s) => s.section === section);\n if (!specs.length) continue;\n const title = SECTION_TITLE[section];\n if (title) lines.push(`【${title}】`);\n lines.push(...specLines(specs, mode));\n }\n if (role !== \"owner\")\n lines.push(\"(切换大脑 / 交接这类账号级指令仅登录者可用;会话与任务指令永远只属于你自己。)\");\n lines.push(\"提示:<尖括号> 表示占位内容,实际发送不要带尖括号。\");\n return lines.join(\"\\n\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\n/**\n * CLI ↔ daemon 的控制桥(文件式):CLI 写 `control-inbox/<ts>-<id>.json` 请求,daemon 扫描执行后写\n * `control-out/<id>.json` 应答;CLI 短轮询应答并打印。\n *\n * 为什么要有它:切换大脑/开新会话/恢复会话都必须**在 daemon 进程里**发生(进程内的 ACP 连接、\n * 当前会话记录、任务表都是 daemon 的活状态)。CLI 另起一个进程去改文件只能改「下次启动才读」的配置,\n * 那就回到「切脑要重启 daemon、打断在途任务」的老问题。桥让 CLI 与微信指令走同一段代码。\n *\n * 与既有 task-inbox 的分工:task-inbox 是**单向**(CLI 派活,结果从微信回);控制桥要**回话**(CLI 要打印成功/失败),\n * 所以多了 out 目录与轮询。\n *\n * **安全边界**:桥只接受**agent 名与纯数据**(target/ref/name/id/page),**绝不接受 bin/args/shell**——\n * 就算被伪造(进程能写账号目录,task-inbox 本来也是这个性质),最坏也只是「选一个已装的 agent 或清掉自己的会话」,\n * 不能借此让 daemon 执行任意程序。`parseControlRequest` 是唯一的入口校验。\n */\n\nexport type ControlKind =\n /** 热切换对话大脑(写入磁盘 + 启动新大脑 + 失败退回)。 */\n | \"brain.switch\"\n /** 设默认任务大脑(本次运行内一直生效,不写入磁盘)。 */\n | \"brain.worker\"\n /** 生成交接摘要(写入磁盘,返回摘要正文)。 */\n | \"brain.handoff\"\n | \"session.new\"\n | \"session.list\"\n | \"session.resume\"\n | \"session.rename\"\n | \"session.delete\"\n /** 重新登录后解除 daemon 进程内的 -14 冷却(`clearSessionPause` 是进程内状态,CLI 自己清不到)。 */\n | \"session.unpause\";\n\nconst KINDS: ReadonlySet<string> = new Set<ControlKind>([\n \"brain.switch\",\n \"brain.worker\",\n \"brain.handoff\",\n \"session.new\",\n \"session.list\",\n \"session.resume\",\n \"session.rename\",\n \"session.delete\",\n \"session.unpause\",\n]);\n\nexport type ControlRequest = {\n id: string;\n kind: ControlKind;\n at: number;\n /** CLI 的等待截止时间(epoch ms):daemon 超过它就不再执行(CLI 已放弃,执行了也看不到结果)。旧 CLI 不传 = 不设截止。 */\n expiresAt?: number;\n /** agent 名(brain.switch / brain.worker)。 */\n target?: string;\n /** 会话引用:序号 / 名字 / id 前缀(session.resume / rename / delete)。 */\n ref?: string;\n /** 会话名字(session.new / session.rename)。 */\n name?: string;\n /** 会话列表页码。 */\n page?: number;\n};\n\nexport type ControlResponse = {\n id: string;\n at: number;\n /** 命令是否成功(决定 CLI 的退出码展示)。 */\n ok: boolean;\n /** 打印给用户的那段话(就是微信里会看到的那句)。 */\n text: string;\n};\n\nexport function controlInboxDir(dir: string = DATA_DIR): string {\n return path.join(dir, \"control-inbox\");\n}\nexport function controlOutDir(dir: string = DATA_DIR): string {\n return path.join(dir, \"control-out\");\n}\n\n/** 请求 id:带时间戳(文件名排序即时间序)+ 随机后缀(同一毫秒内不发重)。 */\nexport function newRequestId(now: number = Date.now()): string {\n return `${now}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\n/** 文件 id 只允许安全字符:它直接拼进 `control-out/<id>.json`,不能让 `../`、盘符或路径分隔符逃出目录。 */\nfunction isSafeFileId(id: string): boolean {\n return /^[A-Za-z0-9._-]+$/.test(id) && !id.includes(\"..\") && !id.includes(\":\");\n}\n\n/** 写请求(原子:tmp + rename,避免 daemon 读到半截 JSON)。 */\nexport function writeControlRequest(req: ControlRequest, dir: string = DATA_DIR): void {\n const d = controlInboxDir(dir);\n fs.mkdirSync(d, { recursive: true });\n const tmp = path.join(d, `${req.at}-${req.id}.json.tmp`);\n fs.writeFileSync(tmp, JSON.stringify(req, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, path.join(d, `${req.at}-${req.id}.json`));\n}\n\n/** 读收件箱(按文件名升序 = 时间序);`req` 为 null 表示坏文件/非法 kind(调用方直接丢弃)。 */\nexport function readControlInbox(\n dir: string = DATA_DIR,\n): { file: string; req: ControlRequest | null }[] {\n let files: string[];\n try {\n files = fs.readdirSync(controlInboxDir(dir)).filter((f) => f.endsWith(\".json\"));\n } catch {\n return [];\n }\n files.sort();\n return files.map((file) => {\n try {\n const raw = JSON.parse(fs.readFileSync(path.join(controlInboxDir(dir), file), \"utf-8\"));\n return { file, req: parseControlRequest(raw) };\n } catch {\n return { file, req: null };\n }\n });\n}\n\n/** 入口校验:只有已知 kind + 字符串/数字字段能通过(**没有** bin/args/shell 这类字段的位置)。 */\nexport function parseControlRequest(raw: unknown): ControlRequest | null {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return null;\n const o = raw as Record<string, unknown>;\n const kind = typeof o.kind === \"string\" ? o.kind : \"\";\n if (!KINDS.has(kind)) return null;\n const id = typeof o.id === \"string\" && o.id ? o.id : \"\";\n if (!id || !isSafeFileId(id)) return null;\n const str = (v: unknown): string | undefined =>\n typeof v === \"string\" && v.trim() ? v.trim().slice(0, 2000) : undefined;\n const req: ControlRequest = {\n id,\n kind: kind as ControlKind,\n at: typeof o.at === \"number\" && Number.isFinite(o.at) ? o.at : Date.now(),\n };\n if (typeof o.expiresAt === \"number\" && Number.isFinite(o.expiresAt)) {\n req.expiresAt = o.expiresAt;\n }\n const target = str(o.target);\n if (target) req.target = target;\n const ref = str(o.ref);\n if (ref) req.ref = ref;\n const name = str(o.name);\n if (name) req.name = name;\n if (typeof o.page === \"number\" && Number.isInteger(o.page) && o.page > 0) req.page = o.page;\n return req;\n}\n\n/** 写应答(原子)。 */\nexport function writeControlResponse(res: ControlResponse, dir: string = DATA_DIR): void {\n if (!isSafeFileId(res.id)) return; // 应答文件名由请求 id 拼成:不安全就不写(否则可借 ../ 覆盖账号数据)\n const d = controlOutDir(dir);\n fs.mkdirSync(d, { recursive: true });\n const tmp = path.join(d, `${res.id}.json.tmp`);\n fs.writeFileSync(tmp, JSON.stringify(res, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, path.join(d, `${res.id}.json`));\n}\n\nexport function readControlResponse(id: string, dir: string = DATA_DIR): ControlResponse | null {\n if (!isSafeFileId(id)) return null;\n try {\n const j = JSON.parse(fs.readFileSync(path.join(controlOutDir(dir), `${id}.json`), \"utf-8\"));\n if (!j || typeof j !== \"object\") return null;\n return {\n id: String(j.id ?? id),\n at: Number(j.at) || 0,\n ok: j.ok === true,\n text: typeof j.text === \"string\" ? j.text : \"\",\n };\n } catch {\n return null;\n }\n}\n\n/** 普通命令的等待上限(30s;`WEMIND_CONTROL_TIMEOUT_MS` 可调)。 */\nexport const CONTROL_TIMEOUT_DEFAULT_MS = Number(process.env.WEMIND_CONTROL_TIMEOUT_MS) || 30_000;\n\n/** 请求是否已超过 CLI 的等待上限:超过就不再执行(否则 CLI 报超时、daemon 却照做,两边说法相反)。 */\nexport function isControlRequestExpired(\n req: Pick<ControlRequest, \"expiresAt\">,\n now: number = Date.now(),\n): boolean {\n return typeof req.expiresAt === \"number\" && Number.isFinite(req.expiresAt) && now > req.expiresAt;\n}\n\n/** 短轮询等应答;超时返回 null(CLI 据此如实说「没等到回复」而不是假装成功)。读到后删掉应答文件,避免长期堆积。 */\nexport async function awaitControlResponse(\n id: string,\n dir: string = DATA_DIR,\n timeoutMs: number = CONTROL_TIMEOUT_DEFAULT_MS,\n pollMs = 100,\n): Promise<ControlResponse | null> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const r = readControlResponse(id, dir);\n if (r) {\n if (isSafeFileId(id)) {\n try {\n fs.unlinkSync(path.join(controlOutDir(dir), `${id}.json`));\n } catch {\n /* ignore */\n }\n }\n return r;\n }\n if (Date.now() >= deadline) return null;\n await new Promise((r2) => setTimeout(r2, pollMs));\n }\n}\n\n/** 清掉没人再读的旧应答(CLI 崩了/超时放弃留下的;按 mtime,默认 1 小时)。返回删除条数。 */\nexport function sweepControlOut(\n maxAgeMs = 3_600_000,\n now: number = Date.now(),\n dir: string = DATA_DIR,\n): number {\n let files: string[];\n try {\n files = fs.readdirSync(controlOutDir(dir)).filter((f) => f.endsWith(\".json\"));\n } catch {\n return 0;\n }\n let removed = 0;\n for (const f of files) {\n const p = path.join(controlOutDir(dir), f);\n try {\n if (now - fs.statSync(p).mtimeMs > maxAgeMs) {\n fs.unlinkSync(p);\n removed++;\n }\n } catch {\n /* ignore */\n }\n }\n return removed;\n}\n\n/** daemon 启动时清空桥目录(作废重启前的残留命令,防重放)。 */\nexport function clearControlDirs(dir: string = DATA_DIR): void {\n for (const d of [controlInboxDir(dir), controlOutDir(dir)]) {\n try {\n fs.rmSync(d, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n try {\n fs.mkdirSync(d, { recursive: true });\n } catch {\n /* ignore */\n }\n }\n}\n\n/**\n * 「这个 daemon 认识控制桥」的凭据:`control-out/.bridge`,内容是**它自己的 pid**。\n *\n * 为什么需要:升级 CLI 后 daemon 往往还在跑旧版本(它没有桥的扫描器)。CLI 若径直把请求写进收件箱,\n * 只会干等到超时(默认 30s,切换 165s),用户看到的是「莫名其妙失败」。有了凭据,CLI 能在**发出前**就说清\n * 「daemon 是旧版,重启一次再用」,把三分钟的静默等待换成一句能照做的话。\n */\nexport function bridgeMarkerPath(dir: string = DATA_DIR): string {\n return path.join(controlOutDir(dir), \".bridge\");\n}\n\nexport function markBridgeReady(dir: string = DATA_DIR, pid: number = process.pid): void {\n try {\n fs.mkdirSync(controlOutDir(dir), { recursive: true });\n fs.writeFileSync(bridgeMarkerPath(dir), JSON.stringify({ pid, at: Date.now() }), \"utf-8\");\n } catch {\n /* ignore */\n }\n}\n\n/** 凭据里的 pid;没有/坏了 → null(CLI 视为「daemon 不是认识桥的版本」)。 */\nexport function bridgePidOf(dir: string = DATA_DIR): number | null {\n try {\n const j = JSON.parse(fs.readFileSync(bridgeMarkerPath(dir), \"utf-8\"));\n return typeof j?.pid === \"number\" && j.pid > 0 ? j.pid : null;\n } catch {\n return null;\n }\n}\n\n/**\n * 按 kind 给等待上限:`brain.switch` 要跑「等在途回合结束 + 启动新大脑」(启动本身就有 120s 硬上限),\n * 用普通上限会让 CLI 在正确的长切换上误报超时。\n */\nexport function controlTimeoutMs(kind: ControlKind, env: NodeJS.ProcessEnv = process.env): number {\n const base = Number(env.WEMIND_CONTROL_TIMEOUT_MS) || CONTROL_TIMEOUT_DEFAULT_MS;\n if (kind === \"brain.switch\") {\n const warm = Number(env.WEMIND_SWITCH_TIMEOUT_MS) || 120_000;\n const drain = Number(env.WEMIND_SWITCH_DRAIN_MS) || 30_000;\n return Math.max(base, warm + drain + 15_000);\n }\n if (kind === \"brain.handoff\") {\n // 交接要跑一整个模型回合,默认上限 30s 必然误报超时;用回合硬上限 + 余量。\n const cap = Number(env.WECHAT_BRAIN_CAP_MS) || 900_000;\n return Math.max(base, cap + 15_000);\n }\n // 登录后的「解除冷却」是尽力而为:旧 daemon 不认识这个 kind 会静默丢弃,不能为它卡住登录 30s。\n if (kind === \"session.unpause\") return Math.min(base, 3000);\n return base;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport {\n controlInboxDir,\n isControlRequestExpired,\n readControlInbox,\n sweepControlOut,\n writeControlResponse,\n type ControlRequest,\n} from \"./control-bridge.js\";\n\nexport type ControlInboxDeps = {\n /** 执行一条已校验且未过期的请求;返回给 CLI 的文本(`❌` 开头表示失败)。 */\n handle: (req: ControlRequest, owner: string) => Promise<string>;\n log: (...args: unknown[]) => void;\n now?: () => number;\n};\n\n/**\n * 控制桥收件箱扫描器。同一时刻只处理一条:切换/恢复本来就要串行(它们争的是同一个 ACP 连接),\n * 处理期间再来一次扫描直接返回。\n *\n * 抽成类是为了让「过期请求不执行 / 失败一定写应答 / 单条异常不拖垮后面」这些编排可单测(见 `control-inbox-runner.test.ts`)。\n */\nexport class ControlInboxRunner {\n private readonly dir: string;\n private readonly deps: Required<ControlInboxDeps>;\n private busy = false;\n\n constructor(dir: string, deps: ControlInboxDeps) {\n this.dir = dir;\n this.deps = { ...deps, now: deps.now ?? Date.now };\n }\n\n async scan(owner: string): Promise<void> {\n if (this.busy) return;\n sweepControlOut(3_600_000, this.deps.now(), this.dir);\n const items = readControlInbox(this.dir);\n if (!items.length) return;\n this.busy = true;\n try {\n for (const { file, req } of items) {\n try {\n fs.unlinkSync(path.join(controlInboxDir(this.dir), file));\n } catch {\n /* ignore */\n } // 先删,避免重复处理\n if (!req) {\n this.deps.log(\"control: 非法请求(未知 kind / 坏 JSON),已丢弃:\", file);\n continue;\n }\n // CLI 已经等到超时并放弃:不要再执行(否则「CLI 说没等到、daemon 却照做」)。\n if (isControlRequestExpired(req, this.deps.now())) {\n this.deps.log(\"control: 请求已超时,跳过执行:\", req.kind, file);\n writeControlResponse(\n {\n id: req.id,\n at: this.deps.now(),\n ok: false,\n text: \"❌ 请求已超时(CLI 已不再等待),本次未执行。\",\n },\n this.dir,\n );\n continue;\n }\n this.deps.log(\n \"control(cli):\",\n req.kind,\n JSON.stringify({ target: req.target, ref: req.ref }),\n );\n let text: string;\n try {\n text = await this.deps.handle(req, owner);\n } catch (e) {\n text = `❌ 处理出错:${String(e)}`;\n }\n // 约定:❌ 开头即失败(CLI 据此定退出码)。应答一定写——不写的话 CLI 会白等到超时。\n writeControlResponse(\n { id: req.id, at: this.deps.now(), ok: !text.startsWith(\"❌\"), text },\n this.dir,\n );\n }\n } finally {\n this.busy = false;\n }\n }\n}\n","import dns from \"node:dns/promises\";\nimport net from \"node:net\";\n\n/**\n * 发图 URL 的安全闸:只允许公网 http(s)。\n *\n * 大脑的回复可能被微信里的提示注入带偏,让它输出 `http://127.0.0.1:…` / 内网服务地址;daemon 直接 fetch 会把\n * 内网响应当作图片发回微信(SSRF)。这里在每次请求(含每一跳重定向)前校验主机,拒绝回环/私网/链路本地等地址。\n * 本地文件绝对路径不受影响——那是大脑自己就能读的文件,不是网络面。\n */\n\ntype Lookup = (host: string) => Promise<Array<{ address: string; family: number }>>;\n\nconst defaultLookup: Lookup = (host) => dns.lookup(host, { all: true });\n\nfunction ipv4Parts(ip: string): number[] {\n const p = ip.split(\".\").map(Number);\n return p.length === 4 && p.every((n) => Number.isInteger(n) && n >= 0 && n <= 255) ? p : [];\n}\n\nfunction ipv4Private(ip: string): boolean {\n const p = ipv4Parts(ip);\n if (p.length !== 4) return false;\n const [a, b] = p;\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 100 && b >= 64 && b <= 127) /* CGNAT */ ||\n (a === 169 && b === 254) /* 链路本地(含云元数据 169.254.169.254) */ ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 198 && (b === 18 || b === 19)) /* 基准测试 */ ||\n a >= 224 /* 组播 + 保留 */\n );\n}\n\n/** 把 IPv6 字面量折成 BigInt(支持 `::` 压缩与末尾内嵌 IPv4);解析不了返回 null。 */\nfunction ipv6ToBigInt(ip: string): bigint | null {\n let s = ip.toLowerCase();\n const pct = s.indexOf(\"%\");\n if (pct >= 0) s = s.slice(0, pct); // zone id\n let tail: number[] = [];\n const v4 = s.match(/(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (v4) {\n const p = ipv4Parts(v4[1]);\n if (p.length !== 4) return null;\n tail = [p[0] * 256 + p[1], p[2] * 256 + p[3]];\n s = s.slice(0, s.length - v4[1].length).replace(/:$/, \"\");\n if (!s) return null;\n }\n const halves = s.split(\"::\");\n if (halves.length > 2) return null;\n const parse = (part: string): number[] | null => {\n if (!part) return [];\n const out: number[] = [];\n for (const h of part.split(\":\")) {\n if (!/^[0-9a-f]{1,4}$/.test(h)) return null;\n out.push(parseInt(h, 16));\n }\n return out;\n };\n const head = parse(halves[0] ?? \"\");\n const mid = parse(halves[1] ?? \"\");\n if (!head || !mid) return null;\n const missing = 8 - head.length - mid.length - tail.length;\n if (missing < 0 || (halves.length === 1 && missing !== 0)) return null;\n const groups = [...head, ...Array<number>(missing).fill(0), ...mid, ...tail];\n let v = 0n;\n for (const g of groups) v = (v << 16n) | BigInt(g);\n return v;\n}\n\nfunction ipv6Private(ip: string): boolean {\n const v = ipv6ToBigInt(ip);\n if (v === null) return true; // 解析不了:保守拒绝\n if (v === 0n || v === 1n) return true; // :: / ::1\n const top16 = v >> 112n;\n if ((top16 & 0xffc0n) === 0xfe80n) return true; // fe80::/10 链路本地\n if ((top16 & 0xffc0n) === 0xfec0n) return true; // fec0::/10 站点本地(已弃用)\n if ((top16 & 0xfe00n) === 0xfc00n) return true; // fc00::/7 唯一本地\n if ((top16 & 0xff00n) === 0xff00n) return true; // ff00::/8 组播\n if (v >> 32n === 0xffffn) {\n // ::ffff:0:0/96:内嵌 IPv4,按 IPv4 规则再判一次\n const n = Number(v & 0xffffffffn);\n return ipv4Private(`${n >>> 24}.${(n >>> 16) & 255}.${(n >>> 8) & 255}.${n & 255}`);\n }\n return false;\n}\n\n/** 回环/私网/链路本地/组播等「不该从公网图片地址访问」的地址;非 IP 字面量返回 false。 */\nexport function isPrivateAddress(ip: string): boolean {\n const t = ip.trim();\n const fam = net.isIP(t);\n if (fam === 4) return ipv4Private(t);\n if (fam === 6) return ipv6Private(t);\n return false;\n}\n\n/**\n * 校验图片 URL:协议只能是 http(s),主机不能是本机/内网。\n * 域名要先解析、逐个地址检查(解析失败也拒绝——宁可不发图,也不把请求打到不可知的主机)。\n */\nexport async function assertPublicHttpUrl(\n raw: string,\n lookup: Lookup = defaultLookup,\n): Promise<URL> {\n let u: URL;\n try {\n u = new URL(raw);\n } catch {\n throw new Error(`图片地址不是合法 URL: ${raw}`);\n }\n if (u.protocol !== \"http:\" && u.protocol !== \"https:\") {\n throw new Error(`只允许 http/https 图片地址: ${raw}`);\n }\n const host = u.hostname\n .replace(/^\\[|\\]$/g, \"\")\n .replace(/\\.$/, \"\")\n .toLowerCase();\n if (!host) throw new Error(`图片地址缺少主机: ${raw}`);\n if (host === \"localhost\" || host.endsWith(\".localhost\")) {\n throw new Error(`拒绝访问本机/内网地址: ${host}`);\n }\n if (net.isIP(host)) {\n if (isPrivateAddress(host)) throw new Error(`拒绝访问本机/内网地址: ${host}`);\n return u;\n }\n let addrs: Array<{ address: string; family: number }>;\n try {\n addrs = await lookup(host);\n } catch {\n throw new Error(`无法解析图片地址的主机: ${host}`);\n }\n if (!addrs.length) throw new Error(`无法解析图片地址的主机: ${host}`);\n for (const a of addrs) {\n if (isPrivateAddress(a.address)) {\n throw new Error(`拒绝访问本机/内网地址: ${host}(${a.address})`);\n }\n }\n return u;\n}\n","import fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { readBodyWithLimit } from \"@wemind/wechat-ilink-core\";\n\nimport { assertPublicHttpUrl } from \"./image-url.js\";\n\n/**\n * 从 brain 的文本回复里提取「要作为图片发送」的引用,并返回去掉这些引用后的纯文本。\n * 覆盖三种来源:\n * A. Markdown 图片 ``,src 可为 http(s) URL 或本地绝对路径;\n * B. brain 按约定输出的也是上述 Markdown 语法(见 acp-brain 注入的系统提示);\n * C. 回复中裸露的图片 URL(http(s) 且以图片扩展名结尾)。\n */\n\nconst IMG_EXT = /\\.(png|jpe?g|gif|webp|bmp)(\\?[^\\s)]*)?$/i;\nconst MAX_BYTES = 10 * 1024 * 1024;\nconst UA =\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\";\nconst REDIRECT_STATUS = new Set([301, 302, 303, 307, 308]);\nconst MAX_REDIRECTS = 3;\n\nexport interface ExtractedImage {\n src: string;\n}\n\nexport type ImageFailureReason = \"download\" | \"send\" | \"budget\";\n\nexport interface ImageFailure {\n src: string;\n reason: ImageFailureReason;\n}\n\n/** 图片也占一条连发额度;始终给最终文字留最后一条。 */\nexport function canSendImage(sentCount: number, limit: number): boolean {\n return sentCount < limit - 1;\n}\n\nexport type ImageTurnPlan =\n | { kind: \"ok\" }\n | { kind: \"text-only\"; note: string }\n | { kind: \"reject\"; text: string };\n\n/**\n * 收图后怎么走这一回合:\n * - 图片能用 → 正常带图提问;\n * - 图片用不了(全部下载失败 / 大脑不支持)但有文字 → 只按文字回答,并在答复前说明图片被忽略;\n * - 图片用不了且没有文字 → 直接拒绝,不去问大脑(空 prompt 只会浪费一整个回合)。\n */\nexport function planImageTurn(\n imageCount: number,\n downloaded: number,\n supportsImages: boolean,\n hasText: boolean,\n): ImageTurnPlan {\n if (imageCount === 0) return { kind: \"ok\" };\n if (downloaded > 0) {\n if (supportsImages) return { kind: \"ok\" };\n return hasText\n ? { kind: \"text-only\", note: \"(当前大脑不支持看图,已忽略你发的图片,只按文字回答。)\" }\n : {\n kind: \"reject\",\n text: \"🖼️ 当前大脑暂不支持看图,请改用文字描述,或切换到支持图片的大脑。\",\n };\n }\n return hasText\n ? { kind: \"text-only\", note: \"(你发的图片没能下载下来,只按文字回答。)\" }\n : { kind: \"reject\", text: \"🖼️ 图片没能下载下来,请重新发一次。\" };\n}\n\n/** 把发图失败按原因归类成给用户的附注(下载失败、发送失败、额度不足各说各的);没有失败返回空串。 */\nexport function imageFailureNote(failures: ImageFailure[]): string {\n if (failures.length === 0) return \"\";\n const labels: Record<ImageFailureReason, string> = {\n download: \"没能下载下来\",\n send: \"没能发出去\",\n budget: \"因接近微信连续发送上限没发\",\n };\n const order: ImageFailureReason[] = [\"download\", \"send\", \"budget\"];\n return order\n .map((reason) => {\n const items = failures.filter((f) => f.reason === reason).map((f) => f.src);\n return items.length\n ? `(有 ${items.length} 张图片${labels[reason]},附原链接:\\n${items.join(\"\\n\")})`\n : \"\";\n })\n .filter(Boolean)\n .join(\"\\n\\n\");\n}\n\nexport function extractImages(text: string): { images: ExtractedImage[]; text: string } {\n const images: ExtractedImage[] = [];\n const seen = new Set<string>();\n const push = (src: string) => {\n const s = src.trim();\n if (s && !seen.has(s)) {\n seen.add(s);\n images.push({ src: s });\n }\n };\n\n let out = text ?? \"\";\n // A/B:Markdown 图片 \n out = out.replace(/!\\[[^\\]]*\\]\\(\\s*<?([^)\\s>]+)>?(?:\\s+\"[^\"]*\")?\\s*\\)/g, (_m, src: string) => {\n push(src);\n return \"\";\n });\n // C:裸图片 URL(排除 markdown 链接/图片里的,即前面不是 `(`)\n out = out.replace(\n /(?<!\\()(https?:\\/\\/[^\\s)]+\\.(?:png|jpe?g|gif|webp|bmp)(?:\\?[^\\s)]*)?)/gi,\n (m: string) => {\n push(m);\n return \"\";\n },\n );\n\n out = out\n .replace(/[ \\t]+\\n/g, \"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n return { images, text: out };\n}\n\n/**\n * 下载图片:手动跟重定向(最多 3 跳),每一跳都过 `assertPublicHttpUrl`——\n * 不能让一个公网 URL 302 到 `http://127.0.0.1/…` 绕过安全闸。15s 超时对整条链生效。\n */\nasync function downloadImage(url: string): Promise<Response> {\n const signal = AbortSignal.timeout(15000);\n let current = url;\n for (let hop = 0; ; hop++) {\n await assertPublicHttpUrl(current);\n const res = await fetch(current, {\n redirect: \"manual\",\n signal,\n // 不少图床(pixabay 等)对无 UA 的直链返回 403,带上浏览器 UA 规避热链拦截\n headers: { \"User-Agent\": UA },\n });\n if (!REDIRECT_STATUS.has(res.status)) return res;\n const loc = res.headers.get(\"location\");\n if (!loc) return res; // 没有 Location:交给调用方按 !ok 报状态码\n if (hop >= MAX_REDIRECTS) throw new Error(\"图片地址重定向次数过多\");\n current = new URL(loc, current).toString();\n }\n}\n\n/** 把图片引用解析成字节:http(s) 下载,或本地文件读取。带大小/类型校验。 */\nexport async function resolveImageBytes(src: string): Promise<Buffer> {\n if (/^https?:\\/\\//i.test(src)) {\n const res = await downloadImage(src);\n if (!res.ok) throw new Error(`下载图片失败 ${res.status}`);\n const ct = res.headers.get(\"content-type\") || \"\";\n if (!ct.startsWith(\"image/\") && !IMG_EXT.test(src)) throw new Error(`非图片内容(${ct})`);\n const declared = Number(res.headers.get(\"content-length\"));\n if (Number.isFinite(declared) && declared > MAX_BYTES) throw new Error(`图片过大 ${declared}`);\n return readBodyWithLimit(res.body, MAX_BYTES);\n }\n const p = src.startsWith(\"file://\") ? fileURLToPath(src) : src;\n if (!IMG_EXT.test(p)) throw new Error(`非图片文件(仅按图片扩展名发送): ${p}`);\n const stat = fs.statSync(p);\n if (!stat.isFile()) throw new Error(\"不是文件\");\n if (stat.size > MAX_BYTES) throw new Error(`图片过大 ${stat.size}`);\n return fs.readFileSync(p);\n}\n","/**\n * 长回复转 Markdown 文件:超过阈值的回复正文,作为 .md 文件发送(一条消息),比拆成多条文本更不易触发 iLink 连续发送上限、且阅读体验完整。\n * 文件名由内容生成一个合适的标题。\n */\n\n/** 超过此字符数的回复转 .md 文件发送。可用 WECHAT_REPLY_FILE_THRESHOLD 覆盖。 */\nexport const LONG_REPLY_THRESHOLD = Number(process.env.WECHAT_REPLY_FILE_THRESHOLD) || 1500;\n\nconst ILLEGAL_FILENAME = /[\\\\/:*?\"<>|\\r\\n\\t]/g;\n\n/** 由内容生成一个合适的标题:取首个 Markdown 标题或首个非空行,清洗为合法文件名片段。 */\nexport function generateTitle(text: string): string {\n let first = \"\";\n for (const ln of text.split(/\\r?\\n/)) {\n const t = ln.trim();\n if (t) {\n first = t;\n break;\n }\n }\n // 去掉行首的 Markdown 标记(标题井号、列表符、有序号)\n first = first\n .replace(/^#{1,6}\\s*/, \"\")\n .replace(/^[-*+]\\s+/, \"\")\n .replace(/^\\d+[.)]\\s+/, \"\")\n .replace(/[*_`~]/g, \"\");\n first = first.replace(ILLEGAL_FILENAME, \" \").replace(/\\s+/g, \" \").trim();\n if (first.length > 40) first = first.slice(0, 40).trim();\n return first || \"回复\";\n}\n\n/**\n * 构造长回复的 md 文件(不写入磁盘,直接内存 buffer)。\n * 若正文未以 Markdown 标题开头,则前置一个由内容生成的 H1,便于打开后阅读。\n */\nexport function buildMdReply(text: string): { fileName: string; data: Buffer } {\n const title = generateTitle(text);\n const body = /^\\s*#/.test(text) ? text : `# ${title}\\n\\n${text}`;\n return { fileName: `${title}.md`, data: Buffer.from(body, \"utf-8\") };\n}\n","/**\n * 发件人鉴权:登录者(owner,始终允许)或在登录者白名单 allowFrom 中。\n *\n * 这是**兜底校验**:微信平台只把消息投给扫码登录该 bot 的微信号,正常只会收到登录者本人的消息;这道闸防的是发送范围变化/别的入口。\n * `allow` 由调用方注入(daemon 传 core 的 `isAllowed`,测试可传假实现)。\n */\nexport function isAuthorizedSender(\n from: string,\n owner: string,\n allow: (userId: string) => boolean,\n): boolean {\n return !!from && (from === owner || allow(from));\n}\n","import { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport {\n currentSessionIdOf,\n deleteSession,\n ensureEntry,\n labelOf,\n listSessions,\n markUsed,\n renameSession,\n renderSessionList,\n resolveRef,\n validateSessionName,\n} from \"./session-registry.js\";\n\n/**\n * 会话管理的**操作层**:把「新会话 / 列表 / 恢复 / 重命名 / 删除」拼成一句答复。\n *\n * 为什么单独成模块(而不是写在 index.ts 的 handleControl 里):\n * - 恢复是**有副作用的两步**(严格恢复的 load → 成功才改当前会话记录),值得单独钉测试;\n * - 依赖注入(`create`/`resume`)让测试不必起真 ACP 进程——ACP 侧的行为由 acp-switch.test.ts 用假 agent 覆盖,\n * 这里只验「顺序、失败不留痕、文案如实」。\n * 文案里的承诺必须与实现一致:恢复失败**不换当前会话记录**、删除**只删登记**(agent 侧归档不在我们手里)。\n */\n\n/** 恢复会话的实现(daemon 注入 acp-brain 的严格恢复(load);测试注入假实现)。 */\nexport type ResumeFn = (\n userId: string,\n sessionId: string,\n) => Promise<{ ok: true } | { ok: false; problem: string }>;\n\n/** 新建会话的实现(daemon 注入 acp-brain.newSession)。 */\nexport type CreateFn = (userId: string) => Promise<void>;\n\n/** 会话操作的最小上下文:谁、哪个大脑、哪个数据目录。 */\nexport type SessionCtx = {\n /** 当前对话大脑的 agent 名(登记表按 agent 分开存)。 */\n agent: string;\n /** 会话属于谁(微信 userId / CLI 的登录者)。 */\n userId: string;\n dir?: string;\n /** 注入 `now` 只为测试可复现;不传就用真实时间。 */\n now?: number;\n};\n\n/**\n * `新会话 [名字]`:先校验名字(非法就不建——不能因为名字打错白清一次上下文),再新建、再登记命名。\n * 名字登记在**新建出来的那条**上(登记表不存 current,所以按当前会话表读刚写下的 id)。\n */\nexport async function newSessionCmd(\n c: SessionCtx & { name?: string; create: CreateFn },\n): Promise<string> {\n const dir = c.dir ?? DATA_DIR;\n const name = c.name?.trim();\n if (name) {\n const bad = validateSessionName(name);\n if (bad) return `❌ ${bad}`;\n }\n await c.create(c.userId);\n const sid = currentSessionIdOf(c.agent, c.userId, dir);\n if (sid && name)\n ensureEntry(c.agent, c.userId, sid, { name, dir, ...(c.now ? { now: c.now } : {}) });\n return name\n ? `✅ 已开启新会话「${name}」(上下文已清空)。`\n : \"✅ 已开启新会话(上下文已清空)。\";\n}\n\n/** `会话列表 [页]`:本人 + 当前大脑;末尾提示其它大脑另有几条。 */\nexport function sessionsCmd(c: SessionCtx & { page?: number }): string {\n const dir = c.dir ?? DATA_DIR;\n const { entries, others, currentSessionId } = listSessions(c.agent, c.userId, dir);\n return renderSessionList(entries, {\n agent: c.agent,\n page: c.page,\n currentSessionId,\n others,\n now: c.now,\n });\n}\n\n/**\n * `恢复会话 <序号|名字|id前缀>`:ref 先在**当前大脑的本人会话**里解析,再走严格恢复(load)。\n * 成功才 markUsed(并已由 ACP 层改当前会话记录);失败**明确报错、当前会话不变**(绝不静默新建一条顶替)。\n */\nexport async function resumeSessionCmd(\n c: SessionCtx & { resume: ResumeFn },\n ref: string,\n): Promise<string> {\n const dir = c.dir ?? DATA_DIR;\n const { entries, currentSessionId } = listSessions(c.agent, c.userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return `❌ ${hit.error}`;\n if (hit.entry.sessionId === currentSessionId) {\n return `ℹ️ 当前就在这条会话上:${labelOf(hit.entry)}(${hit.entry.turns} 轮)。直接发消息即可。`;\n }\n const r = await c.resume(c.userId, hit.entry.sessionId);\n if (!r.ok) return `❌ ${r.problem}`;\n markUsed(c.agent, c.userId, hit.entry.sessionId, { dir, ...(c.now ? { now: c.now } : {}) });\n return `✅ 已切回会话 ${labelOf(hit.entry)}(${hit.entry.turns} 轮)。接下来发的消息会接上它的上下文。`;\n}\n\n/** `重命名会话 <ref> <名字>`:改名后不会被条数上限淘汰。 */\nexport function renameSessionCmd(c: SessionCtx, ref: string, name: string): string {\n const dir = c.dir ?? DATA_DIR;\n const bad = validateSessionName(name);\n if (bad) return `❌ ${bad}`;\n const { entries } = listSessions(c.agent, c.userId, dir);\n const hit = resolveRef(ref, entries);\n if (!hit.ok) return `❌ ${hit.error}`;\n const before = labelOf(hit.entry);\n const r = renameSession(c.agent, c.userId, ref, name, dir);\n if (!r.ok) return `❌ ${r.error}`;\n return `✅ 已重命名:${before} → 「${name.trim()}」。`;\n}\n\n/**\n * `删除会话 <ref>`:只删登记项;删的若是当前会话则**同时清当前会话记录**(下一条消息自然开新的)。\n * agent 自己那份历史记录不在 WeMind 手里(ACP 无可验证的 session/delete),如实写在答复里。\n */\nexport function deleteSessionCmd(c: SessionCtx, ref: string): string {\n const dir = c.dir ?? DATA_DIR;\n const r = deleteSession(c.agent, c.userId, ref, { dir });\n if (!r.ok) return `❌ ${r.error}`;\n const tail = r.clearedPointer ? \"它就是当前会话,下一条消息会开一个新的。\" : \"当前会话没有变。\";\n return `✅ 已从列表删除:${labelOf(r.entry)}。${tail}\\n(agent 自己那份历史记录不在 WeMind 手里、仍留在它的存储里;这里删掉的是 WeMind 的列表登记。)`;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { ensureKiroV3Permissions, type ResolvedAgent } from \"./brain-agents.js\";\nimport { getAgent } from \"./agent-slot.js\";\nimport {\n readMcpServers,\n withMcpServers,\n parseJsonc,\n toAcpMcpServers,\n isWechatMcpServer,\n} from \"./agent-mcp.js\";\nimport { copyProfilePatch } from \"./profile-model.js\";\nimport { syncInherited } from \"./sandbox-inherit.js\";\n\n/**\n * worker 沙箱:与 brain 沙箱(mcp-sandbox.ts)并列,但 MCP 策略相反。\n *\n * worker 是无人值守的任务执行机,默认**继承真实配置目录的全部 MCP**(仅排除 wechat,防它把消息发回自己),而非 brain 的「默认空 + 对话中按需加载」——因为没人在旁边逐个加载工具,任务需要开箱即用的完整能力。\n * 记忆/技能/规则(mirrorRealKiro)与 settings(syncSettings,含模型/信任)的继承与 brain 一致。\n *\n * 按 agent 适配(同 mcp-sandbox.ts):配置目录、MCP 格式、设置继承方式均随 agent 而变。\n * agent 不再存成模块常量:函数显式收 `ag`,入口用 `getAgent(\"worker\")`(默认任务大脑)。\n *\n * 设计依据:docs/方案C-MVP-远程human-in-the-loop.md §8.4。\n * 与 mcp-sandbox.ts 有少量重复(mirrorRealKiro/syncSettings),刻意复制以隔离演进、不动已稳定的 brain 沙箱。\n */\n\n/** worker 专用沙箱根(由 agent 的 homeEnvVar 指向它)。与 agent 无关。 */\nexport const WORKER_HOME =\n process.env.WEMIND_WORKER_HOME?.trim() || path.join(DATA_DIR, \"worker-home\");\n\n/** 该 agent 在 worker 沙箱里的「配置目录」(kiro=沙箱根;opencode=<沙箱>/opencode)。 */\nfunction workerConfigDir(ag: ResolvedAgent): string {\n return path.join(WORKER_HOME, ag.configSubdir);\n}\n\nfunction workerMcpJson(ag: ResolvedAgent): string {\n return path.join(workerConfigDir(ag), ag.mcpConfigFile);\n}\n\n/** MCP「目录」:可继承的 MCP 定义来源(同 brain,默认取 agent 真实配置目录的 catalogFile)。 */\nfunction catalogPath(ag: ResolvedAgent): string {\n return (\n process.env.WEMIND_MCP_CATALOG?.trim() ||\n process.env.WECHAT_MCP_CATALOG?.trim() ||\n path.join(ag.realHome || path.join(os.homedir(), \".kiro\"), ag.catalogFile)\n );\n}\n\n/** worker 不继承的 MCP:wechat 始终强制排除(防它把消息发回自己),外加 WEMIND_WORKER_MCP_EXCLUDE 指定的。 */\nfunction excludeSet(): Set<string> {\n const raw = process.env.WEMIND_WORKER_MCP_EXCLUDE ?? \"wechat\";\n const s = new Set(\n raw\n .split(/[,\\s]+/)\n .map((x) => x.trim())\n .filter(Boolean),\n );\n s.add(\"wechat\"); // 强制:worker 加载 wechat 会用同一 bot token 长轮询、与 daemon 抢消息(坑①回环)\n return s;\n}\n\n/** 宽松读取(JSONC):Gemini 的 settings.json 允许注释,严格 JSON.parse 会把合法配置当损坏。 */\nfunction readJson(file: string): Record<string, unknown> | null {\n try {\n return parseJsonc(fs.readFileSync(file, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n/** 同 brain:把凭据文件(如 DSH 的 `.credentials.yaml`、Qoder 的 `.auth/user`)同步进 worker 沙箱。\n * `newer`=新者胜;`always`=内容不同即覆盖(标识类文件必须与真实目录一致,见 mcp-sandbox.ts)。 */\nfunction syncCredentialFile(ag: ResolvedAgent, file: string, mode: \"newer\" | \"always\"): void {\n if (!ag.realHome) return;\n const src = path.join(ag.realHome, file);\n const dst = path.join(workerConfigDir(ag), file);\n try {\n if (!fs.existsSync(src)) return;\n fs.mkdirSync(path.dirname(dst), { recursive: true });\n if (mode === \"always\") {\n const same = fs.existsSync(dst) && fs.readFileSync(src).equals(fs.readFileSync(dst));\n if (!same) fs.copyFileSync(src, dst);\n return;\n }\n const s = fs.statSync(src);\n const d = fs.existsSync(dst) ? fs.statSync(dst) : null;\n if (!d || s.mtimeMs > d.mtimeMs + 1000) fs.copyFileSync(src, dst);\n } catch {\n /* best-effort */\n }\n}\n\nfunction syncCredentials(ag: ResolvedAgent): void {\n for (const f of ag.credentialFiles) syncCredentialFile(ag, f, \"newer\");\n for (const f of ag.identityFiles) syncCredentialFile(ag, f, \"always\");\n}\n\n/** 把预设的默认设置项补进沙箱 settings.json:只补缺失键(用户显式设过就保留用户的值),真实配置不动。 */\nfunction applySettingsDefaults(ag: ResolvedAgent): void {\n if (Object.keys(ag.settingsDefaults).length === 0) return;\n const file = path.join(workerConfigDir(ag), \"settings.json\");\n const cur = readJson(file) ?? {};\n let changed = false;\n for (const [k, v] of Object.entries(ag.settingsDefaults)) {\n if (cur[k] === undefined) {\n cur[k] = v;\n changed = true;\n }\n }\n if (!changed) return;\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(cur, null, 2), \"utf-8\");\n } catch {\n /* best-effort */\n }\n}\n\nfunction loadCatalog(ag: ResolvedAgent): Record<string, unknown> {\n return readMcpServers(readJson(catalogPath(ag)), ag.mcpFormat);\n}\n\n/** worker 继承全量 MCP:复制 catalog 全部 server,去掉排除集与 disabled:true 的。\n * 配了 `mcpViaAcp` 的 agent(DSH)不写文件——全量集合经 workerMcpServers() 转成 ACP mcpServers 随 session 下发。 */\nfunction applyWorkerSandbox(ag: ResolvedAgent): void {\n if (ag.mcpViaAcp) return;\n const cat = loadCatalog(ag);\n const ex = excludeSet();\n const servers: Record<string, unknown> = {};\n for (const [name, def] of Object.entries(cat)) {\n if (ex.has(name) || isWechatMcpServer(def)) continue; // 名字或定义命中微信 MCP 都排除(防改名绕开)\n if (def && typeof def === \"object\" && (def as { disabled?: unknown }).disabled === true)\n continue; // 跳过用户已禁用的\n servers[name] = def;\n }\n const file = workerMcpJson(ag);\n const existing = readJson(file) ?? {};\n const next = withMcpServers(existing, servers, ag.mcpFormat);\n // opencode 的免审批只能走配置(`opencode acp` 不接受 --auto):默认 \"allow\",用户自己配了 permission 则以用户的为准。\n if (ag.mcpFormat === \"opencode\" && next.permission === undefined) next.permission = \"allow\";\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(next, null, 2), \"utf-8\");\n}\n\n/** worker 当前应挂载的 ACP mcpServers(配了 mcpViaAcp 的 agent 用):全量 catalog(除排除集/disabled/坏条目)→ ACP 声明。 */\nexport function workerMcpServers(forAgent?: ResolvedAgent) {\n const ag = forAgent ?? getAgent(\"worker\");\n const cat = loadCatalog(ag);\n const ex = excludeSet();\n const picked: Record<string, unknown> = {};\n for (const [name, def] of Object.entries(cat)) {\n if (ex.has(name) || isWechatMcpServer(def)) continue;\n if (def && typeof def === \"object\" && (def as { disabled?: unknown }).disabled === true)\n continue;\n picked[name] = def;\n }\n const { servers, skipped } = toAcpMcpServers(picked, { exclude: ex });\n if (skipped.length)\n console.error(`[wemind/wechat-daemon/mcp] worker 跳过无法转换的 MCP:${skipped.join(\", \")}`);\n return servers;\n}\n\n/** kiro:继承真实 settings/ 下除 mcp.json 外的文件(cli.json 含模型/免确认等)。逻辑同 brain 的 syncKiroSettings。 */\nfunction syncKiroSettings(ag: ResolvedAgent): void {\n const realSettings = path.join(ag.realHome!, \"settings\");\n const workerSettings = path.join(workerConfigDir(ag), \"settings\");\n try {\n fs.mkdirSync(workerSettings, { recursive: true });\n } catch {\n /* ignore */\n }\n let names: string[] = [];\n try {\n names = fs.readdirSync(realSettings);\n } catch {\n /* 真实配置目录不存在:v3 仍需要沙箱里的默认 permissions.yaml */\n }\n for (const f of names) {\n if (f === \"mcp.json\") continue; // MCP 用 worker curated 版本(applyWorkerSandbox 全量除排除集)\n try {\n const src = path.join(realSettings, f);\n if (fs.statSync(src).isFile()) fs.copyFileSync(src, path.join(workerSettings, f));\n } catch {\n /* best-effort */\n }\n }\n ensureKiroV3Permissions(ag, workerSettings);\n}\n\n/** opencode:把用户全局 opencode.json(c)(去 mcp 段)作基底写入 worker 沙箱,继承 model 等;mcp 由 applyWorkerSandbox 覆盖。 */\nfunction syncOpencodeBase(ag: ResolvedAgent): void {\n const src = [\"opencode.json\", \"opencode.jsonc\"]\n .map((f) => path.join(ag.realHome!, f))\n .find((f) => fs.existsSync(f));\n if (!src) return;\n let parsed: Record<string, unknown> | null = null;\n try {\n const raw = fs.readFileSync(src, \"utf-8\");\n parsed = src.endsWith(\".jsonc\")\n ? parseJsonc(raw)\n : (JSON.parse(raw) as Record<string, unknown>);\n } catch {\n return;\n }\n if (!parsed || typeof parsed !== \"object\") return;\n delete (parsed as Record<string, unknown>).mcp;\n const file = workerMcpJson(ag);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(parsed, null, 2), \"utf-8\");\n}\n\n/** 「设置与 MCP 同文件」的 agent:与 brain 相同的基底疗法(按 `ag.settingsStrip` 剥离 + 关自动更新)。 */\nfunction settingsBase(ag: ResolvedAgent): Record<string, unknown> {\n let parsed: unknown = null;\n try {\n parsed = parseJsonc(fs.readFileSync(path.join(ag.realHome!, ag.settingsBase!), \"utf-8\"));\n } catch {\n /* 缺失/损坏 → 空基底 */\n }\n const base =\n parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? { ...(parsed as Record<string, unknown>) }\n : {};\n for (const k of ag.settingsStrip) delete base[k];\n const general = base.general;\n base.general = {\n ...(general && typeof general === \"object\" && !Array.isArray(general)\n ? (general as Record<string, unknown>)\n : {}),\n enableAutoUpdate: false,\n };\n return base;\n}\n\nfunction syncSettingsBase(ag: ResolvedAgent): void {\n const dir = workerConfigDir(ag);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(workerMcpJson(ag), JSON.stringify(settingsBase(ag), null, 2), \"utf-8\");\n}\n\n/** 继承真实 profile 的用户层(DSH:`profiles/<名>/cordis.patch.yml`,桌面端写 provider 目录与默认模型),**原样复制**。\n * 逻辑同 brain 的 syncProfilePatch:WeMind 只镜像用户配置、不合成(`- id: acp` 由用户自己维护,缺失时 CLI 提示怎么补);\n * **唯独剔除用户自己挂的微信 MCP 条目**(worker 的 MCP 也永远排除 `wechat`,防抢同一个 bot token)。 */\nfunction syncProfilePatch(ag: ResolvedAgent): void {\n if (!ag.realProfile || !ag.realHome || !ag.inherit) return;\n if (path.resolve(WORKER_HOME) === path.resolve(ag.realHome)) return;\n const src = path.join(ag.realHome, \"profiles\", ag.realProfile, \"cordis.patch.yml\");\n try {\n if (!fs.existsSync(src)) return;\n const removed = copyProfilePatch(ag.realHome, ag.realProfile, WORKER_HOME);\n if (removed.length) {\n console.error(\n `[wemind/wechat-daemon/mcp] 真实 profile 里的微信 MCP 已从 worker 沙箱副本剔除(防回环,真实文件未改):${removed.join(\", \")}`,\n );\n }\n } catch {\n /* best-effort */\n }\n}\n\nfunction syncSettings(ag: ResolvedAgent): void {\n syncProfilePatch(ag); // DSH 等:继承真实 profile 用户层(provider 目录 / 默认模型);MCP 仍走 ACP 注入\n if (ag.mcpViaAcp) return; // 无「配置文件式」的 MCP/settings 可继承(模型走 set_config_option)\n if (ag.settingsBase && ag.realHome) {\n syncSettingsBase(ag); // 不依赖 inherit:沙箱硬化(关自动更新)与隔离始终生效\n return;\n }\n if (!ag.inherit || !ag.realHome) return;\n if (ag.mcpFormat === \"opencode\") syncOpencodeBase(ag);\n else syncKiroSettings(ag);\n}\n\n/** 用 junction/symlink 把真实配置目录的记忆/技能/规则类子目录链入 worker 配置目录;单文件记忆直接拷。逻辑同 brain(共用 `sandbox-inherit.ts`)。 */\nfunction mirrorRealKiro(ag: ResolvedAgent): void {\n syncInherited(WORKER_HOME, workerConfigDir(ag), ag);\n}\n\n/**\n * 确保 worker 沙箱就绪:继承 settings(除 mcp)→ 写全量 MCP(除排除集)→ 链入记忆/技能 → 凭据 → 预设默认设置项。\n * daemon 启动时按默认任务大脑调用一次;**每次派发新 worker 前**(`worker-pool.ts` 的 `acquire`)再按**这条任务的 agent** 调一次——\n * 否则派到非默认 agent(如 `--agent pi`)时,它自己的 settings/凭据/MCP 还没进沙箱,agent 会拿空配置跑。\n * `forAgent` 可显式指定 agent(此刻槽位可能指着别的默认)。\n */\nexport function ensureWorkerHome(forAgent?: ResolvedAgent): void {\n const ag = forAgent ?? getAgent(\"worker\");\n syncSettings(ag); // 先同步「非 MCP」设置(模型/免确认等)\n applyWorkerSandbox(ag); // 再写全量 MCP(除 wechat 等排除集)\n mirrorRealKiro(ag); // 链入记忆/steering/skills(sessions 仍隔离)\n syncCredentials(ag); // 凭据(如 DSH 的 .credentials.yaml、Qoder 的 .auth/*)\n applySettingsDefaults(ag); // 预设默认设置项(如 pi 的 quietStartup:关掉 pi-acp 当消息发出来的启动横幅)\n}\n","import { spawnSync, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { WORKER_HOME, workerMcpServers } from \"./worker-sandbox.js\";\nimport { spawnAgent } from \"./spawn-agent.js\";\nimport {\n backupCorruptSessionMap,\n normalizeSessionMap,\n readSessionMapStrict,\n sessionKey,\n} from \"./session-map.js\";\nimport { acpArgsHint, agentEnv, prepareAgent, type ResolvedAgent } from \"./brain-agents.js\";\nimport {\n applyAcpModel,\n applyAcpSessionMode,\n availableAcpModes,\n acpTurnChunks,\n acpTurnText,\n beginAcpTurn,\n clearAcpTurn,\n drainAcpReplay,\n endAcpTurn,\n feedAcpStdout,\n handleAcpFrame,\n newTurnState,\n promptAcp,\n requestAcp,\n type AcpHost,\n type AcpPending,\n type AcpProgressEvent,\n} from \"./acp-connection.js\";\nimport { LineDecoder } from \"./line-decoder.js\";\n\n/**\n * worker ACP 连接:**一条连接=一个 ACP agent 进程**;协议细节(帧处理/请求与回合超时/模型与模式设置/回放静默)与 brain 共用 `acp-connection.ts`,本模块只保留任务侧差异:\n * - **每连接一实例**(child/pending/session/进度全在实例里,不再是模块级全局)——多任务并行要有多个进程;\n * 进程池(worker-pool.ts)持有它们、按 agent 复用,任务结束把连接**还给进程池**而不是杀掉;\n * - **每任务一个 session key**(`task-<编号>@<userId>`),持久化到 worker-sessions.json,重建后 session/load 续上下文;\n * - **cwd = 任务工作目录**(本地派发可指定,默认用户主目录),而沙箱走 WORKER_HOME(继承全量 MCP 除 wechat + 记忆);\n * - **更宽松超时**(漫长任务,env 可调);去掉 brain 的「可发图」首轮提示(任务执行不是聊天发图场景)。\n * 自愈:session/prompt 超时/失败 → 只杀**这一条**连接的进程;下次 ask 重建并 session/load 恢复(→ 用户回「继续」即可续跑)。\n */\n\nfunction dlog(...a: unknown[]): void {\n console.error(\"[wemind/wechat-daemon/worker]\", ...a);\n}\n\nconst WORKER_CWD = process.env.WEMIND_WORKER_CWD?.trim() || os.homedir(); // 任务工作目录默认值\n\n/** 实际生效的任务工作目录:显式 cwd 优先,否则 `WEMIND_WORKER_CWD` / 用户主目录。 */\nexport function effectiveWorkerCwd(cwd?: string): string {\n return cwd?.trim() || WORKER_CWD;\n}\nconst REQ_TIMEOUT = 30_000; // initialize / session/new / session/load\nconst PROMPT_IDLE_MS = Number(process.env.WEMIND_WORKER_IDLE_MS) || 180_000; // 无任何帧多久算\"卡死\"(比 brain 宽松:3min)\nconst PROMPT_CAP_MS = Number(process.env.WEMIND_WORKER_CAP_MS) || 1_800_000; // 单轮硬上限 30min(漫长任务)\nconst FIRST_FRAME_DEFAULT_MS = 30_000;\nconst SESSION_MAP_FILE = path.join(DATA_DIR, \"worker-sessions.json\");\n\n/** 首帧超时(配置可覆盖;每轮现算,热切换/指定 agent 后随之改变)。 */\nfunction firstFrameMs(a: ResolvedAgent): number {\n return (\n Number(process.env.WEMIND_WORKER_FIRST_FRAME_MS) || a.firstFrameMs || FIRST_FRAME_DEFAULT_MS\n );\n}\n\nexport type WorkerProgressEvent = AcpProgressEvent;\n\nlet connSeq = 0;\n\n// ---- 磁盘 session 映射(`<agent>:<sessionKey>` -> sessionId),与 brain 同样按 agent 分开存 ----\nfunction loadSessionMap(a: ResolvedAgent): Record<string, string> {\n let raw: Record<string, string>;\n try {\n raw = readSessionMapStrict(SESSION_MAP_FILE);\n } catch (e) {\n const backup = backupCorruptSessionMap(SESSION_MAP_FILE);\n dlog(\n `session map unreadable (${String(e)}) → ${backup ? `backed up to ${backup}` : \"backup failed\"};按空表继续`,\n );\n return {};\n }\n const { map, migrated } = normalizeSessionMap(raw, a.name);\n if (migrated) saveSessionMap(map); // 立即写入磁盘,避免下次换 agent 时旧条目再被“继承”一次\n return map;\n}\nfunction saveSessionMap(m: Record<string, string>): void {\n fs.mkdirSync(DATA_DIR, { recursive: true });\n const tmp = `${SESSION_MAP_FILE}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(m, null, 2), \"utf-8\");\n fs.renameSync(tmp, SESSION_MAP_FILE); // 原子写:多连接并发时会各自读改写这张表,半截文件会让读方整张丢\n}\n\n/** 一条连接:一个 ACP agent 进程 + 它自己的握手/会话/进度状态。 */\nexport class WorkerConn {\n readonly id: string;\n readonly agent: ResolvedAgent;\n /** 池用来标注「这条连接现在跑的是哪个任务」(空闲时为 undefined)。 */\n taskId?: number;\n\n private child: ChildProcessWithoutNullStreams | null = null;\n private initialized = false;\n private idCounter = 0;\n private pending = new Map<number, AcpPending>();\n private sessions = new Map<string, string>(); // sessionKey -> sessionId\n private turn = newTurnState(); // 当前回合的拼接缓冲与进度状态\n private rpcState = { lastFrameAt: 0, frameSeq: 0 }; // 最近收帧时间 + 累计序号\n private stdoutLines = new LineDecoder();\n private host: AcpHost;\n private cwd?: string; // 本任务的工作目录(spawn 时生效;子进程 cwd 不可中途改)\n private spawnError?: Error; // spawn 失败的真实原因(cwd 不存在/权限),kill() 用它答复而不是笼统的 killed\n /** spawn 后立刻回调:池据此把 pid 身份记录写入磁盘(`wemind stop` 靠它精确找到本进程)。 */\n onSpawn?: (conn: WorkerConn) => void;\n\n constructor(agent: ResolvedAgent) {\n this.agent = agent;\n this.id = `w${++connSeq}`;\n this.host = {\n pending: this.pending,\n turn: this.turn,\n state: this.rpcState,\n hasProcess: () => !!this.child,\n noProcessMessage: \"no worker process\",\n nextId: () => ++this.idCounter,\n write: (payload) => this.write(JSON.stringify(payload)),\n log: (message) => dlog(`${this.id}(${this.agent.name}) ${message}`),\n };\n }\n\n isAlive(): boolean {\n return !!this.child && this.child.exitCode === null && !this.child.killed;\n }\n\n pid(): number | null {\n return this.child?.pid ?? null;\n }\n\n /** 设置本任务的工作目录(下次 spawn 生效)。 */\n setCwd(cwd?: string): void {\n this.cwd = cwd?.trim() || undefined;\n }\n\n effectiveCwd(): string {\n return effectiveWorkerCwd(this.cwd);\n }\n\n hasSession(key: string): boolean {\n return this.sessions.has(key);\n }\n\n /** 杀掉本连接的进程(按 PID 进程树)并清空内存态;不清磁盘 session 映射(留给重建后 load)。 */\n kill(): void {\n const c = this.child;\n this.child = null;\n this.initialized = false;\n if (c?.pid) {\n try {\n if (process.platform === \"win32\")\n spawnSync(\"taskkill\", [\"/PID\", String(c.pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n else c.kill();\n } catch {\n /* ignore */\n }\n }\n const reason = this.spawnError\n ? `进程启动失败:${this.spawnError.message}`\n : \"worker conn killed\";\n for (const p of this.pending.values()) {\n p.clear();\n p.reject(new Error(reason));\n }\n this.pending.clear();\n this.sessions.clear();\n clearAcpTurn(this.turn);\n this.stdoutLines.reset();\n dlog(`${this.id}(${this.agent.name}) killed`);\n }\n\n /** 丢弃某个会话(内存 + 磁盘上的当前会话记录):任务结束用,下个任务全新开始。 */\n dropSession(key: string): void {\n this.sessions.delete(key);\n const map = loadSessionMap(this.agent);\n delete map[sessionKey(this.agent.name, key)];\n saveSessionMap(map);\n }\n\n /** 提前起进程:只做「起进程 + initialize」,不建会话(任务拿到连接时用;失败由调用方处理)。 */\n async warm(): Promise<void> {\n await this.ensureProcess();\n }\n\n /**\n * 发一轮 prompt(任务派发或回话续跑),等这一轮 ACP 响应结束,返回拼接文本。\n * 失败即杀本连接的进程(下一轮 ask 重建并 session/load 恢复上下文)→ 配合「继续」实现中断恢复。\n * `strictResume`:磁盘上有旧会话、但 load/resume 都失败时**不静默新建**,直接抛错(续跑场景要的是「上下文接上」,\n * 悄悄换一个空会话会让用户以为续上了、其实什么都不知道)。\n */\n async ask(\n instruction: string,\n key: string,\n onProgress?: (ev: WorkerProgressEvent) => void,\n opts: { strictResume?: boolean } = {},\n ): Promise<string> {\n await this.ensureProcess();\n const sid = await this.ensureSession(key, opts.strictResume === true);\n beginAcpTurn(this.turn, sid, onProgress);\n const t0 = Date.now();\n const prompt: unknown[] = [{ type: \"text\", text: instruction || \"(继续)\" }];\n try {\n await promptAcp(\n this.host,\n { sessionId: sid, prompt },\n {\n idleMs: PROMPT_IDLE_MS,\n capMs: PROMPT_CAP_MS,\n firstFrameMs: firstFrameMs(this.agent),\n },\n );\n const txt = acpTurnText(this.turn, sid);\n dlog(\n `${this.id}(${this.agent.name}) prompt ok ${Date.now() - t0}ms chunks=${acpTurnChunks(this.turn, sid)} updateTypes=[${[...this.turn.seenUpdateTypes].join(\",\")}]`,\n );\n return txt;\n } catch (e) {\n const chunks = acpTurnChunks(this.turn, sid);\n const sinceFrame = this.rpcState.lastFrameAt ? Date.now() - this.rpcState.lastFrameAt : -1;\n dlog(\n `${this.id}(${this.agent.name}) prompt FAIL ${Date.now() - t0}ms chunks=${chunks} lastFrameAgo=${sinceFrame}ms err=${String(e)} -> rebuild`,\n );\n this.kill();\n throw e;\n } finally {\n endAcpTurn(this.turn, sid);\n }\n }\n\n private write(line: string): void {\n this.child?.stdin.write(line + \"\\n\");\n }\n\n private async ensureProcess(): Promise<void> {\n if (this.isAlive() && this.initialized) return;\n this.kill();\n this.spawnError = undefined; // 上一次的 spawn 失败不能污染这一次\n const kept: string[] = [];\n const env = agentEnv(this.agent, WORKER_HOME, kept); // 父进程 env → 沙箱目录 → 预设 env → 预设 envDefaults(父进程已设的不覆盖)\n if (kept.length) dlog(`${this.id} env defaults kept from parent: ${kept.join(\",\")}`);\n prepareAgent(this.agent); // 启动前落盘 agent 需要的派生配置(DSH 的 session-root patch)\n const cwd = this.effectiveCwd();\n const proc = spawnAgent(this.agent, {\n cwd, // 任务工作目录(本地派发可指定,否则默认 WORKER_CWD),区别于沙箱\n env,\n });\n this.child = proc;\n dlog(\n `${this.id} spawn ${this.agent.name} worker pid=${proc.pid} cwd=${cwd} home=${WORKER_HOME} task=${this.taskId ?? \"-\"}`,\n );\n this.onSpawn?.(this); // 立刻落 pid 身份记录(在握手之前,`wemind stop` 才来得及找到它)\n proc.stdout.on(\"data\", (d) =>\n feedAcpStdout(this.stdoutLines, d, (f) => handleAcpFrame(f, this.host)),\n );\n proc.stderr.on(\"data\", () => {\n /* ignore */\n });\n proc.on(\"exit\", (code) => {\n // A:握手前就退出时附上 args 诊断(与 brain 侧同一提示,见 brain-agents.ts 的 acpArgsHint)\n const hint = this.child === proc && !this.initialized ? acpArgsHint(this.agent) : \"\";\n dlog(`${this.id}(${this.agent.name}) exited code=${code}${hint}`);\n if (this.child === proc) this.kill();\n });\n proc.on(\"error\", (e) => {\n this.spawnError = e instanceof Error ? e : new Error(String(e));\n dlog(`${this.id}(${this.agent.name}) error ${String(e)}`);\n if (this.child === proc) this.kill();\n });\n try {\n await requestAcp(\n this.host,\n \"initialize\",\n { protocolVersion: 1, clientCapabilities: {} },\n REQ_TIMEOUT,\n );\n } catch (e) {\n // spawn 失败(cwd 不存在/权限)不是「args 漏了 acp」,别贴那条误导性提示\n const hint = this.spawnError ? \"\" : acpArgsHint(this.agent);\n dlog(`${this.id}(${this.agent.name}) initialize failed: ${String(e)}${hint}`);\n throw e;\n }\n this.initialized = true;\n dlog(`${this.id} initialized`);\n }\n\n /** 取会话:内存 → 磁盘(load/resume 恢复) → 新建。key 由调用方给(每任务一个)。\n * `strictResume`:有旧会话但恢复失败 → 抛「接不上」,绝不静默新建空会话。 */\n private async ensureSession(key: string, strictResume = false): Promise<string> {\n const existing = this.sessions.get(key);\n if (existing) return existing;\n // 配了 mcpViaAcp 的 agent(DSH):worker 继承全量 MCP(除排除集),随 session 下发;其余 agent 传空\n const mcpServers = this.agent.mcpViaAcp ? workerMcpServers(this.agent) : [];\n const mapKey = sessionKey(this.agent.name, key);\n const map = loadSessionMap(this.agent);\n const saved = map[mapKey];\n if (saved) {\n const methods: Array<\"load\" | \"resume\"> =\n this.agent.restore === \"resume\" ? [\"resume\"] : [\"load\", \"resume\"];\n for (const method of methods) {\n try {\n const restored = await requestAcp(\n this.host,\n `session/${method}`,\n { sessionId: saved, cwd: this.effectiveCwd(), mcpServers },\n REQ_TIMEOUT,\n );\n await drainAcpReplay(this.host, this.agent);\n await applyAcpModel(this.host, this.agent, saved, REQ_TIMEOUT);\n await applyAcpSessionMode(\n this.host,\n this.agent,\n saved,\n availableAcpModes(restored),\n REQ_TIMEOUT,\n );\n this.sessions.set(key, saved);\n dlog(\n `${this.id} session/${method} ok ${saved} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`,\n );\n return saved;\n } catch (e) {\n dlog(`${this.id} session/${method} failed (${String(e)})`);\n }\n }\n if (strictResume)\n throw new Error(\n `接不上原会话 ${saved}(该 agent 不支持恢复、或这条会话已被它自己清理)。重试还是同一个结果——想重来请结束这条任务后重新派一条(把进展写进新任务里)。`,\n );\n }\n const res = (await requestAcp(\n this.host,\n \"session/new\",\n { cwd: this.effectiveCwd(), mcpServers },\n REQ_TIMEOUT,\n )) as { sessionId?: string };\n if (!res?.sessionId) throw new Error(\"session/new returned no sessionId\");\n await applyAcpModel(this.host, this.agent, res.sessionId, REQ_TIMEOUT);\n await applyAcpSessionMode(\n this.host,\n this.agent,\n res.sessionId,\n availableAcpModes(res),\n REQ_TIMEOUT,\n );\n this.sessions.set(key, res.sessionId);\n // 重新读改写:多个连接并存时,别用陈旧的记录整张覆盖(会抹掉别的任务刚写的 key)\n const fresh = loadSessionMap(this.agent);\n fresh[mapKey] = res.sessionId;\n saveSessionMap(fresh);\n dlog(\n `${this.id} session/new ok ${res.sessionId} mcp=[${mcpServers.map((s: any) => s.name).join(\",\")}]`,\n );\n return res.sessionId;\n }\n}\n\n/** 建一条连接(不 spawn;第一次 `warm()`/`ask()` 才起进程)。 */\nexport function createConn(agent: ResolvedAgent, onSpawn?: (c: WorkerConn) => void): WorkerConn {\n const c = new WorkerConn(agent);\n c.onSpawn = onSpawn;\n return c;\n}\n","import path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\nimport { createConn, effectiveWorkerCwd, type WorkerConn } from \"./worker-acp.js\";\nimport { pidRecordOf, writePidRecords, type PidRecord, type PidState } from \"./pid-record.js\";\nimport type { ResolvedAgent } from \"./brain-agents.js\";\nimport { ensureWorkerHome } from \"./worker-sandbox.js\";\n\n/**\n * worker 进程池:**一条任务一条 ACP 连接**,按 agent 复用常驻进程。\n *\n * 为什么需要池:多任务并行要求「a 大脑做任务1、b 大脑做任务2」,而一个 agent 进程同一时刻只跑一条会话;\n * 同时每个 agent 进程都是一个完整的 agent(内存/冷启动都不便宜),任务一结束就杀掉会让「紧接着派下一个任务」反复付冷启动。\n * 于是任务结束只是把连接**还给进程池**:同 agent 的下一个任务直接复用,空闲久了才回收。\n *\n * 策略(`planAcquire`/`planReap` 是纯函数,可单独测——IO 与判定分离):\n * - 同 agent + 同 cwd 的空闲连接优先复用(**最旧的先用**:它本来就最接近被回收,先让它干活正好续命);cwd 不同必须换进程(子进程 cwd 不能中途改);\n * - 没有可复用的就 spawn;\n * - 同时运行的**任务**数上限 `WEMIND_MAX_WORKERS`(默认 3):超了**直接拒绝派发**(用户当场知道),不排队(排队会让任务悄悄等着);\n * - 空闲连接数上限 `WEMIND_IDLE_MAX`(默认 2)、空闲超 `WEMIND_WORKER_REAP_MS`(默认 5min)回收,都从最旧的开始;\n * - busy 连接永不回收(正在跑任务的进程,杀了就是丢任务)。\n *\n * 与 `WEMIND_WORKER_IDLE_MS` 的区别:那个是「单轮 prompt 多久没有帧算卡死」(既有语义,见 worker-acp.ts),\n * 这里是「空闲**连接**留多久」,两个时间尺度不同,故不复用同一个变量名。\n *\n * 同一 agent 并发 = 两个进程共用一个配置目录(等价「同一个 agent 开两个终端」):不同 agent 各有自己的子目录天然隔离,\n * 同 agent 的会话/缓存文件已有一轮并发样本(2026-10-29,opencode 两条),未见互踩;长期风险仍在(见方案「待实测」5/6)。\n */\n\nexport const WORKERS_PID_FILE = path.join(DATA_DIR, \"workers.pid.json\");\n\nexport type PoolLimits = {\n /** 同时运行的任务数上限(每个运行中的任务占一个连接/进程)。 */\n maxConcurrent: number;\n /** 空闲连接数上限。 */\n idleMax: number;\n /** 空闲多久回收。 */\n idleMs: number;\n};\n\nexport function poolLimits(env: NodeJS.ProcessEnv = process.env): PoolLimits {\n const n = (v: string | undefined, d: number) => (Number(v) > 0 ? Number(v) : d);\n return {\n maxConcurrent: n(env.WEMIND_MAX_WORKERS, 3),\n idleMax: n(env.WEMIND_IDLE_MAX, 2),\n idleMs: n(env.WEMIND_WORKER_REAP_MS, 300_000),\n };\n}\n\n/** 连接的取用判定只需这些字段(纯函数入参,与真实 conn 解耦)。 */\nexport type ConnInfo = {\n id: string;\n agent: string;\n /** agent 身份键(名字+bin+args):不同的启动参数 = 不同的进程,不能互相复用。 */\n key: string;\n state: PidState;\n lastUsedAt: number;\n taskId?: number;\n};\n\nexport type AcquirePlan =\n | { kind: \"reuse\"; connId: string; reap: string[] }\n | { kind: \"spawn\"; reap: string[] }\n | { kind: \"reject\"; problem: string };\n\n/** agent 身份键:同一个 agent 但启动参数不同(如 `--bin` 改过)必须算两个进程。 */\nexport function agentKey(ag: ResolvedAgent): string {\n return `${ag.name}|${ag.bin}|${ag.args.join(\" \")}`;\n}\n\n/**\n * 连接身份键:agent 身份 + **实际生效的任务工作目录**。\n * 子进程的 cwd 不能中途改,所以换 `--cwd` 必须换进程;不把 cwd 纳入身份就会复用旧进程、让它在上一个项目里干活。\n */\nexport function connKey(ag: ResolvedAgent, cwd?: string): string {\n return `${agentKey(ag)}|cwd=${effectiveWorkerCwd(cwd)}`;\n}\n\n/**\n * 决定「这条任务用哪条连接」。`reap` 里的是顺带该回收的空闲连接(调用方先复用/起新进程,再按它杀)。\n * 被复用的那条不会出现在 `reap` 里——哪怕它是过期的(复用比「先杀再起」便宜)。\n */\nexport function planAcquire(\n conns: ConnInfo[],\n key: string,\n limits: PoolLimits,\n now: number,\n): AcquirePlan {\n const busy = conns.filter((c) => c.state === \"busy\").length;\n if (busy >= limits.maxConcurrent)\n return {\n kind: \"reject\",\n problem: `同时运行的任务数已达上限(${limits.maxConcurrent} 个)。等一个跑完,或先「结束任务 <编号>」收掉不再需要的。`,\n };\n const reusable = conns\n .filter((c) => c.state === \"idle\" && c.key === key)\n .sort((a, b) => a.lastUsedAt - b.lastUsedAt || a.id.localeCompare(b.id))[0];\n if (reusable)\n return { kind: \"reuse\", connId: reusable.id, reap: planReap(conns, limits, now, reusable.id) };\n return { kind: \"spawn\", reap: planReap(conns, limits, now) };\n}\n\n/**\n * 该回收哪些空闲连接:① 空闲超时;② 回收后仍超过 idleMax 的部分(从最旧的开始)。\n * `keep` 是「刚用上的那条」——哪怕它最旧也不回收(收掉正在干活的东西是纯亏)。\n */\nexport function planReap(\n conns: ConnInfo[],\n limits: PoolLimits,\n now: number,\n keep?: string,\n): string[] {\n const idle = conns\n .filter((c) => c.state === \"idle\")\n .sort((a, b) => a.lastUsedAt - b.lastUsedAt || a.id.localeCompare(b.id));\n const reaped = new Set<string>();\n for (const c of idle) {\n if (c.id !== keep && now - c.lastUsedAt > limits.idleMs) reaped.add(c.id);\n }\n let excess = idle.filter((c) => !reaped.has(c.id)).length - limits.idleMax;\n for (const c of idle) {\n if (excess <= 0) break;\n if (c.id === keep || reaped.has(c.id)) continue;\n reaped.add(c.id);\n excess -= 1;\n }\n return [...reaped];\n}\n\ntype Entry = {\n conn: WorkerConn;\n key: string;\n state: PidState;\n taskId?: number;\n lastUsedAt: number;\n spawnedAt: number;\n};\n\nconst entries = new Map<string, Entry>(); // conn.id -> entry(Task 侧只拿 conn 引用,映射留在这里)\n\nfunction info(e: Entry): ConnInfo {\n const i: ConnInfo = {\n id: e.conn.id,\n agent: e.conn.agent.name,\n key: e.key,\n state: e.state,\n lastUsedAt: e.lastUsedAt,\n };\n if (e.taskId !== undefined) i.taskId = e.taskId;\n return i;\n}\n\n/** 池内记录(只读;判定一律走这个 + 纯函数)。 */\nexport function poolInfo(): ConnInfo[] {\n return [...entries.values()].filter((e) => e.conn.isAlive()).map(info);\n}\n\n/** 写入磁盘 pid 身份记录:`wemind stop` 据此精确找到全部 worker(多 agent 并存时新记录显得必要)。 */\nfunction writeRecords(): void {\n const recs: PidRecord[] = [];\n for (const e of entries.values()) {\n const pid = e.conn.pid();\n if (pid === null || !e.conn.isAlive()) continue;\n const extra: { taskId?: number; state: PidState } = { state: e.state };\n if (e.taskId !== undefined) extra.taskId = e.taskId;\n const rec = pidRecordOf(pid, e.conn.agent, extra);\n rec.startedAt = e.spawnedAt;\n recs.push(rec);\n }\n writePidRecords(WORKERS_PID_FILE, recs);\n}\n\nfunction reapByIds(ids: string[]): void {\n let changed = false;\n for (const id of ids) {\n const e = entries.get(id);\n if (!e || e.state === \"busy\") continue; // 复核:判定之后可能已被别的任务取用\n e.conn.kill();\n entries.delete(id);\n changed = true;\n }\n if (changed) writeRecords();\n}\n\n/**\n * 取一条连接跑任务:命中空闲就复用,否则起新进程(spawn + initialize 成功才算拿到)。\n * 同步登记(busy)**在** await spawn 之前——否则两个任务会同时规划到「空闲」的同一状态、各起一个进程。\n */\nexport async function acquire(\n ag: ResolvedAgent,\n taskId?: number,\n cwd?: string,\n): Promise<{ ok: true; conn: WorkerConn } | { ok: false; problem: string }> {\n const key = connKey(ag, cwd);\n const plan = planAcquire(poolInfo(), key, poolLimits(), Date.now());\n if (plan.kind === \"reject\") return { ok: false, problem: plan.problem };\n // 为**这条任务的 agent** 备沙箱:daemon 启动时只按默认任务大脑备过一次,派到别的 agent(如 `--agent pi`)时\n // 它自己的 settings/凭据/MCP 还没进沙箱,agent 会拿空配置跑。复用空闲连接前也补一次,让运行中改过的配置跟得上。\n try {\n ensureWorkerHome(ag);\n } catch (e) {\n console.error(\n `[wemind/wechat-daemon/worker] 准备沙箱失败(${ag.name}):${e instanceof Error ? e.message : String(e)}`,\n );\n }\n reapByIds(plan.reap);\n\n if (plan.kind === \"reuse\") {\n const e = entries.get(plan.connId);\n if (e) {\n e.state = \"busy\";\n e.taskId = taskId;\n e.lastUsedAt = Date.now();\n e.conn.setCwd(cwd); // 身份键已保证 cwd 相同;这里同步一次,防止连接被别处改过\n writeRecords();\n return { ok: true, conn: e.conn };\n }\n // 理论上到不了(plan 与 entries 之间没有 await);真到了就当真·spawn 处理,宁可多起一个也不假装拿到了\n }\n\n const conn = createConn(ag, () => {\n const e = entries.get(conn.id);\n if (e) {\n e.spawnedAt = Date.now();\n writeRecords(); // 进程刚起来就落记录(握手之前),`wemind stop` 才来得及抓到它\n }\n });\n conn.setCwd(cwd);\n const entry: Entry = {\n conn,\n key,\n state: \"busy\",\n lastUsedAt: Date.now(),\n spawnedAt: Date.now(),\n ...(taskId !== undefined ? { taskId } : {}),\n };\n entries.set(conn.id, entry); // 先登记:并发的第二个 acquire 立刻能看到它(busy)\n try {\n await conn.warm();\n writeRecords();\n return { ok: true, conn };\n } catch (e) {\n entries.delete(conn.id);\n conn.kill();\n writeRecords();\n const why = e instanceof Error ? e.message : String(e);\n return { ok: false, problem: `起 worker 进程失败(${ag.name}):${why}` };\n }\n}\n\n/** 任务结束:把连接还给进程池(置空闲)并按策略回收多余/过期的空闲连接;连接已经死了就地清掉。 */\nexport function release(conn: WorkerConn, limits: PoolLimits = poolLimits()): void {\n const e = entries.get(conn.id);\n if (!e) return;\n if (!conn.isAlive()) {\n entries.delete(conn.id); // 进程已死(任务中途被停/崩溃):没有可复用的东西,别在池里留空壳\n writeRecords();\n return;\n }\n e.state = \"idle\";\n delete e.taskId;\n e.lastUsedAt = Date.now();\n reapByIds(planReap(poolInfo(), limits, Date.now(), conn.id));\n writeRecords();\n}\n\n/**\n * 定时清扫该摘掉哪些连接:只收「空闲且已死」的空壳。\n * **busy 的死连接必须保留**:任务可能还会 `ask()`,`WorkerConn` 会在原连接里复活进程;池里若已没有它的 entry,\n * `onSpawn` 就写不了 pid 记录、`release` 也找不到它——复活进程会脱离池管理,上限失效且永久泄漏。\n * busy 的死连接由任务结束时的 `release`(进程已死 → 摘除)或 `drop`(杀 + 摘除)负责。\n */\nexport function planSweep(conns: Array<ConnInfo & { alive: boolean }>): string[] {\n return conns.filter((c) => c.state === \"idle\" && !c.alive).map((c) => c.id);\n}\n\n/** 定时清扫(index.ts 起 interval):清掉已死的空闲空壳 + 处理「空闲过期/超编」,不动 busy。 */\nexport function sweep(limits: PoolLimits = poolLimits()): void {\n let changed = false;\n let deadBusy = false;\n const dead = planSweep(\n [...entries.values()].map((e) => {\n const alive = e.conn.isAlive();\n if (!alive && e.state === \"busy\") deadBusy = true;\n return { ...info(e), alive };\n }),\n );\n for (const id of dead) {\n entries.delete(id);\n changed = true;\n }\n const reap = planReap(poolInfo(), limits, Date.now());\n // busy 的死连接要留在池里等 release/drop,但它的身份记录必须立刻清掉(否则 workers.pid.json 留着死 pid)。\n if (changed || reap.length || deadBusy) {\n reapByIds(reap);\n writeRecords();\n }\n}\n\n/**\n * 杀掉池里**全部**连接——只给「daemon 退出」用(`index.ts` 的 `cleanupAll()`,`wemind stop` / 进程退出时触发)。\n * 结束单条任务**不经过这里**:那条路走 `task-manager.ts` 的「等回复的 `release()` 留着复用 / 正在跑的 `drop()` 杀一条」。\n */\nexport function killAll(): void {\n for (const e of entries.values()) e.conn.kill();\n entries.clear();\n writePidRecords(WORKERS_PID_FILE, []);\n}\n\n/** 弃用某条连接(任务被停时正在跑的那条):杀进程 + 从池里摘掉。 */\nexport function drop(conn: WorkerConn): void {\n conn.kill();\n if (entries.delete(conn.id)) writeRecords();\n}\n\n/** 只回收空闲连接(有任务在跑时用)。 */\nexport function killIdle(): void {\n let changed = false;\n for (const [id, e] of entries) {\n if (e.state !== \"idle\") continue;\n e.conn.kill();\n entries.delete(id);\n changed = true;\n }\n if (changed) writeRecords();\n}\n\n/** 池状态摘要:给 `wemind list` / 微信 `AI` 展示「哪些 agent 有几条忙/闲」。 */\nexport function poolSummary(): Array<{\n agent: string;\n busy: number;\n idle: number;\n taskIds: number[];\n}> {\n const byAgent = new Map<string, { busy: number; idle: number; taskIds: number[] }>();\n for (const e of entries.values()) {\n if (!e.conn.isAlive()) continue;\n let s = byAgent.get(e.conn.agent.name);\n if (!s) {\n s = { busy: 0, idle: 0, taskIds: [] };\n byAgent.set(e.conn.agent.name, s);\n }\n if (e.state === \"busy\") {\n s.busy += 1;\n if (e.taskId !== undefined) s.taskIds.push(e.taskId);\n } else s.idle += 1;\n }\n return [...byAgent.entries()].map(([agent, s]) => ({ agent, ...s }));\n}\n\n/** 某条任务当前用的连接(任务表与池的桥)。 */\nexport function connOfTask(taskId: number): WorkerConn | null {\n for (const e of entries.values()) if (e.taskId === taskId) return e.conn;\n return null;\n}\n\n/** 测试用:清空池(默认连进程一起杀)。 */\nexport function resetPoolForTest(kill = true): void {\n if (kill) for (const e of entries.values()) e.conn.kill();\n entries.clear();\n writePidRecords(WORKERS_PID_FILE, []);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\n/**\n * 任务表写入磁盘(`tasks.json`):只为「daemon 重启后能续跑」这一件事,不做历史归档\n * (微信聊天记录本身就是每条任务输出的归档,另存历史收益低、维护面大)。\n *\n * 三条设计约束:\n * - **编号跨重启不归零**:`nextId` 按用户单调递增、只增不减,重启后接着排(否则「任务2」在对话里会指代变化);\n * - **不自动恢复**:重启后未完成的任务一律标 `interrupted`,只在 `任务列表` 可见;用户回「继续」/`#n 继续` 才拉进程续跑\n * (3 个 agent 一起冷启动既重、也可能正好复现刚崩的原因);\n * - **损坏不阻塞启动**:读失败降级为空表(只记日志)——任务表坏了顶多「列表空」,绝不能让 daemon 起不来。\n */\n\nexport const TASK_STORE_VERSION = 1;\n\nexport type StoredTaskStatus = \"running\" | \"waiting\" | \"interrupted\";\n\nexport type StoredTask = {\n id: number;\n userId: string;\n agent: string;\n /** 会话键(`task-<id>@<userId>`):worker-sessions.json 里 `<agent>:<key>` 指向的 sessionId。 */\n key: string;\n status: StoredTaskStatus;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n /** 任务工作目录(本地派发可指定;续跑要还原成同一个 cwd)。 */\n cwd?: string;\n};\n\nexport type TaskStore = {\n version: number;\n tasks: StoredTask[];\n /** 按用户的「下一个编号」:只增,永不复用,跨重启连续。 */\n nextId: Record<string, number>;\n};\n\nexport function taskStorePath(dir: string = DATA_DIR): string {\n return path.join(dir, \"tasks.json\");\n}\n\nexport function emptyTaskStore(): TaskStore {\n return { version: TASK_STORE_VERSION, tasks: [], nextId: {} };\n}\n\n/** 读任务表:缺文件/坏 JSON/字段离谱 → 空表(降级,不抛)。 */\nexport function loadTaskStore(dir: string = DATA_DIR): TaskStore {\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(taskStorePath(dir), \"utf-8\"));\n } catch {\n return emptyTaskStore();\n }\n const j = raw as Record<string, unknown> | null;\n if (!j || typeof j !== \"object\" || Array.isArray(j)) return emptyTaskStore();\n const tasks: StoredTask[] = [];\n if (Array.isArray(j.tasks)) {\n for (const e of j.tasks as Array<Record<string, unknown>>) {\n const id = Number(e?.id);\n const userId = typeof e?.userId === \"string\" ? e.userId : \"\";\n if (!Number.isInteger(id) || id <= 0 || !userId) continue;\n const status: StoredTaskStatus =\n e.status === \"running\" || e.status === \"waiting\" ? e.status : \"interrupted\";\n const t: StoredTask = {\n id,\n userId,\n agent: typeof e.agent === \"string\" ? e.agent : \"\",\n key: typeof e.key === \"string\" ? e.key : `task-${id}@${userId}`,\n status,\n turns: Number(e.turns) || 0,\n startedAt: Number(e.startedAt) || 0,\n };\n if (typeof e.lastOutput === \"string\") t.lastOutput = e.lastOutput;\n if (typeof e.cwd === \"string\" && e.cwd) t.cwd = e.cwd;\n tasks.push(t);\n }\n }\n const nextId: Record<string, number> = {};\n const rawNext = (j.nextId ?? {}) as Record<string, unknown>;\n if (rawNext && typeof rawNext === \"object\" && !Array.isArray(rawNext)) {\n for (const [k, v] of Object.entries(rawNext)) {\n const n = Number(v);\n if (Number.isInteger(n) && n > 0) nextId[k] = n;\n }\n }\n // 表里的编号也要算进 nextId:手改过/半截文件时不会发号撞车\n for (const t of tasks) nextId[t.userId] = Math.max(nextId[t.userId] ?? 1, t.id + 1);\n return { version: TASK_STORE_VERSION, tasks, nextId };\n}\n\n/** 原子写(临时文件 + rename):daemon 可能随时被杀,别留半截表。 */\nexport function saveTaskStore(store: TaskStore, dir: string = DATA_DIR): void {\n const file = taskStorePath(dir);\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const tmp = `${file}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(store, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, file);\n } catch {\n /* best-effort:任务表写失败不该影响任务本身 */\n }\n}\n\n/** 重启后:未完成的任务一律标「已中断」(返回被标的那些,调用方据此记日志/展示)。 */\nexport function markInterrupted(store: TaskStore): StoredTask[] {\n const hit: StoredTask[] = [];\n for (const t of store.tasks) {\n if (t.status === \"running\" || t.status === \"waiting\") {\n t.status = \"interrupted\";\n hit.push(t);\n }\n }\n return hit;\n}\n\n/** 发号:按用户单调递增,不复用(返回新编号并推进 nextId)。 */\nexport function takeNextId(store: TaskStore, userId: string): number {\n const n = store.nextId[userId] ?? 1;\n store.nextId[userId] = n + 1;\n return n;\n}\n\nexport function upsertTask(store: TaskStore, task: StoredTask): void {\n const i = store.tasks.findIndex((t) => t.id === task.id);\n if (i >= 0) store.tasks[i] = task;\n else store.tasks.push(task);\n}\n\nexport function removeTask(store: TaskStore, id: number): boolean {\n const i = store.tasks.findIndex((t) => t.id === id);\n if (i < 0) return false;\n store.tasks.splice(i, 1);\n return true;\n}\n","import { DATA_DIR } from \"@wemind/wechat-ilink-core\";\n\nimport { getAgent } from \"./agent-slot.js\";\nimport { agentConfigFor, loadBrainConfig } from \"./brain-config.js\";\nimport { resolvePresetByName, type ResolvedAgent } from \"./brain-agents.js\";\nimport { acquire, drop, release, poolSummary } from \"./worker-pool.js\";\nimport type { WorkerConn, WorkerProgressEvent } from \"./worker-acp.js\";\nimport {\n emptyTaskStore,\n loadTaskStore,\n markInterrupted,\n removeTask as removeStoredTask,\n saveTaskStore,\n takeNextId,\n type StoredTask,\n type TaskStore,\n type StoredTaskStatus,\n} from \"./task-store.js\";\n\n/**\n * 任务表:**一条任务一条 worker 连接**,多条任务并行;编号按用户单调递增、永不复用(避免「任务2」在对话里指代变化)。\n *\n * 与改造前的差别(单任务单进程 → 任务表):\n * - 派发时决定 agent(默认任务大脑,或 `任务 <agent 名>:正文` 指定);每条任务一个 session key `task-<编号>@<userId>`;\n * - 任务结束(`结束任务`)把连接**还回进程池**(同 agent 的下个任务直接复用),而不是杀掉;\n * - 任务表写入磁盘(tasks.json):daemon 重启后未完成的标「已中断」并在「任务列表」可见,回「继续」/`#n 继续` 才拉进程续跑;\n * - 一轮失败在**模型/账号侧**(余额、密钥、计费、模型不存在,见 `providerIssue()`)也直接转「已中断」:这类错误重试不会好,而停在「等你回复」会一直抢用户的无编号消息;\n * - 编号跨重启不归零(nextId 存在表里,只增不减)。\n *\n * 依赖注入(push/typing/stateSink)保持不变:task-manager 不直接依赖发送层与 account,保持可测。\n */\n\nconst log = (...a: unknown[]) => console.error(\"[wemind/wechat-daemon/task]\", ...a);\n\nexport type TaskStatus = StoredTaskStatus;\n\nexport type Task = {\n id: number;\n userId: string;\n /** agent 名(写入磁盘这个;恢复时按名字重新解析,可能已经换了二进制或不可用)。 */\n agent: string;\n /** 会话键:`task-<编号>@<userId>`(worker-sessions.json 里 `<agent>:<key>`)。 */\n key: string;\n status: TaskStatus;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n cwd?: string;\n /** 本次运行解析出的 agent(interrupted 任务在恢复前没有)。 */\n ag?: ResolvedAgent;\n conn?: WorkerConn;\n /** 本地命令(`wemind task`)等着的答复编号:起进程有结果了才回,见 `setTaskAckSink`。 */\n ackId?: string;\n /** 答复里要不要带编号(带 `--agent`/`--cwd` 或已有别的任务时带)。 */\n numbered?: boolean;\n /** 下一轮是「从中断处续跑」:会话恢复失败要诚实报错,不能静默换空会话。 */\n strictResume?: boolean;\n pendingInjects?: string[];\n injectResolver?: (text: string | null) => void;\n};\n\n/** 依赖注入:把「推送文本给用户」交给 index 提供(内部走 sendOrdered + 最新 token)。 */\ntype PushFn = (to: string, text: string) => void;\nlet push: PushFn = () => {};\nexport function setTaskPush(fn: PushFn): void {\n push = fn;\n}\n\n/** 依赖注入:发「正在输入中」(typing 不占连续发送上限),让用户知道任务在干活。 */\nlet typing: (to: string) => void = () => {};\nexport function setTaskTyping(fn: (to: string) => void): void {\n typing = fn;\n}\n\n/**\n * 依赖注入:答复出口(CLI 写 `task-out/<id>.json`)。\n *\n * 只在「本地命令问结果」时用到:`wemind task` 这类命令丢完命令就只能猜,\n * 猜出来的「已派发」在达上限被拒时会变成假消息。派发的答复**必须等起进程有结果**再回——\n * 达上限/agent 起不来都是 acquire 阶段才知道的(dispatchTask 当场返回时还不知道)。\n */\nlet ack: (id: string, text: string) => void = () => {};\nexport function setTaskAckSink(fn: (id: string, text: string) => void): void {\n ack = fn;\n}\n/**\n * 发一条答复(没有 id / 没有出口就当没这回事)。\n *\n * **空文案也照发**:它是「命令已送达、没有额外要说的」这个信号——CLI 收到它就能立刻用自己那句话,\n * 而不是干等到超时才敢打印(超时是留给「老版 daemon 不认这条通道」的)。\n */\nfunction ackIf(id: string | undefined, text: string): void {\n if (!id) return;\n try {\n ack(id, text);\n } catch {\n /* ignore */\n }\n}\n\n/** 任务状态记录(写入磁盘 task-state.json 供本地 `task-status` 读;顶层字段复制「最近一条」,旧版 CLI 仍能显示)。 */\nexport type TaskStateSnapshot = {\n active: boolean;\n status: TaskStatus | \"idle\";\n to?: string;\n startedAt?: number;\n updatedAt: number;\n turns: number;\n lastOutput?: string;\n tasks: Array<{\n id: number;\n userId: string;\n agent: string;\n status: TaskStatus;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n }>;\n};\nlet stateSink: (s: TaskStateSnapshot) => void = () => {};\nexport function setTaskStateSink(fn: (s: TaskStateSnapshot) => void): void {\n stateSink = fn;\n}\n\nconst tasks = new Map<number, Task>();\nlet store: TaskStore = emptyTaskStore();\nlet storeDir = DATA_DIR;\n\n/** 写入磁盘时机:派发/每轮结束/waiting/停止(表很小,写多了也没关系;丢了才会真的影响续跑)。 */\nfunction persist(): void {\n store.tasks = [...tasks.values()].map((t) => {\n const s: StoredTask = {\n id: t.id,\n userId: t.userId,\n agent: t.agent,\n key: t.key,\n status: t.status,\n turns: t.turns,\n startedAt: t.startedAt,\n };\n if (t.lastOutput !== undefined) s.lastOutput = t.lastOutput;\n if (t.cwd) s.cwd = t.cwd;\n return s;\n });\n saveTaskStore(store, storeDir);\n}\n\nfunction emitState(): void {\n const live = [...tasks.values()].filter((t) => t.status !== \"interrupted\");\n const primary = live.sort((a, b) => b.startedAt - a.startedAt)[0];\n const snap: TaskStateSnapshot = {\n active: !!primary,\n status: primary?.status ?? \"idle\",\n updatedAt: Date.now(),\n turns: primary?.turns ?? 0,\n tasks: [...tasks.values()]\n .sort((a, b) => a.id - b.id)\n .map((t) => {\n const e: TaskStateSnapshot[\"tasks\"][number] = {\n id: t.id,\n userId: t.userId,\n agent: t.agent,\n status: t.status,\n turns: t.turns,\n startedAt: t.startedAt,\n };\n if (t.lastOutput !== undefined) e.lastOutput = t.lastOutput;\n return e;\n }),\n };\n if (primary) {\n snap.to = primary.userId;\n snap.startedAt = primary.startedAt;\n if (primary.lastOutput !== undefined) snap.lastOutput = primary.lastOutput;\n }\n try {\n stateSink(snap);\n } catch {\n /* best-effort */\n }\n}\n\nconst PROGRESS = (process.env.WEMIND_WORKER_PROGRESS ?? \"0\") === \"1\"; // 默认安静:只在轮次结束/中断推送\n\n/** 启动时读回任务表:未完成的标「已中断」(**不自动恢复**),编号接着排。 */\nexport function initTasks(dir: string = DATA_DIR): { interrupted: number[] } {\n storeDir = dir;\n store = loadTaskStore(dir);\n const hit = markInterrupted(store);\n saveTaskStore(store, dir);\n tasks.clear();\n for (const t of store.tasks) tasks.set(t.id, { ...t });\n if (hit.length)\n log(\n `重启:${hit.length} 条未完成任务标记为「已中断」(编号 ${hit.map((t) => t.id).join(\"、\")};回「继续」可续跑)`,\n );\n emitState();\n return { interrupted: hit.map((t) => t.id) };\n}\n\n// ---- 查询 ----\nconst isLive = (t: Task): boolean => t.status === \"running\" || t.status === \"waiting\";\nexport const liveTasks = (userId: string): Task[] =>\n [...tasks.values()].filter((t) => t.userId === userId && isLive(t)).sort((a, b) => a.id - b.id);\nexport const awaitingTasks = (userId: string): Task[] =>\n liveTasks(userId).filter((t) => t.status === \"waiting\");\nexport const interruptedTasks = (userId: string): Task[] =>\n [...tasks.values()]\n .filter((t) => t.userId === userId && t.status === \"interrupted\")\n .sort((a, b) => a.id - b.id);\nexport const listTasks = (userId: string): Task[] =>\n [...tasks.values()].filter((t) => t.userId === userId).sort((a, b) => a.id - b.id);\n\n/**\n * 像不像「继续跑」这句话。\n *\n * 只在一处用到:**「已中断」的任务**(daemon 重启留下的,或一轮失败在 provider 侧——见 `providerIssue()`)在没有活跃任务时要把无前缀消息放行进任务表(`injectTask` → `resumeTask`)。\n * 关键字这层闸不能省:中断任务会一直躺在表里,若见了任何消息都放行,一句「帮我查一下 X」会把陈旧的它唤醒。\n * 单条任务时 `#n 继续` 也走这条路(显式编号那条分支在解析层就分流了)。\n */\nexport function looksLikeResume(text: string | undefined): boolean {\n return /^(?:继续|接着|接着跑|继续跑|续跑|继续吧|continue|go\\s*on)[。.!!]?$/i.test(\n (text ?? \"\").trim(),\n );\n}\n\n/**\n * 无编号消息是否该交给任务续跑:只有「继续」类短句 + 存在可续跑的任务(有 waiting;或没有活跃任务时存在 interrupted,多条时由 `injectTask` 回编号清单)。\n * 2026-11-02 起 waiting **不再**接管任意无编号消息——任务出完结果后还躺在 waiting,会把用户找大脑的日常消息吞掉(真机踩到两次:陈旧任务 #26、#28)。\n * 其余消息一律走大脑;任务侧的追问用 `#<编号> 正文` / `> 正文` / `任务 …` 显式寻址。\n */\nexport function shouldResumeTask(\n text: string | undefined,\n state: { awaiting: number; active: boolean; interrupted: number },\n): boolean {\n if (!looksLikeResume(text)) return false;\n return state.awaiting > 0 || (!state.active && state.interrupted > 0);\n}\n\n/** 是否存在活跃任务(可选限定某用户)。 */\nexport function hasActiveTask(to?: string): boolean {\n return to === undefined ? [...tasks.values()].some(isLive) : liveTasks(to).length > 0;\n}\n/** 池状态摘要(`AI`/`wemind list` 展示用)。 */\nexport const taskPoolSummary = poolSummary;\n\nfunction since(ms: number): string {\n const s = Math.max(0, Math.round(ms / 1000));\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n if (m < 60) return `${m}m`;\n return `${Math.floor(m / 60)}h${m % 60}m`;\n}\n\nconst STATUS_LABEL: Record<TaskStatus, string> = {\n running: \"运行中\",\n waiting: \"等你回复\",\n interrupted: \"已中断\",\n};\n\n/** 任务标签:多条任务时带编号(单任务免编号,延续改造前的顺手)。 */\nfunction labelOf(task: Task): string {\n return listTasks(task.userId).length > 1 || liveTasks(task.userId).length > 1\n ? `任务${task.id}`\n : \"任务\";\n}\n\n/**\n * 任务回合结束时附带的「怎么回这条任务」提示。\n * 2026-11-02 起不带前缀的消息一律交给大脑,任务必须显式寻址,所以每条需要用户接话的推送都要把当前编号的写法带上;\n * 只拼在发送文本上,不写进 `lastOutput`(任务列表里不需要重复这段)。\n */\nfunction replyFooter(task: Task, kind: \"result\" | \"interrupted\"): string {\n const id = `#${task.id}`;\n return kind === \"interrupted\"\n ? `\\n↩ 续跑用「${id} 继续」,补充用「${id} 正文」,结束用「结束任务 ${task.id}」;不带前缀的消息会交给大脑。`\n : `\\n↩ 回这条任务用「${id} 正文」;不带前缀的消息会交给大脑。`;\n}\n\n/** `任务列表`:编号 / agent / 状态 / 轮次 / 已运行 / 最近输出摘要。 */\nexport function renderTaskList(userId: string): string {\n const all = listTasks(userId);\n if (!all.length)\n return \"📋 当前没有任务。派发:直接发「任务:正文」或「任务 <agent 名>:正文」。\";\n const lines = [`📋 任务(${all.length} 条)`];\n for (const t of all) {\n const age = t.startedAt ? since(Date.now() - t.startedAt) : \"?\";\n lines.push(\n `#${t.id} [${t.agent}] ${STATUS_LABEL[t.status]} · ${t.turns} 轮 · ${age}${t.cwd ? ` · cwd=${t.cwd}` : \"\"}`,\n );\n if (t.lastOutput) lines.push(` 最近输出:${t.lastOutput.replace(/\\n/g, \" \").slice(0, 120)}`);\n }\n const live = all.filter(isLive);\n if (live.length > 1)\n lines.push(`多条任务并行:回话用「#<编号> 正文」,结束用「结束任务 <编号>」。`);\n const intr = all.filter((t) => t.status === \"interrupted\");\n if (intr.length)\n lines.push(\n `已中断的任务回「继续」(或「#${intr[0].id} 继续」)可续跑——注意上一步可能已经改过文件,核对后再继续。`,\n );\n return lines.join(\"\\n\");\n}\n\n// ---- 派发与续跑 ----\n/**\n * 答复文案:单任务(且没指定 agent、没指定 cwd)沿用改造前那句话——最常见路径不该变得啰嗦;\n * 其余情况(指定了 agent / 已经有多条任务 / 本地派发)带上编号,因为这时编号才有用。\n */\nfunction ackFor(task: Task, numbered: boolean): string {\n if (!numbered)\n return \"✅ 已派给工作机执行(任务运行中)。需要你决策或中断时我会主动找你;随时可回复「结束任务」终止。\";\n return `✅ 已派发任务 #${task.id}(${task.agent} 执行)。看进展发「任务列表」,回话发「#${task.id} 正文」,收掉发「结束任务 ${task.id}」。`;\n}\n\n/** 解析「本任务用哪个 agent」:显式 agent 名优先,否则默认任务大脑。 */\nfunction resolveTaskAgent(\n agentKey?: string,\n): { ok: true; ag: ResolvedAgent } | { ok: false; problem: string } {\n if (!agentKey) return { ok: true, ag: getAgent(\"worker\") };\n const cfg = loadBrainConfig(storeDir);\n const r = resolvePresetByName(agentKey, agentConfigFor(cfg, \"worker\") ?? cfg);\n return r.ok ? { ok: true, ag: r.agent } : { ok: false, problem: r.problem };\n}\n\n/**\n * 派发新任务(后台异步执行,立即返回答复文案,不阻塞调用方)。\n * `agentKey`:`任务 <agent 名>:正文` 指定的 agent;`cwd`:本地派发指定的任务工作目录。\n * `ackId` 给了就等起进程有结果再答复(成功=真答复文案,达上限/起不来=那句警告)。\n */\nexport function dispatchTask(\n to: string,\n instruction: string,\n opts: { cwd?: string; agentKey?: string; ackId?: string } = {},\n): string {\n const text = (instruction ?? \"\").trim();\n if (!text) {\n const r = \"要派发的任务正文是空的。\";\n ackIf(opts.ackId, r);\n return r;\n }\n const got = resolveTaskAgent(opts.agentKey);\n if (!got.ok) {\n const r = `❌ 不能用「${opts.agentKey}」跑任务:${got.problem}`;\n ackIf(opts.ackId, r);\n return r;\n }\n const numbered = !!opts.agentKey || !!opts.cwd || listTasks(to).length > 0;\n const id = takeNextId(store, to);\n const task: Task = {\n id,\n userId: to,\n agent: got.ag.name,\n key: taskKeyOf(id, to),\n status: \"running\",\n turns: 0,\n startedAt: 0, // 真正起进程时再定(起进程可能失败)\n ag: got.ag,\n numbered,\n };\n if (opts.cwd) task.cwd = opts.cwd;\n if (opts.ackId) task.ackId = opts.ackId;\n tasks.set(id, task);\n persist();\n emitState();\n void runTask(task, text);\n return ackFor(task, numbered);\n}\n\n/** 会话键:带用户,避免多用户的编号相撞。 */\nexport function taskKeyOf(id: number, userId: string): string {\n return `task-${id}@${userId}`;\n}\n\nfunction waitForInject(task: Task): Promise<string | null> {\n if (task.pendingInjects?.length) {\n const t = task.pendingInjects.join(\"\\n\"); // 多条暂存按序合并喂下一轮,一条不丢\n task.pendingInjects = undefined;\n return Promise.resolve(t);\n }\n return new Promise((resolve) => {\n task.injectResolver = resolve;\n });\n}\n\n/** 任务循环:每轮 ask → 推送本轮输出 → 等用户回话 → 续跑。绝不在 workerLoop 里 await 它。 */\nasync function runTask(task: Task, firstInstruction: string): Promise<void> {\n const ag = task.ag;\n if (!ag) {\n log(`task ${task.id} 没有 agent(内部错误)`);\n return;\n }\n task.status = \"running\";\n emitState();\n const got = await acquire(ag, task.id, task.cwd);\n if (!got.ok) {\n const msg = `⚠️ 任务 #${task.id} 没起得来:${got.problem}`;\n // 续跑(`strictResume`)起不来 ≠ 新派发起不来:新任务从没跑过,删掉是对;续跑的记录必须留着——\n // 用户是照着「修好后回「继续」」的提示来的,删掉记录等于把编号和会话键一起丢了,还得重派一条。\n if (task.strictResume === true) {\n task.strictResume = false;\n task.status = \"interrupted\";\n task.lastOutput = msg;\n persist();\n emitState();\n push(\n task.userId,\n `${msg}(任务记录还在,处理好后按下面方式续跑)` + replyFooter(task, \"interrupted\"),\n );\n ackIf(task.ackId, msg);\n return;\n }\n tasks.delete(task.id);\n persist();\n emitState();\n push(task.userId, msg);\n ackIf(task.ackId, msg); // 本地命令要的是「到底派上了没有」——这里才是答案\n return;\n }\n const conn = got.conn;\n task.conn = conn;\n conn.setCwd(task.cwd);\n if (!task.startedAt) task.startedAt = Date.now();\n persist();\n emitState();\n ackIf(task.ackId, ackFor(task, task.numbered === true)); // 起进程成功:这时才敢说「已派发」\n task.ackId = undefined;\n log(`task ${task.id} start agent=${ag.name} conn=${conn.id} cwd=${conn.effectiveCwd()}`);\n\n let instruction: string | null = firstInstruction;\n while (tasks.get(task.id) === task && instruction !== null) {\n typing(task.userId); // 立即一次「正在输入」\n const ti = setInterval(() => typing(task.userId), 5000); // 每 5s 续命(小于客户端 typing 自动过期,防闪烁;不占连发上限)\n let wasResume = false;\n try {\n const onProgress = PROGRESS\n ? (ev: WorkerProgressEvent) => {\n if (ev.type === \"tool\") push(task.userId, `🔧 ${labelOf(task)} 正在调用工具…`);\n }\n : undefined;\n wasResume = task.strictResume === true;\n task.strictResume = false;\n const out = await conn.ask(instruction, task.key, onProgress, { strictResume: wasResume });\n if (tasks.get(task.id) !== task) break; // 期间被 stop\n const label = labelOf(task);\n const body = out || \"(本轮无输出)\";\n task.turns += 1;\n task.lastOutput = `🤖【${label}】${body}`;\n push(task.userId, task.lastOutput + replyFooter(task, \"result\"));\n } catch (e) {\n if (tasks.get(task.id) !== task) break; // 被 stop 导致的 reject,不算中断\n const why = String(e instanceof Error ? e.message : e);\n if (wasResume && why.includes(\"接不上\")) {\n // 续跑失败(原会话接不上):回到「已中断」并把话说清楚,而不是静默换一个空会话假装续上了。\n // 当前会话记录**保留**(收尾处按「任务是否还在表里」决定要不要清);但别再劝「重述一遍」——重述也是走 strictResume,接不上就是接不上,只能力尽于此。\n task.status = \"interrupted\";\n task.lastOutput = `⚠️ #${task.id} 号任务这次接不上:${why}(这条任务的记录还在;要重来就「结束任务 ${task.id}」后重新派一条。)`;\n persist();\n emitState();\n log(`task ${task.id} resume failed(接不上,已转「已中断」):`, why);\n push(task.userId, task.lastOutput + replyFooter(task, \"interrupted\"));\n break;\n }\n const label = labelOf(task);\n log(`task ${task.id} turn failed:`, why);\n const prov = providerIssue(e);\n if (prov) {\n // 模型/账号侧的问题(余额、密钥、计费、模型不存在):重试也不会好,停在「等你回复」只会让用户以为还能接着干。转「已中断」——状态如实反映这轮没跑完,续跑要用户显式回「继续」/`#n 继续`。\n task.status = \"interrupted\";\n task.lastOutput = `⚠️【${label}】中断了(${prov})。这类错误重试也不会好——把账号/模型处理好后再续跑。`;\n persist();\n emitState();\n log(`task ${task.id} provider issue(${prov})→ 已转「已中断」`);\n push(task.userId, task.lastOutput + replyFooter(task, \"interrupted\"));\n break;\n }\n task.lastOutput = `⚠️【${label}】中断了(${reason(e)})。`;\n push(task.userId, task.lastOutput + replyFooter(task, \"interrupted\"));\n } finally {\n clearInterval(ti); // 本轮结束停 typing\n }\n if (tasks.get(task.id) !== task) break;\n task.status = \"waiting\";\n persist();\n emitState(); // → waiting(task-list 可见「等你回复」)\n instruction = await waitForInject(task); // null = stop\n if (tasks.get(task.id) === task) {\n task.status = \"running\";\n persist();\n emitState();\n }\n }\n // 收尾:连接还给进程池(活着才有复用价值;被 stop 时可能已经死了,release 会就地清掉空壳)。\n // 当前会话记录只在「任务真的结束」(已被 stop、不在表里)时清掉:回到「已中断」的任务要留着它,用户重述进展后还能再试。\n if (task.conn === conn) {\n if (tasks.get(task.id) !== task) conn.dropSession(task.key);\n release(conn);\n task.conn = undefined;\n }\n emitState();\n log(`task ${task.id} loop ended`);\n}\n\nfunction reason(e: unknown): string {\n const s = String(e instanceof Error ? e.message : e);\n if (/no-first-frame|idle|cap|timeout/i.test(s)) return \"超时/卡住\";\n return \"执行出错\";\n}\n\n/**\n * 像不像「等一会儿重试就会好」的限流/过载。**先判它**,像就一律不归类为账号问题。\n *\n * 为什么必须先判:Google 的**按分钟**限流原文是「Quota exceeded for quota metric '…requests per minute'…」(HTTP 429),只按关键字会把限流误判成额度问题、把任务提前转「已中断」——而文档口径是「限流不算」。这里宁可漏判一个限流(退回老行为:停在等你回复),也不让限流误触发。\n * 判的是**文本里有没有限流语义**,**故意不看裸的状态码 `429`**:各家把 HTTP 状态塞进错文本的形态不一,而 OpenAI 的余额耗尽(「You exceeded your current quota, please check your plan and billing details」)本身就是 HTTP 429——按状态码判会把真正的账号问题放过。`overloaded` 是 Anthropic 的 529(过载,可重试)。\n * `-32603` 本身**不**在这里判——只有它包着的 provider 文案算(DSH 的余额不足就是这样包出来的),所以本函数只看文本、不看 code。\n */\nconst RATE_LIMIT_LIKE =\n /rate limit|rate_limit|ratelimit|too many requests|requests per minute|per minute|resource_exhausted|overloaded/i;\n\n/**\n * 这一轮失败是不是「模型/账号侧」的问题(额度、余额、密钥、鉴权、计费、模型不存在)——这类错误重试也不会好,得等用户把账号修好。\n *\n * 为什么单独认这一类:**停在「等你回复」的任务会抢用户下一条无编号消息**(「谁在等就喂谁」)。一条余额不足的任务因此会不断吞掉日常消息、每次都白失败一轮(真机踩过:它把「在吗」吃掉了)。认出来就转「已中断」,用户修好账号后回「继续」/`#n 继续` 再续跑。\n *\n * 用关键字判(错误文本来自各 agent 的 provider,措辞不统一),**认不出的一律按老规矩停在「等你回复」**——宁可多抢一次,也不要把偶发失败误判成账号问题。限流(429/rate limit)不算(见上面的 `RATE_LIMIT_LIKE`):等一会儿重试就会好,仍走老路。\n */\nexport function providerIssue(e: unknown): string | null {\n const s = String(e instanceof Error ? e.message : e);\n if (RATE_LIMIT_LIKE.test(s)) return null;\n if (\n /insufficient balance|balance is insufficient|insufficient[ _]funds|insufficient_quota|insufficient credits|credit balance|requires more credits|quota exceeded|exceeded your current quota|out of credits|余额不足|额度不足|欠费|请充值/i.test(\n s,\n )\n )\n return \"额度/余额不足\";\n if (\n /no api key|invalid api key|incorrect api key|api key not valid|invalid x-api-key|authentication required|unauthorized|not authenticated/i.test(\n s,\n )\n )\n return \"密钥/鉴权有问题\";\n if (/payment required|\"code\"\\s*:\\s*402|billing/i.test(s)) return \"计费问题\";\n // 「模型不存在」各家的写法都不同:OpenAI 的 model_not_found、Anthropic 的 no such model、\n // DeepSeek 的 Model Not Exist、Gemini 的 `models/<名> is not found`、还有「The model '…' does not exist」。\n // 中间那段用 [^\\n]{0,80} 兜住带引号/带点的模型名(gpt-3.5-turbo 里有点,不能用 [^.])。\n if (\n /model_not_found|no such model|unknown model|model[ _]?not[ _]?(?:found|exist)|model\\S*\\b[^\\n]{0,80}\\b(?:not found|does not exist)/i.test(\n s,\n )\n )\n return \"模型不可用\";\n return null;\n}\n\n/** 续跑一条「已中断」的任务:按原 agent 拉进程 + session/load 恢复上下文,然后照常跑循环。 */\nfunction resumeTask(task: Task, instruction: string): string {\n const got = resolveTaskAgent(task.agent);\n if (!got.ok) {\n // 返回而不是 push:微信侧一样会看到(injectTask 的返回值由调用方当答复发出去),CLI 侧则能拿到真话,\n // 否则「接不上」只进 daemon 日志、CLI 却报「已发送」。\n return `⚠️ 接不上 #${task.id} 号任务(原 agent「${task.agent}」现在用不了):${got.problem}`;\n }\n task.ag = got.ag;\n task.strictResume = true; // 续跑要求「上下文真的接上」:恢复失败要报错,不能静默换空会话\n void runTask(\n task,\n `(这条任务上一轮没跑完就中断了,现在续跑;上下文按会话恢复。动手前先核对上一步是否已经产生副作用,避免重复。)\\n${instruction}`,\n );\n return `⏳ 正在按 #${task.id} 号任务续跑(${got.ag.name})。上一步可能已经改过文件或发过请求,核对时注意别重复动作。`;\n}\n\n/**\n * 把用户消息送给任务。`id` 给定时只作用于那条任务;不给时:唯一一条 → 它;多条 → 回编号提示(不猜)。\n * 返回要额外回复的文案(空字符串=只推给了任务,不需要额外答复)。\n * `ackId`(本地命令):非空返回文案也回一份给 CLI——「没有 #9 号任务」这种话不能只进 daemon 日志。\n */\nexport function injectTask(to: string, text: string, id?: number, ackId?: string): string {\n const done = (r: string): string => {\n ackIf(ackId, r);\n return r;\n };\n const live = liveTasks(to);\n const intr = interruptedTasks(to);\n const body = (text ?? \"\").trim();\n if (!body) return done(\"要回给任务的内容是空的。\");\n\n let target: Task | undefined;\n if (id !== undefined) {\n target = tasks.get(id);\n if (!target || target.userId !== to)\n return done(`没有 #${id} 号任务。发「任务列表」看现有编号。`);\n } else {\n const awaiting = live.filter((t) => t.status === \"waiting\");\n // 「谁在等就喂谁」优先于「只有一条就喂它」:一条 running + 一条 waiting 时,无前缀消息显然是回给在等的那条\n if (awaiting.length === 1) target = awaiting[0];\n else if (live.length === 1) target = live[0];\n else if (live.length === 0) {\n if (intr.length === 1) return done(resumeTask(intr[0], body));\n if (intr.length > 1)\n return done(\n `有 ${intr.length} 条已中断的任务,请指明编号续跑:${intr.map((t) => `#${t.id}`).join(\"、\")}(写法:\\`#${intr[0].id} 继续\\`)。`,\n );\n return done(\"\");\n } else {\n const rows = live.map((t) => `#${t.id} [${t.agent}] ${STATUS_LABEL[t.status]}`).join(\" \");\n return done(`有 ${live.length} 条任务在跑,请指明要回给哪条:「#<编号> 正文」。\\n${rows}`);\n }\n }\n\n if (target.status === \"interrupted\") return done(resumeTask(target, body));\n if (target.status === \"waiting\" && target.injectResolver) {\n const r = target.injectResolver;\n target.injectResolver = undefined;\n r(body);\n return done(\"\");\n }\n (target.pendingInjects ??= []).push(body); // running 期间:按序累积,等这一步跑完一并处理(不覆盖前一条)\n push(to, `📝 已记下给 #${target.id} 的补充,等当前这步跑完就处理。`);\n return done(\"\");\n}\n\n/** 结束任务:停 worker、清状态、唤醒退出循环。`id` 缺省=唯一那条;`\"all\"`=全部。 */\nexport function stopTask(to: string, id?: number | \"all\", ackId?: string): string {\n const done = (r: string): string => {\n ackIf(ackId, r);\n return r;\n };\n const live = liveTasks(to);\n const intr = interruptedTasks(to);\n if (id === undefined && live.length === 0) {\n // daemon 重启后内存无任务,但 task-state.json 可能残留 active/waiting(幽灵任务)→ 仍要写入磁盘 idle\n persist();\n emitState();\n const hint = intr.length\n ? `有 ${intr.length} 条已中断的任务记录(回「继续」可续跑,或「结束任务 <编号>」清掉记录)。`\n : \"\";\n return done(`当前没有正在运行的任务。${hint}`);\n }\n if (id === undefined && live.length > 1) {\n const rows = live.map((t) => `#${t.id} [${t.agent}] ${STATUS_LABEL[t.status]}`).join(\" \");\n return done(\n `有 ${live.length} 条任务在跑,请指明编号(「结束任务 <编号>」),或「结束全部任务」。\\n${rows}`,\n );\n }\n\n const targets =\n id === \"all\" ? live : id === undefined ? live.slice(0, 1) : live.filter((t) => t.id === id);\n if (!targets.length) {\n if (id === undefined) return done(\"当前没有正在运行的任务。\");\n if (id === \"all\") return done(\"当前没有正在运行的任务。\");\n const t = tasks.get(id);\n if (t && t.userId === to && t.status === \"interrupted\") {\n tasks.delete(id);\n removeStoredTask(store, id);\n persist();\n emitState();\n return done(`已清掉 #${id} 号已中断任务的记录。`);\n }\n return done(`没有 #${id} 号在跑的任务。发「任务列表」看现有编号。`);\n }\n\n for (const t of targets) {\n const wasWaiting = t.status === \"waiting\";\n tasks.delete(t.id); // 先删:循环里的 `tasks.get(id) !== task` 据此感知被停\n if (t.injectResolver) {\n const r = t.injectResolver;\n t.injectResolver = undefined;\n r(null);\n }\n const conn = t.conn;\n if (conn) {\n conn.dropSession(t.key);\n if (wasWaiting)\n release(conn); // 等回复的任务进程是干净的 → 留着复用\n else drop(conn); // 正在跑的那条:回合没法「优雅取消」,只能杀掉(正在做的动作就此作罢)\n t.conn = undefined;\n }\n }\n persist();\n emitState();\n if (id === \"all\") return done(`🛑 已结束全部任务(${targets.length} 条)。`);\n const one = targets[0];\n return done(targets.length === 1 ? `🛑 任务 #${one.id} 已结束。` : \"🛑 任务已结束。\");\n}\n\n/** 测试用:清空内存任务表与写入磁盘路径(不写入磁盘、不杀进程由调用方决定)。 */\nexport function resetTasksForTest(dir?: string): void {\n tasks.clear();\n store = emptyTaskStore();\n if (dir) storeDir = dir;\n}\n","import { DATA_DIR, clearSessionPause, loadAccount } from \"@wemind/wechat-ilink-core\";\nimport { askBrainTurn, newSession, restartForMcpChange, resumeSession } from \"./acp-brain.js\";\nimport { agentConfigFor, loadBrainConfig } from \"./brain-config.js\";\nimport {\n presetAvailable,\n presetNames,\n resolvePresetByName,\n type ResolvedAgent,\n} from \"./brain-agents.js\";\nimport { getAgent, setAgent } from \"./agent-slot.js\";\nimport { preflightSwitch, switchBrain, switchInFlightTarget } from \"./brain-switch.js\";\nimport { renderHelp, type ChatCommand } from \"./chat-commands.js\";\nimport type { ControlRequest } from \"./control-bridge.js\";\nimport { currentSessionIdOf } from \"./session-registry.js\";\nimport {\n deleteSessionCmd,\n newSessionCmd,\n renameSessionCmd,\n resumeSessionCmd,\n sessionsCmd,\n type SessionCtx,\n} from \"./session-manager.js\";\nimport { addMcp, listMcp, removeMcp } from \"./mcp-sandbox.js\";\nimport { HANDOFF_PROMPT, HANDOFF_TTL_MS, HANDOFF_VERSION, saveHandoff } from \"./handoff.js\";\nimport { LONG_REPLY_THRESHOLD } from \"./md-reply.js\";\nimport { hasActiveTask, taskPoolSummary } from \"./task-manager.js\";\nimport { killIdle as killIdleConns } from \"./worker-pool.js\";\n\nconst log = (...a: unknown[]) => console.error(\"[wemind/wechat-daemon]\", ...a);\n\n/** 切脑结果/来源的中文说法(`AI` 总览与答复共用一套词,避免两处说法不一致)。 */\nconst SOURCE_LABEL: Record<ResolvedAgent[\"source\"], string> = {\n env: \"环境变量\",\n file: \"brain.json\",\n auto: \"自动探测\",\n};\n\nfunction agentLine(ag: ResolvedAgent): string {\n return `${ag.name}(来源:${SOURCE_LABEL[ag.source]})`;\n}\n\n/** `AI` / `/ai`:可选 agent(带「装了没」)+ 当前对话大脑/默认任务大脑 + 池中进程。 */\nfunction agentOverview(): string {\n const cfg = loadBrainConfig();\n const brain = getAgent(\"brain\");\n const worker = getAgent(\"worker\");\n const list = presetNames()\n .map((k) => `${k}${presetAvailable(k, cfg) ? \"✅\" : \"❌\"}`)\n .join(\"、\");\n const pool = taskPoolSummary();\n const lines = [\n \"🤖 AI 配置\",\n `对话大脑:${agentLine(brain)}`,\n `默认任务大脑:${agentLine(worker)}${worker.name === brain.name ? \"(随对话大脑)\" : \"\"}`,\n pool.length\n ? `池中 worker:${pool.map((p) => `${p.agent} 忙${p.busy}${p.taskIds.length ? `(#${p.taskIds.join(\"/\")})` : \"\"}/闲${p.idle}`).join(\" | \")}`\n : undefined,\n `可选 agent:${list}、auto`,\n \"切换:`切换大脑 <agent 名>` | `切换任务大脑 <agent 名>`\",\n ].filter((x): x is string => x !== undefined);\n return lines.join(\"\\n\");\n}\n\n/**\n * 切对话大脑:先回 ack,启动新大脑在后台跑、结果补发(决策⑦:切换可能要几十秒,不能让用户干等等不到一句回话)。\n * 这里**不** await 整段切换——启动新大脑期间的入站消息会自己在 acp-brain 门口等新大脑就绪(有界),不会双起进程。\n */\nasync function switchBrainAck(target: string, userId: string, ctx: ControlCtx): Promise<string> {\n const running = switchInFlightTarget();\n if (running) return `正在切到「${running}」,等它完成后再发一次。`;\n const pre = preflightSwitch(target, DATA_DIR);\n if (!pre.ok) return `❌ ${pre.problem}`;\n if (pre.already) {\n // 已经是它(只是把选择落到 brain.json):亚毫秒级,直接等它,不用 ack/补发两段\n const out = await switchBrain(target, { userId, dir: DATA_DIR });\n return out.ok ? out.notice : `❌ ${out.problem}`;\n }\n void switchBrain(target, { userId, dir: DATA_DIR })\n .then((out) => (out.ok ? out.notice : `❌ ${out.problem}`))\n .catch((e) => `❌ 切换出错:${String(e)}`)\n .then(async (r) => {\n log(\"brain switch:\", r.replace(/\\n/g, \" | \"));\n await ctx.notify(r).catch((e) => log(\"switch notice failed:\", String(e)));\n });\n return `⏳ 正在切到「${pre.agent.name}」…(正在启动新大脑,成功或失败稍后补发消息;这期间发来的消息会等新大脑就绪)`;\n}\n\n/** `切换任务大脑`:只改本进程的默认任务大脑(本次运行内一直生效,不写 brain.json)。 */\nfunction setTaskBrain(target: string): string {\n const key = target.trim().toLowerCase();\n const cfg = loadBrainConfig(DATA_DIR);\n const r = resolvePresetByName(key, agentConfigFor(cfg, \"worker\"));\n if (!r.ok) return `❌ ${r.problem}`;\n const cur = getAgent(\"worker\");\n // 与 brain-switch 的 sameAgent 同一条判据(name/bin/args/model):任一项不同都算一次真切换,\n // 否则「改完再切」会被判成「已经是它」,新任务继续用旧二进制/旧参数/旧模型。\n if (\n cur.name === r.agent.name &&\n cur.bin === r.agent.bin &&\n cur.args.join(\"\\u0000\") === r.agent.args.join(\"\\u0000\") &&\n cur.model === r.agent.model\n )\n return `默认任务大脑已经是「${r.agent.name}」,无需切换。`;\n setAgent(\"worker\", r.agent);\n const busy = hasActiveTask();\n killIdleConns(); // 池里的空闲连接属于旧默认:留着没用(下次按新 agent 取),有任务在跑的那些不动\n const note = \"(不写 brain.json,重启后回到配置里的默认)\";\n return busy\n ? `✅ 默认任务大脑:${cur.name} → ${r.agent.name}${note}。\\n在跑的任务继续用它自己起的 agent 跑完;**之后的新任务**用新大脑。`\n : `✅ 默认任务大脑:${cur.name} → ${r.agent.name}${note}。`;\n}\n\ntype ControlCtx = {\n /** 登录者 id(帮助文案据此裁剪)。 */\n owner: string;\n /** 补发一条「必须送达」的消息(用于切脑的 ack/结果两段答复)。 */\n notify: (text: string) => Promise<void>;\n /** 补发一份长内容(交接摘要这类「要给用户留着」的文本走 .md 文件,不刷屏)。 */\n notifyFile?: (text: string) => Promise<void>;\n};\n\n/**\n * `交接`:让当前大脑把上下文写成摘要,写入磁盘 + 回复给用户(长则另发 .md)。\n * 先回一条「在做」的提示:写摘要要走一整个模型回合(可能一两分钟),没有进度提示会让人以为卡死。\n */\nasync function runHandoff(userId: string, ctx: ControlCtx, deadline?: number): Promise<string> {\n const before = getAgent(\"brain\").name;\n if (!currentSessionIdOf(before, userId, DATA_DIR))\n return \"ℹ️ 当前大脑还没有和你的对话记录,没什么可交接的。先聊几句再来交接。\";\n await ctx\n .notify(`⏳ 正在让「${before}」把当前上下文写成交接摘要…(可能要一两分钟)`)\n .catch(() => {});\n let text: string;\n let from: string;\n try {\n const r = await askBrainTurn(userId, HANDOFF_PROMPT, undefined, undefined, { deadline });\n text = r.text.trim();\n from = r.agent; // 排队期间可能刚好切完脑:记实际答话的那个\n } catch (e) {\n return `❌ 生成交接摘要失败:${String(e)}`;\n }\n if (!text) return \"❌ 大脑这次没给出摘要内容(空回复),没有保存。可以再试一次。\";\n const now = Date.now();\n saveHandoff(\n {\n version: HANDOFF_VERSION,\n fromAgent: from,\n userId,\n text,\n createdAt: now,\n expiresAt: now + HANDOFF_TTL_MS,\n },\n DATA_DIR,\n );\n const head =\n `✅ 交接摘要已保存(${text.length} 字,来自「${from}」)。\\n` +\n `切到别的大脑后,你发的第一条消息会自动带上它(答完即用掉,只带一次;切回「${from}」不带上)。`;\n if (text.length > LONG_REPLY_THRESHOLD && ctx.notifyFile) {\n await ctx.notifyFile(text).catch((e) => log(\"handoff md send failed:\", String(e)));\n return `${head}\\n(摘要较长,已另发 .md 文件。)`;\n }\n return `${head}\\n\\n${text}`;\n}\n\nexport async function handleControl(\n c: ChatCommand,\n userId: string,\n ctx: ControlCtx,\n): Promise<string> {\n if (c.kind === \"list\") return listMcp();\n // 会话类指令的共用上下文:谁、当前对话大脑、数据目录(登记表按 agent 分开存,切换后自然按新大脑列)\n const sc = (): SessionCtx => ({ agent: getAgent(\"brain\").name, userId, dir: DATA_DIR });\n if (c.kind === \"new\") return await newSessionCmd({ ...sc(), name: c.name, create: newSession });\n if (c.kind === \"sessions\") return sessionsCmd({ ...sc(), page: c.page });\n if (c.kind === \"resume\") return await resumeSessionCmd({ ...sc(), resume: resumeSession }, c.ref);\n if (c.kind === \"rename\") return renameSessionCmd(sc(), c.ref, c.name);\n if (c.kind === \"delete\") return deleteSessionCmd(sc(), c.ref);\n if (c.kind === \"add\" || c.kind === \"remove\") {\n const r = c.kind === \"add\" ? addMcp(c.name) : removeMcp(c.name);\n if (!r.ok || !r.changed) return r.text; // 失败/没变化都不重启大脑\n // 切换进行中先等它结束再重启,别把正在预热的新大脑杀在半路\n if (!(await restartForMcpChange()))\n return `${r.text}\\n(大脑正在切换中,这次没有重启它;切换完成后请再发一次这条指令,让新 MCP 列表生效。)`;\n return r.text;\n }\n if (c.kind === \"ai\") return agentOverview();\n if (c.kind === \"brain\")\n return c.target\n ? await switchBrainAck(c.target, userId, ctx)\n : `当前对话大脑:${agentLine(getAgent(\"brain\"))}。切换用 \\`切换大脑 <agent 名>\\`(可选:${presetNames().join(\"、\")}、auto)。`;\n if (c.kind === \"workerBrain\")\n return c.target\n ? setTaskBrain(c.target)\n : `当前默认任务大脑:${agentLine(getAgent(\"worker\"))}。切换用 \\`切换任务大脑 <agent 名>\\`。`;\n if (c.kind === \"help\") return renderHelp(userId === ctx.owner ? \"owner\" : \"user\", \"wechat\");\n if (c.kind === \"handoff\") return await runHandoff(userId, ctx);\n // 兜底:指令表新增了 kind 但这里还没接通时(正常情况下 TS 会先把这里判成不可达),别静默吞掉\n return `「${(c as { kind: string }).kind}」这条指令还没接通(当前版本请先用 CLI 对应命令)。`;\n}\n\n/**\n * 执行一条 CLI 桥请求,返回要打印给用户的那句话。\n * 与微信指令**共用同一批实现函数**(switchBrain / session-manager / task-manager),\n * 所以「CLI 与微信行为一致」不是靠约定,而是同一段代码。\n * 这里的 `owner` 就是登录者:本地命令没有「谁发的」的概念,一律作用于本人。\n */\nexport async function handleControlRequest(req: ControlRequest, owner: string): Promise<string> {\n const sc = (): SessionCtx => ({ agent: getAgent(\"brain\").name, userId: owner, dir: DATA_DIR });\n switch (req.kind) {\n case \"brain.switch\": {\n if (!req.target) return \"❌ 用法:brain switch <agent 名|auto>\";\n const pre = preflightSwitch(req.target, DATA_DIR);\n if (!pre.ok) return `❌ ${pre.problem}`;\n // 注意:这里**不用** ack/补发两段(那是微信侧的写法)。CLI 在等应答,直接等完整结果更实在。\n const out = await switchBrain(req.target, { userId: owner, dir: DATA_DIR });\n return out.ok ? out.notice : `❌ ${out.problem}`;\n }\n case \"brain.worker\":\n if (!req.target)\n return `当前默认任务大脑:${agentLine(getAgent(\"worker\"))}。切换用 \\`wemind brain switch <agent 名> --worker\\`。`;\n return setTaskBrain(req.target);\n case \"brain.handoff\":\n return await runHandoff(owner, { owner, notify: async () => {} }, req.expiresAt);\n case \"session.new\":\n return await newSessionCmd({\n ...sc(),\n name: req.name,\n create: (uid) => newSession(uid, { deadline: req.expiresAt }),\n });\n case \"session.list\":\n return sessionsCmd({ ...sc(), page: req.page });\n case \"session.resume\":\n if (!req.ref) return \"❌ 用法:session resume <序号|名字|id前缀>\";\n return await resumeSessionCmd(\n {\n ...sc(),\n resume: (uid, sid) => resumeSession(uid, sid, { deadline: req.expiresAt }),\n },\n req.ref,\n );\n case \"session.rename\":\n if (!req.ref || !req.name) return \"❌ 用法:session rename <序号|名字> <新名字>\";\n return renameSessionCmd(sc(), req.ref, req.name);\n case \"session.delete\":\n if (!req.ref) return \"❌ 用法:session delete <序号|名字|id前缀>\";\n return deleteSessionCmd(sc(), req.ref);\n case \"session.unpause\": {\n // 控制桥入口没有外层 `account` 变量(`runDaemon` 里那份是局部的);这里按当前凭证重新取 botId。\n const botId = loadAccount()?.botId;\n if (!botId) return \"❌ 没有登录凭证;先登录再解除冷却。\";\n clearSessionPause(botId);\n log(\"control(cli): 已解除 -14 冷却,继续轮询\");\n return \"✅ 已解除会话过期冷却,继续轮询。\";\n }\n }\n}\n","/**\n * 「必须送达」外发的挂起队列(重启后旧 context_token 期间)。\n *\n * 背景:daemon 启动时 `state.contextTokens` 里还是上次运行留下的 context_token,服务端已经认不得;新 token 只有该用户发来下一条消息时才会到(`index.ts` 的 poller 登记处)。这段时间里任务结果、CLI 指令答复这类「不在某个回合里」的推送会 ret=-2 失败,重试一次仍是旧 token,消息直接丢失。\n *\n * 做法:失败时按收件人把文本挂起(内存队列),等新 token 到达时按原顺序补发。队列只在内存里,daemon 再重启一次就丢(任务结果仍能在 `任务列表`/`tasks.json` 里看到)。\n */\nexport const PENDING_PUSH_LIMIT = 20;\n/** 一条推送最多补发几次(首次补发 + 失败后重挂 = 每次该用户发消息时试一次);超过就放弃并记日志——防一条内容永远被服务端拒绝的消息每次都拖着 replyChain。 */\nexport const PENDING_PUSH_MAX_TRIES = 3;\n\nexport type PendingPush = { text: string; tries: number };\n\n/** 大脑回合已用到「上限-2」时,任务推送让出最后一条额度给最终答案(否则会把答案挤成 ret=-2)。 */\nexport function shouldDeferTaskPush(\n activeFrom: string | undefined,\n to: string,\n sentCount: number,\n limit: number,\n): boolean {\n return activeFrom === to && sentCount >= limit - 2;\n}\n\nexport class PendingPushes {\n private readonly queues = new Map<string, PendingPush[]>();\n private readonly limit: number;\n\n constructor(limit: number = PENDING_PUSH_LIMIT) {\n this.limit = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : PENDING_PUSH_LIMIT;\n }\n\n /** 挂起一条(按收件人分开;`tries` 为已经补发失败的次数);超过上限丢最旧的——保留最新的更可能还有用。返回该收件人当前挂起的条数。 */\n push(to: string, text: string, tries = 0): number {\n const q = this.queues.get(to) ?? [];\n q.push({ text, tries });\n if (q.length > this.limit) q.splice(0, q.length - this.limit);\n this.queues.set(to, q);\n return q.length;\n }\n\n /** 取走该收件人最前面至多 `max` 条(按入队顺序);没取走的留在队里。没有则返回空数组。 */\n take(to: string, max: number = Number.POSITIVE_INFINITY): PendingPush[] {\n const q = this.queues.get(to);\n if (!q || q.length === 0) return [];\n const n = Math.max(0, Math.min(q.length, Math.floor(max)));\n const out = q.splice(0, n);\n if (q.length === 0) this.queues.delete(to);\n return out;\n }\n\n /** 该收件人当前挂起多少条。 */\n size(to: string): number {\n return this.queues.get(to)?.length ?? 0;\n }\n\n /** 全部收件人的挂起总条数(日志用)。 */\n total(): number {\n let n = 0;\n for (const q of this.queues.values()) n += q.length;\n return n;\n }\n}\n","import { MessageState } from \"@wemind/wechat-ilink-core\";\n\nimport { PENDING_PUSH_MAX_TRIES, PendingPushes } from \"./pending-push.js\";\n\n/** 按收件人维护的连续发送预算(service 端按「距用户上一条消息」计)。 */\nexport type SendBudget = { count: number; awaiting: boolean };\n\nexport type SendPipelineDeps = {\n /** 发一条文本;实现方负责 ret=-2/传输错误的重试,本类只管排队、节流与失败后的挂起。 */\n sendText: (\n to: string,\n ctx: string | undefined,\n text: string,\n state: number,\n fallbackToken?: string,\n ) => Promise<void>;\n /** 发一张图;失败直接抛错(图片无法挂起补发)。 */\n sendImage: (to: string, ctx: string | undefined, image: Buffer) => Promise<void>;\n /** 把长回复作为 .md 文件发出;失败直接抛错(本类会回退为文本)。 */\n sendFileMd: (to: string, ctx: string | undefined, text: string) => Promise<void>;\n /** 分类记录发送错误(会话过期要暂停账号等)。 */\n onSendError: (e: unknown) => void;\n /** 是否为 ret=-2 参数错误(限流或旧 token)。 */\n isParamError: (e: unknown) => boolean;\n log: (...args: unknown[]) => void;\n now?: () => number;\n sleep?: (ms: number) => Promise<void>;\n minGapMs?: number;\n consecLimit?: number;\n pendingBatch?: number;\n pending?: PendingPushes;\n};\n\n/**\n * 外发管线:串行链 + 最小间隔 + 连发预算 + 旧 token 挂起/补发 + 回合末尾的让位队列。\n *\n * 抽成类是为了让这些编排可以脱离真实微信网络做单测:测试只注入假的 sendText/sendImage/sendFileMd 与可控时钟。\n */\nexport class SendPipeline {\n private readonly deps: Required<Omit<SendPipelineDeps, \"pending\">> & {\n pending: PendingPushes;\n };\n private readonly budgets = new Map<string, SendBudget>();\n private readonly freshTokenUsers = new Set<string>();\n private readonly inflightSends = new Map<string, number>();\n private readonly deferredTaskPushes = new Map<string, string[]>();\n private replyChain: Promise<void> = Promise.resolve();\n private lastSendAt = 0;\n\n constructor(deps: SendPipelineDeps) {\n this.deps = {\n ...deps,\n now: deps.now ?? Date.now,\n sleep: deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))),\n minGapMs: deps.minGapMs ?? 1500,\n consecLimit: deps.consecLimit ?? 10,\n pendingBatch: deps.pendingBatch ?? 5,\n pending: deps.pending ?? new PendingPushes(),\n };\n }\n\n get consecLimit(): number {\n return this.deps.consecLimit;\n }\n\n /** 该收件人的连发预算;调用方可直接读写 `count`/`awaiting`(与拆分前的模块级对象语义一致)。 */\n budgetOf(to: string): SendBudget {\n let b = this.budgets.get(to);\n if (!b) {\n b = { count: 0, awaiting: false };\n this.budgets.set(to, b);\n }\n return b;\n }\n\n /** 本进程已收到过该用户的消息 = 手里是这一进程拿到的 token,此后它的 -2 属于限流、不再挂起。 */\n markFresh(to: string): void {\n this.freshTokenUsers.add(to);\n }\n\n isFresh(to: string): boolean {\n return this.freshTokenUsers.has(to);\n }\n\n inflightCount(to: string): number {\n return this.inflightSends.get(to) ?? 0;\n }\n\n pendingCount(to: string): number {\n return this.deps.pending.size(to);\n }\n\n private trackInflight(to: string, delta: number): void {\n const n = this.inflightCount(to) + delta;\n if (n > 0) this.inflightSends.set(to, n);\n else this.inflightSends.delete(to);\n }\n\n private async waitGap(): Promise<void> {\n const wait = this.deps.minGapMs - (this.deps.now() - this.lastSendAt);\n if (wait > 0) await this.deps.sleep(wait);\n }\n\n /** 串行排队一条外发;`onError` 用于「必须送达」的调用方接管失败(挂起/重挂)。 */\n sendOrdered(\n to: string,\n ctx: string | undefined,\n text: string,\n state: number = MessageState.FINISH,\n fallbackToken?: string,\n onError?: (e: unknown) => void,\n ): Promise<void> {\n this.trackInflight(to, 1);\n this.replyChain = this.replyChain\n .then(async () => {\n await this.waitGap();\n await this.deps.sendText(to, ctx, text, state, fallbackToken).catch((e) => {\n this.deps.onSendError(e);\n onError?.(e);\n });\n this.lastSendAt = this.deps.now();\n })\n // 串行链不能因为一次意外抛错而永久卡死(后续 .then 全被跳过,所有消息静默不发)\n .catch((e) => this.deps.log(\"send chain error:\", String(e)))\n .finally(() => this.trackInflight(to, -1));\n return this.replyChain;\n }\n\n /**\n * 发一张图片:与文本共用串行链和最小间隔,避免图片与进度/任务推送并发乱序。\n * 返回是否发出——图片无法像文本那样挂起补发(没有可重放的文本),失败由调用方在文字里附原链接。\n */\n async sendImageOrdered(to: string, ctx: string | undefined, image: Buffer): Promise<boolean> {\n let ok = true;\n this.trackInflight(to, 1);\n const task = this.replyChain\n .then(async () => {\n await this.waitGap();\n await this.deps.sendImage(to, ctx, image).catch((e) => {\n ok = false;\n this.deps.onSendError(e);\n });\n this.lastSendAt = this.deps.now();\n })\n .catch((e) => {\n ok = false;\n this.deps.log(\"send image chain error:\", String(e));\n })\n .finally(() => this.trackInflight(to, -1));\n this.replyChain = task;\n await task;\n return ok;\n }\n\n /**\n * 发一条「必须送达」的答复:先计入连发额度再排队。\n * 计数后仍走同一条串行链,保证与进度/答案不乱序、遵守最小间隔。\n */\n sendNotice(\n to: string,\n ctx: string | undefined,\n text: string,\n fallbackToken?: string,\n ): Promise<void> {\n this.budgetOf(to).count += 1;\n return this.sendOrdered(to, ctx, text, MessageState.FINISH, fallbackToken, (e) =>\n this.holdUndelivered(to, text, e),\n );\n }\n\n /** 把长回复作为 .md 文件发送(走同一串行链+节流);发文件失败则兜底回退为文本。 */\n sendMdOrdered(\n to: string,\n ctx: string | undefined,\n finalText: string,\n fallbackToken?: string,\n onError?: (e: unknown) => void,\n ): Promise<void> {\n this.trackInflight(to, 1);\n this.replyChain = this.replyChain\n .then(async () => {\n await this.waitGap();\n try {\n await this.deps.sendFileMd(to, ctx, finalText);\n } catch (e) {\n this.deps.log(\"send md file failed, fallback to text:\", String(e));\n await this.deps\n .sendText(to, ctx, finalText, MessageState.FINISH, fallbackToken)\n .catch((er) => {\n this.deps.onSendError(er);\n onError?.(er);\n });\n }\n this.lastSendAt = this.deps.now();\n })\n .catch((e) => this.deps.log(\"send md chain error:\", String(e)))\n .finally(() => this.trackInflight(to, -1));\n return this.replyChain;\n }\n\n /** -2 且该用户还没在本进程发过消息 → 旧 token,挂起等新 token 补发;限流与其它错误只记日志。 */\n holdUndelivered(to: string, text: string, e: unknown): void {\n if (!this.deps.isParamError(e) || this.freshTokenUsers.has(to)) return;\n const n = this.deps.pending.push(to, text);\n this.deps.log(`send -2(旧 token)→ 挂起,累计 ${n} 条,等该用户下一条消息补发(${to})`);\n }\n\n /** 补发又失败:重新挂起,试够上限就放弃——防一条永远被服务端拒绝的内容每次发消息都拖着串行链。 */\n requeueHeld(to: string, text: string, tries: number, e: unknown): void {\n const n = tries + 1;\n if (n >= PENDING_PUSH_MAX_TRIES) {\n this.deps.log(\n `补发 ${n} 次仍失败 → 放弃这条挂起的推送(${to}):${text.slice(0, 40)}(原因:${e instanceof Error ? e.message : String(e)})`,\n );\n return;\n }\n const size = this.deps.pending.push(to, text, n);\n this.deps.log(`补发 ${n} 次仍失败 → 重新挂起,累计 ${size} 条(${to})`);\n }\n\n /** 新 token 到了:按序补发,受单轮批量与「给最终答案留两条」双重限制;剩下的留给后续消息。 */\n flushHeldPushes(to: string, token: string): void {\n const room = Math.max(0, this.deps.consecLimit - 2 - this.budgetOf(to).count);\n if (room === 0) {\n this.deps.log(\n `挂起 ${this.deps.pending.size(to)} 条,但本回合连发额度已不够 → 留给后续消息补发(${to})`,\n );\n return;\n }\n const held = this.deps.pending.take(to, Math.min(this.deps.pendingBatch, room));\n if (held.length === 0) return;\n const rest = this.deps.pending.size(to);\n this.deps.log(\n `新 token 已到 → 补发挂起消息 ${held.length} 条(${to})${rest > 0 ? `,还有 ${rest} 条排在后续消息` : \"\"}`,\n );\n for (const item of held) {\n this.budgetOf(to).count += 1;\n void this.sendOrdered(to, token, item.text, MessageState.FINISH, token, (e) =>\n this.requeueHeld(to, item.text, item.tries, e),\n );\n }\n }\n\n /** 大脑回合接近连发上限时,任务推送先让出最后一条额度给最终答案。 */\n deferTaskPush(to: string, text: string): number {\n const list = this.deferredTaskPushes.get(to) ?? [];\n list.push(text);\n this.deferredTaskPushes.set(to, list);\n return list.length;\n }\n\n takeDeferredTaskPushes(to: string): string[] {\n const list = this.deferredTaskPushes.get(to);\n if (!list) return [];\n this.deferredTaskPushes.delete(to);\n return list;\n }\n\n /** 回合结束后补发被让位的任务推送:还有额度就立刻发;没有就转挂起队列,等该用户下一条消息。 */\n flushDeferredTaskPushes(to: string, token: string | undefined, finalSendFailed: boolean): void {\n const items = this.takeDeferredTaskPushes(to);\n if (items.length === 0) return;\n const b = this.budgetOf(to);\n for (const text of items) {\n if (!finalSendFailed && b.count < this.deps.consecLimit) {\n b.count += 1;\n void this.sendOrdered(to, token, text, MessageState.FINISH, token, (e) =>\n this.holdUndelivered(to, text, e),\n );\n } else {\n const n = this.deps.pending.push(to, text);\n this.deps.log(\n `任务推送让位后仍无额度 → 挂起,等该用户下一条消息补发(累计 ${n} 条,${to})`,\n );\n }\n }\n }\n\n /** 等串行链上已排队的发送全部落定(测试用;运行代码无需调用)。 */\n async idle(): Promise<void> {\n await this.replyChain;\n }\n}\n","/**\n * 一批消息处理完后的收尾:先唤醒 worker,再持久化状态。\n * 顺序刻意如此——`saveState` 失败(磁盘满/权限)不能把已经入队的消息卡在队列里,否则 worker 会一直睡到用户下一条消息。\n * 返回持久化错误(成功返回 undefined),调用方负责记日志。\n */\nexport function finishPollBatch(enq: boolean, save: () => void, wake: () => void): unknown {\n if (enq) wake();\n try {\n save();\n return undefined;\n } catch (e) {\n return e;\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * 任务收件箱的文件协议(CLI ↔ daemon):**收命令通道**(`task-inbox/`)与**回结果通道**(`task-out/`)。\n *\n * 为什么要有回结果通道:`wemind task` / `task-reply` / `task-stop` 原先只是把命令丢进收件箱就返回,\n * 于是它们只能靠轮询 `task-state.json` 猜结果——「达上限被拒绝派发」会被报成「已派发」,\n * 甚至报成**上一条**任务的编号(`active` 本来就是 true),「回复不存在的编号」也会被报成「已发送」。\n * 回结果通道让 daemon 把**真实结果**写回来,CLI 打印的就是 daemon 的那句话。\n *\n * 兼容:老 daemon 不认这条通道(不会写答复),CLI 等不到就退回原来的「已发送」措辞并说明一句,\n * 而不是把「没等到」当成「成功了」。\n */\n\nexport const TASK_INBOX_DIRNAME = \"task-inbox\";\nexport const TASK_OUT_DIRNAME = \"task-out\";\n\nexport type TaskAck = { id: string; at: number; ok: boolean; text: string };\n\nexport const taskInboxDir = (base: string): string => path.join(base, TASK_INBOX_DIRNAME);\nexport const taskOutDir = (base: string): string => path.join(base, TASK_OUT_DIRNAME);\n\n/** 文件 id 只允许安全字符:它直接拼进 `task-out/<id>.json`,不能让 `../`、盘符或路径分隔符逃出目录。 */\nfunction isSafeFileId(id: string): boolean {\n return /^[A-Za-z0-9._-]+$/.test(id) && !id.includes(\"..\") && !id.includes(\":\");\n}\n\n/** 原子写(临时文件 + rename):daemon 扫目录时不会读到半截 JSON。 */\nfunction atomicWriteJson(fp: string, obj: unknown): void {\n fs.mkdirSync(path.dirname(fp), { recursive: true });\n const tmp = `${fp}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(obj, null, 2), \"utf-8\");\n fs.renameSync(tmp, fp);\n}\n\n/**\n * 写一条命令进收件箱,返回它的答复编号(同时写进 `ack` 字段;老 daemon 忽略这个字段)。\n * 文件名用同一个 id:带时间戳前缀,daemon 按名字排序即按时间排序。\n */\nexport function writeTaskCmd(base: string, obj: Record<string, unknown>): string {\n // id 与 ts 取同一次 Date.now():分两次取时一旦跨毫秒边界,id 的时间戳前缀就与 ts 对不上(「按名排序即按时间排序」依赖它)。\n const ts = Date.now();\n const id = `${ts}-${Math.random().toString(36).slice(2, 8)}`;\n atomicWriteJson(path.join(taskInboxDir(base), `${id}.json`), { ...obj, ack: id, ts });\n return id;\n}\n\n/** daemon 侧:把一条命令的结果写回去(CLI 正在短轮询它)。写失败不影响命令本身。 */\nexport function writeTaskAck(base: string, id: string | undefined, text: string): void {\n if (!id || !isSafeFileId(id)) return;\n try {\n atomicWriteJson(path.join(taskOutDir(base), `${id}.json`), {\n id,\n at: Date.now(),\n ok: !text.startsWith(\"❌\"),\n text,\n });\n } catch {\n /* 答复写不进去不该影响命令本身(CLI 会回退到「已发送」措辞) */\n }\n}\n\n/** CLI 侧:等这次命令的答复(读到即删,目录不留垃圾)。超时返回 null。 */\nexport async function awaitTaskAck(\n base: string,\n id: string,\n timeoutMs = 8000,\n pollMs = 150,\n): Promise<TaskAck | null> {\n if (!isSafeFileId(id)) return null;\n const fp = path.join(taskOutDir(base), `${id}.json`);\n const t0 = Date.now();\n for (;;) {\n try {\n const a = JSON.parse(fs.readFileSync(fp, \"utf-8\")) as TaskAck;\n fs.unlinkSync(fp);\n if (a?.id === id && typeof a.text === \"string\") return a;\n } catch {\n /* 还没到 */\n }\n if (Date.now() - t0 >= timeoutMs) return null;\n await new Promise((r) => setTimeout(r, pollMs));\n }\n}\n\n/** 启动 / 收尾清理:作废上一条命令的残留(防重放),两个通道一起清。 */\nexport function clearTaskDirs(base: string): void {\n for (const d of [taskInboxDir(base), taskOutDir(base)]) {\n try {\n fs.rmSync(d, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n }\n try {\n fs.mkdirSync(taskInboxDir(base), { recursive: true });\n } catch {\n /* ignore */\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { taskInboxDir } from \"./task-inbox.js\";\n\n/** 收件箱命令执行方:由 daemon 注入 task-manager 的真实实现(测试可注入假实现)。 */\nexport type TaskInboxOps = {\n dispatch: (\n owner: string,\n text: string,\n opts: { cwd?: string; agentKey?: string; ackId?: string },\n ) => string;\n inject: (owner: string, text: string, id?: number, ackId?: string) => string;\n stop: (owner: string, id?: number | \"all\", ackId?: string) => string;\n writeAck: (ackId: string | undefined, text: string) => void;\n log: (...args: unknown[]) => void;\n};\n\n/**\n * 扫描 `task-inbox/`:按文件名(时间戳前缀)升序逐个读、分发到 task-manager、先删文件再执行。\n * 抽成注入式函数是为了让「坏文件丢弃 / 出错回 ack / 继续处理下一条」这些编排可单测(见 `task-inbox-runner.test.ts`)。\n */\nexport function scanTaskInbox(dataDir: string, owner: string, ops: TaskInboxOps): void {\n const dir = taskInboxDir(dataDir);\n let files: string[];\n try {\n files = fs.readdirSync(dir).filter((f) => f.endsWith(\".json\"));\n } catch {\n return;\n } // 目录不存在/读失败:跳过\n if (!files.length) return;\n files.sort(); // 文件名带时间戳前缀,字典序即时间序\n for (const f of files) {\n const fp = path.join(dir, f);\n let cmd: {\n kind?: string;\n text?: string;\n cwd?: string;\n agent?: string;\n id?: number | string;\n ack?: string;\n };\n try {\n cmd = JSON.parse(fs.readFileSync(fp, \"utf-8\"));\n } catch {\n try {\n fs.unlinkSync(fp);\n } catch {\n /* ignore */\n }\n continue;\n } // 坏文件丢弃\n try {\n fs.unlinkSync(fp);\n } catch {\n /* ignore */\n } // 先删,避免重复处理\n try {\n if (cmd.kind === \"dispatch\") {\n // 答复由 task-manager 在**起进程有结果之后**发(达上限/起不来都是那时才知道),这里只把 ack id 递进去\n ops.log(\n \"local task dispatch:\",\n ops\n .dispatch(owner, String(cmd.text ?? \"\"), {\n ...(cmd.cwd ? { cwd: String(cmd.cwd) } : {}),\n ...(cmd.agent ? { agentKey: String(cmd.agent) } : {}),\n ...(cmd.ack ? { ackId: String(cmd.ack) } : {}),\n })\n .slice(0, 60),\n );\n } else if (cmd.kind === \"reply\") {\n const id = Number(cmd.id) > 0 ? Number(cmd.id) : undefined;\n const ackId = cmd.ack === undefined ? undefined : String(cmd.ack);\n const r = ops.inject(owner, String(cmd.text ?? \"\"), id, ackId);\n ops.log(\n \"local task reply:\",\n id ? `#${id}` : \"-\",\n String(cmd.text ?? \"\").slice(0, 40),\n r.slice(0, 40),\n );\n } else if (cmd.kind === \"stop\") {\n ops.log(\n \"local task stop:\",\n ops.stop(\n owner,\n cmd.id === \"all\" ? \"all\" : Number(cmd.id) > 0 ? Number(cmd.id) : undefined,\n cmd.ack === undefined ? undefined : String(cmd.ack),\n ),\n );\n } else {\n ops.log(\"unknown task-inbox cmd:\", cmd.kind);\n ops.writeAck(\n cmd.ack === undefined ? undefined : String(cmd.ack),\n `❌ 不认识的任务命令:${cmd.kind}`,\n );\n }\n } catch (e) {\n ops.log(\"task-inbox dispatch error:\", String(e));\n ops.writeAck(\n cmd.ack === undefined ? undefined : String(cmd.ack),\n `❌ 命令执行出错:${String(e)}`,\n );\n }\n }\n}\n","import type { WeixinMessage } from \"@wemind/wechat-ilink-core\";\nimport { MessageItemType } from \"@wemind/wechat-ilink-core\";\nimport { authHint } from \"./brain-agents.js\";\nimport {\n parseChatCommand,\n parseTaskCommand,\n type ChatCommand,\n type TaskCommand,\n} from \"./chat-commands.js\";\nimport { providerIssue, shouldResumeTask } from \"./task-manager.js\";\n\n/**\n * 三档进度(按最终答案到达时间):\n * - <5s:只发最终答案;\n * - 5–10s:第 5s 发「收到」;\n * - ≥10s:第 5s 发「收到」,第 10s 再发第二条(正文已开始→「整理回复」,否则→「还在处理中」)。\n * 固定提示让位于已有进度:5s 时已发过工具/心跳/图片/重试提示就不再发确认;10s 的第二条要求距上一条非确认进度 ≥5s。\n */\nexport const PROGRESS_ACK_DELAY_MS = 5_000;\nexport const PROGRESS_SECOND_NOTICE_MS = 10_000;\nexport const PROGRESS_NOTICE_GAP_MS = 5_000;\n\n/** 第 5s 的即时确认:只在还没有任何可见进度时发(有进度就顶替它)。 */\nexport function shouldShowAck(progressSent: boolean): boolean {\n return !progressSent;\n}\n\n/** 第 10s 的第二条:最终答案未发出,且距上一条非确认进度至少 5s(确认本身不算,否则它会挡住第二条)。 */\nexport function shouldShowSecondNotice(\n elapsedMs: number,\n finalStarted: boolean,\n sinceNonAckProgressMs: number,\n): boolean {\n return (\n !finalStarted &&\n elapsedMs >= PROGRESS_SECOND_NOTICE_MS &&\n sinceNonAckProgressMs >= PROGRESS_NOTICE_GAP_MS\n );\n}\n\n/** 第 10s 第二条的措辞:正文已开始 → 整理回复;还在思考/跑工具 → 还在处理中。 */\nexport function secondNoticeText(answerStarted: boolean): string {\n return answerStarted ? \"✍️ 正在整理回复…\" : \"⏳ 还在处理中…\";\n}\n\n/** 分类大脑错误:backend=模型后端瞬时错误(可多重试);timeout=超时/卡死;other=其它。 */\nfunction classifyBrainError(e: unknown): \"timeout\" | \"backend\" | \"other\" {\n const s = String(e instanceof Error ? e.message : e);\n if (\n /-32603|internal error|please try again|response stream|overloaded|rate.?limit|\\b(429|503)\\b/i.test(\n s,\n )\n )\n return \"backend\";\n if (/idle timeout|no-first-frame|cap timeout|timeout/i.test(s)) return \"timeout\";\n return \"other\";\n}\n/**\n * 一次大脑失败该怎么处置:provider 侧(余额/额度、密钥鉴权、计费、模型不存在)是**永久**错误——\n * 重试只是又白失败一轮,何况每次重试都要先冷启动一个 agent 进程;这类只试 1 次,直接把原因告诉用户。\n * 其余沿用老规矩:backend(`-32603`/限流等瞬时错误)3 次,timeout/其它 2 次。\n */\nexport function brainFailurePlan(e: unknown): { attemptLimit: number; category: string } {\n const prov = providerIssue(e);\n if (prov) return { attemptLimit: 1, category: `provider:${prov}` };\n const cat = classifyBrainError(e);\n return { attemptLimit: cat === \"backend\" ? 3 : 2, category: cat };\n}\n/** 按错误类型给用户的失败提示。`agentName` 用于给特定 agent 补专属建议(如 Cline 的自定义 provider 要设环境变量)。 */\nexport function brainErrorMessage(e: unknown, agentName?: string): string {\n const prov = providerIssue(e);\n if (prov) {\n // 余额/计费在 provider 侧处理完,重发消息即可(daemon 不用重启);\n // 密钥/模型类常常是刚在本机改过登录态或 brain.json——运行中的 daemon 不重读配置、也不重新同步凭据(槽位启动时种入),\n // 必须重启一次才生效,所以这句提示要说全,否则用户会陷入「改了、重发、还是错」。\n // Cline 是例外:自定义 provider 只认启动环境里的 CLINE_PROVIDER/CLINE_API_KEY,重启不带变量照样失败——\n // 用 agent 专属提示顶掉通用那句(`authHint`)。\n const auth = agentName ? authHint(agentName, String(e instanceof Error ? e.message : e)) : \"\";\n const hint = auth\n ? ` ${auth}`\n : prov === \"密钥/鉴权有问题\" || prov === \"模型不可用\"\n ? \"若刚在本机改过登录态或模型配置,还要重启一次 daemon(wemind stop && wemind start)才会读到。\"\n : \"\";\n return `(这次没答上来:${prov}。这类问题重试不会好——把账号/模型处理好后把消息再发一次就行。${hint})`;\n }\n switch (classifyBrainError(e)) {\n case \"backend\":\n return \"(模型后端临时出错了,自动重试多次仍失败,请稍后把消息再发一次)\";\n case \"timeout\":\n return \"(这次处理超时/卡住被中断了,重试也没成功——可把任务拆小一点再发)\";\n default:\n return \"(处理出错了,请把消息再发一次试试)\";\n }\n}\n\nexport function extractText(msg: WeixinMessage): string {\n return (msg.item_list ?? [])\n .map((it) => {\n if (it.type === MessageItemType.TEXT) return it.text_item?.text ?? \"\";\n if (it.type === MessageItemType.VOICE) return it.voice_item?.text ?? \"\"; // 微信侧语音转文字(省事路)\n return \"\";\n })\n .filter((s) => s)\n .join(\"\\n\")\n .trim();\n}\n\n/** 工具标题中文化(无 purpose 时的兜底)。 */\nexport function zhTool(t: string): string {\n return t\n .replace(/^Searching the web for:?\\s*/i, \"联网搜索:\")\n .replace(/^Running:?\\s*/i, \"执行:\")\n .replace(/^Reading\\s+/i, \"读取 \")\n .replace(/^Writing\\s+/i, \"写入 \")\n .replace(/^Finding\\s+/i, \"查找 \");\n}\n\n/** 工具类型 → 中文动词(有 detail 时拼成「动词 参数」;没有 detail 则回退到标题)。 */\nconst TOOL_VERB: Record<string, string> = {\n read: \"读取\",\n edit: \"修改\",\n write: \"写入\",\n execute: \"执行\",\n search: \"搜索\",\n fetch: \"访问\",\n delete: \"删除\",\n};\n\n/**\n * 工具步骤的可读文案:优先 agent 自己给的人话 purpose,其次从 rawInput 取出的短参数(command/路径/query…),\n * 最后才用工具标题。长命令由协议层截断,这里只负责语义化,避免微信里出现整段原始参数。\n */\nexport function toolSummary(\n kind?: string,\n title?: string,\n purpose?: string,\n detail?: string,\n): string {\n const p = purpose?.trim();\n if (p) return zhTool(p);\n const d = detail?.trim();\n if (d) {\n const verb = TOOL_VERB[kind ?? \"\"];\n return verb ? `${verb} ${d}` : d;\n }\n if (title?.trim()) return zhTool(title.trim());\n return \"调用工具\";\n}\n\n/** 按工具类型给图标。 */\nexport function toolIcon(kind?: string): string {\n switch (kind) {\n case \"read\":\n return \"📄\";\n case \"search\":\n return \"🔍\";\n case \"execute\":\n return \"⌨️\";\n case \"fetch\":\n return \"🌐\";\n case \"edit\":\n case \"write\":\n return \"✏️\";\n case \"delete\":\n return \"🗑️\";\n default:\n return \"🔧\";\n }\n}\n\n/**\n * 解析控制指令(命中则由 daemon 直接处理,不转发给 brain)。\n * 表与解析逻辑都在 chat-commands.ts(`manage.ts` 也要用它渲染 `wemind help`,故不能留在这里)。\n */\nexport function parseControl(text: string): ChatCommand | null {\n return parseChatCommand(text);\n}\n\n/** 解析「明确发给 worker」指令:任务 [agent:]正文 / >xxx / #编号 正文 / 任务列表 / 结束任务 [编号]。 */\nexport function parseTaskControl(text: string): TaskCommand | null {\n return parseTaskCommand(text);\n}\n\n/** 解析「明确问大脑」逃逸前缀:问大脑 xxx / 问大脑:xxx。命中返回去前缀后的文本(强制走 brain,即使 worker 在等你)。 */\nexport function parseBrainEscape(text: string): string | null {\n const m = text.trim().match(/^问大脑[::]?\\s*([\\s\\S]+)$/);\n return m ? m[1].trim() : null;\n}\n\n/** 判断「继续」类短句时要用到的任务现状(由 task-manager 提供,纯函数便于单测)。 */\nexport type TaskFacts = { awaiting: number; active: boolean; interrupted: number };\n\n/**\n * 这条消息是否走**快车道**:控制指令与任务指令不经过大脑,不该排在一条十几分钟的大脑回合后面。\n * 「继续」类短句在确实有可续跑任务时也走快车道;问大脑前缀永远走大脑。\n * 注意:切脑 / 会话 / 交接也在快车道,但它们要操作同一个 ACP 连接,仍会在 ACP 门口等当前回合结束——那是语义要求,不是排队阻塞。\n */\nexport function isFastLane(text: string, tasks: TaskFacts): boolean {\n const t = (text ?? \"\").trim();\n if (!t || parseBrainEscape(t)) return false;\n if (parseControl(t) || parseTaskControl(t)) return true;\n return shouldResumeTask(t, tasks);\n}\n\n/** 大脑队列里的无编号消息是否该交给任务:只有「继续」类短句 + 确实有可续跑任务;带图片的消息永远给大脑。 */\nexport function shouldResumeInBrainQueue(\n text: string,\n imageCount: number,\n tasks: TaskFacts,\n): boolean {\n return imageCount === 0 && shouldResumeTask(text, tasks);\n}\n","#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n getUpdates,\n sendMessage,\n sendTyping,\n getConfig,\n notifyStart,\n generateId,\n SESSION_EXPIRED_ERRCODE,\n isSessionPaused,\n pauseSession,\n getRemainingPauseMs,\n loadAccount,\n loadState,\n saveState,\n DATA_DIR,\n MessageItemType,\n MessageState,\n MessageType,\n downloadImage,\n sendImage,\n sendFile,\n ILinkError,\n classifyTransportError,\n isAllowed,\n addAllowFrom,\n readAllowFrom,\n} from \"@wemind/wechat-ilink-core\";\nimport type {\n AccountData,\n ServerState,\n SendMessageReq,\n ImageItem,\n} from \"@wemind/wechat-ilink-core\";\nimport { askBrain, supportsImages, heartbeatMs } from \"./acp-brain.js\";\nimport { agentProblem, BRAIN_CONFIG_EXIT_CODE } from \"./brain-agents.js\";\nimport { switchInFlightTarget } from \"./brain-switch.js\";\nimport { loadBrainConfig } from \"./brain-config.js\";\nimport { getAgent, seedSlots } from \"./agent-slot.js\";\nimport { writeBrainState } from \"./brain-state.js\";\nimport { CLI_PID_MARKERS, otherOurProcessPid } from \"./proc-identity.js\";\nimport { DAEMON_ALREADY_RUNNING_EXIT_CODE } from \"./watchdog-runner.js\";\nimport { isOwnerOnly } from \"./chat-commands.js\";\nimport { ensureBrainHome } from \"./mcp-sandbox.js\";\nimport { syncFromPointerMap } from \"./session-registry.js\";\nimport { clearControlDirs, markBridgeReady } from \"./control-bridge.js\";\nimport { ControlInboxRunner } from \"./control-inbox-runner.js\";\nimport { clearHandoff, handoffPrefix, loadHandoff, takeHandoffFor } from \"./handoff.js\";\nimport {\n canSendImage,\n extractImages,\n imageFailureNote,\n planImageTurn,\n resolveImageBytes,\n type ImageFailure,\n} from \"./image-reply.js\";\nimport { LONG_REPLY_THRESHOLD, buildMdReply } from \"./md-reply.js\";\nimport { isAuthorizedSender } from \"./access.js\";\nimport { handleControl, handleControlRequest } from \"./daemon-control.js\";\nimport { shouldDeferTaskPush } from \"./pending-push.js\";\nimport { SendPipeline } from \"./send-pipeline.js\";\nimport { finishPollBatch } from \"./poller-batch.js\";\nimport {\n dispatchTask,\n injectTask,\n stopTask,\n hasActiveTask,\n awaitingTasks,\n interruptedTasks,\n shouldResumeTask,\n renderTaskList,\n initTasks,\n setTaskPush,\n setTaskTyping,\n setTaskStateSink,\n setTaskAckSink,\n type TaskStateSnapshot,\n} from \"./task-manager.js\";\nimport { clearTaskDirs, writeTaskAck } from \"./task-inbox.js\";\nimport { scanTaskInbox } from \"./task-inbox-runner.js\";\nimport { killAll as killWorkerPool, sweep as sweepWorkerPool } from \"./worker-pool.js\";\nimport { ensureWorkerHome } from \"./worker-sandbox.js\";\n\nconst log = (...a: unknown[]) => console.error(\"[wemind/wechat-daemon]\", ...a);\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nimport {\n brainErrorMessage,\n brainFailurePlan,\n extractText,\n isFastLane,\n parseBrainEscape,\n parseControl,\n parseTaskControl,\n PROGRESS_ACK_DELAY_MS,\n PROGRESS_SECOND_NOTICE_MS,\n secondNoticeText,\n shouldResumeInBrainQueue,\n shouldShowAck,\n shouldShowSecondNotice,\n toolIcon,\n toolSummary,\n type TaskFacts,\n} from \"./daemon-routing.js\";\n\nexport {\n brainErrorMessage,\n brainFailurePlan,\n isFastLane,\n parseBrainEscape,\n parseControl,\n parseTaskControl,\n shouldResumeInBrainQueue,\n} from \"./daemon-routing.js\";\nexport type { TaskFacts } from \"./daemon-routing.js\";\n\nasync function setTyping(\n account: AccountData,\n to: string,\n ctx: string | undefined,\n status: 1 | 2,\n): Promise<void> {\n if (!ctx) return;\n try {\n const cfg = await getConfig({\n baseUrl: account.baseUrl,\n token: account.botToken,\n ilinkUserId: to,\n contextToken: ctx,\n });\n await sendTyping({\n baseUrl: account.baseUrl,\n token: account.botToken,\n body: { ilink_user_id: to, typing_ticket: cfg.typing_ticket, status },\n });\n } catch (e) {\n // typing 是体验增强(best-effort 不阻断),但失败要留痕:区分「偶发网络失败」与「ticket 拿不到」\n log(`setTyping(${status}) failed:`, String(e));\n }\n}\n\nasync function reply(\n account: AccountData,\n to: string,\n ctx: string | undefined,\n text: string,\n state: number = MessageState.FINISH,\n fallbackToken?: string,\n): Promise<void> {\n const send = (token: string | undefined) => {\n const req: SendMessageReq = {\n msg: {\n from_user_id: \"\",\n to_user_id: to,\n client_id: generateId(\"wemind/wechat-daemon\"),\n message_type: MessageType.BOT,\n message_state: state,\n item_list: [{ type: MessageItemType.TEXT, text_item: { text } }],\n context_token: token,\n },\n };\n return sendMessage({ baseUrl: account.baseUrl, token: account.botToken, body: req });\n };\n try {\n await send(ctx);\n } catch (e) {\n // ret=-2 既可能是「限流」(rapid sends)也可能是「context_token 失效/不匹配」(参数错误)。\n // 两者都靠:退避 ~1.5s 后重试一次——限流靠等待恢复,失效 token 则换用该用户最新缓存 token。\n if (e instanceof ILinkError && e.kind === \"param_error\") {\n log(\"send -2(限流或token失效) → 退避 1.5s 后重试\");\n await new Promise((r) => setTimeout(r, 1500));\n await send(fallbackToken && fallbackToken !== ctx ? fallbackToken : ctx);\n return;\n }\n // 传输层错误(网络抖动 fetch failed/ECONNRESET/超时等):可重试类退避 1.5s 后重试一次\n if (!(e instanceof ILinkError)) {\n const t = classifyTransportError(e);\n if (t.retryable) {\n log(`send 传输错误(${t.kind}) → 退避 1.5s 后重试`);\n await new Promise((r) => setTimeout(r, 1500));\n await send(ctx);\n return;\n }\n }\n throw e;\n }\n}\n\n/** 统一的发送错误处理:按错误类型分类记录;会话过期则暂停账号。 */\nfunction handleSendError(e: unknown, account: AccountData): void {\n if (e instanceof ILinkError) {\n log(\"send error:\", e.message);\n if (e.kind === \"session_expired\") {\n pauseSession(account.botId);\n log(\"会话过期(-14) on send → 该账号暂停 1h,待重新登录\");\n }\n } else {\n const t = classifyTransportError(e);\n log(`send error(传输/${t.kind}${t.retryable ? \",可重试\" : \"\"}):`, t.message);\n }\n}\n\n// iLink 在用户回复前能连续发送的消息数上限(实测约 10,可调)。\nconst CONSEC_SEND_LIMIT = Number(process.env.WECHAT_CONSEC_SEND_LIMIT) || 10;\n// 静默期心跳默认 15s(配置可放宽,如 DSH 的 60s);间隔在每回合开头算,见 handleMessage 的 heartbeatMsValue。\nconst HEARTBEAT_DEFAULT_MS = 15_000;\n\n/** -2(param_error):既可能是限流、也可能是 token 还是旧的——两者都值得「等该用户下一条消息再试」。 */\nconst isParamError = (e: unknown): boolean => e instanceof ILinkError && e.kind === \"param_error\";\n\n/**\n * 外发管线(串行链、最小间隔、连发预算、旧 token 挂起/补发、任务推送让位)都在 `send-pipeline.ts`,\n * 这里只把它接到本账号的真实发送函数上。拆出去是为了让这些编排可以单测(见 `send-pipeline.test.ts`)。\n */\nfunction createSendPipeline(account: AccountData): SendPipeline {\n return new SendPipeline({\n sendText: (to, ctx, text, state, fallbackToken) =>\n reply(account, to, ctx, text, state, fallbackToken),\n sendImage: (to, ctx, image) =>\n sendImage({\n baseUrl: account.baseUrl,\n token: account.botToken,\n to,\n image,\n contextToken: ctx,\n }),\n sendFileMd: async (to, ctx, text) => {\n const { fileName, data } = buildMdReply(text);\n await sendFile({\n baseUrl: account.baseUrl,\n token: account.botToken,\n to,\n data,\n fileName,\n kind: \"file\",\n contextToken: ctx,\n });\n log(`final as md file: ${fileName} (${text.length} chars)`);\n },\n onSendError: (e) => handleSendError(e, account),\n isParamError,\n log,\n minGapMs: Number(process.env.WECHAT_MIN_SEND_GAP_MS) || 1500,\n consecLimit: CONSEC_SEND_LIMIT,\n });\n}\n\n// 当前进行中回合(worker 串行,只有一个):记录发往用户与所用 token(token 可被「继续」更新)。\nlet activeTurn: { from: string; token: string | undefined } | null = null;\n\n/** 从解密后的字节嗅探图片 MIME。 */\nfunction sniffMime(b: Buffer): string {\n const h = b.subarray(0, 4).toString(\"hex\");\n if (h.startsWith(\"89504e47\")) return \"image/png\";\n if (h.startsWith(\"47494638\")) return \"image/gif\";\n if (h.startsWith(\"52494646\")) return \"image/webp\";\n return \"image/jpeg\";\n}\n\n/**\n * 处理一条发给大脑的消息。返回**是否真的答成了**(`brainErrorMessage` 兜底算没答成)——\n * 交接摘要的消费就靠这个返回值:只有答成了才把摘要删掉(失败留着,下次还能用)。\n */\nasync function handleMessage(\n account: AccountData,\n sends: SendPipeline,\n ctx: string | undefined,\n to: string,\n text: string,\n imageItems: ImageItem[],\n createTimeMs?: number,\n latestToken?: () => string | undefined,\n): Promise<boolean> {\n const msgAge = () => (createTimeMs ? `${Math.round((Date.now() - createTimeMs) / 1000)}s` : \"?\"); // 这条消息已存在多久(诊断用)\n // activeTurn 只存当前回合的 from/token;连续发送计数用按用户、跨回合的 budgetOf(to)(不在回合开始清零)。\n activeTurn = { from: to, token: ctx };\n const cx = () => activeTurn?.token ?? ctx;\n await setTyping(account, to, cx(), 1);\n // 处理期间每 5s 续发\"正在输入\"(sendtyping 不计入连续消息上限),避免长任务时指示消失、用户以为卡了。\n // 间隔需小于客户端对 typing 的自动过期(约 5–8s),否则中间会出现指示闪烁的空洞。\n const keepAlive = setInterval(() => {\n void setTyping(account, to, cx(), 1).catch(() => {});\n }, 5000);\n\n const t0 = Date.now();\n let lastProgressAt = t0; // 心跳基线:任意帧(含思考/工具活动)都会刷新\n let lastNonAckProgressAt = t0; // 上一条除 5s 确认以外的可见进度\n let visibleProgressSent = false;\n let finalStarted = false;\n let answerStarted = false;\n let secondNoticeSent = false;\n // 三档进度:<5s 只发最终答案;5–10s 第 5s 发确认;≥10s 第 10s 再发第二条。\n // 固定提示让位于已有进度(工具/心跳/图片/重试),避免叠加;「正在输入」由 keepAlive 每 5s 续发。\n let ackTimer: NodeJS.Timeout | undefined;\n let secondTimer: NodeJS.Timeout | undefined;\n const cancelAck = () => {\n if (ackTimer) {\n clearTimeout(ackTimer);\n ackTimer = undefined;\n }\n };\n const cancelSecond = () => {\n if (secondTimer) {\n clearTimeout(secondTimer);\n secondTimer = undefined;\n }\n };\n // 心跳间隔**每回合**取一次:热切换大脑后新 agent 的心跳覆盖要立刻生效(改造前是 import 期定死的常量,\n // 切到 DSH(heartbeatMs=60s)却还按旧大脑的 15s 发心跳,白占微信连发额度)。\n const heartbeatMsValue =\n Number(process.env.WECHAT_BRAIN_HEARTBEAT_MS) || heartbeatMs() || HEARTBEAT_DEFAULT_MS;\n // iLink 限制 bot 在用户回复前连续发送的消息数(实测约 10,超出被 ret=-2 拒)。计数按用户跨回合维护(budgetOf)。\n // 当已发到「上限-2」(默认第8条)后,下一条进度改为「即将达上限」提示并进入等待,把最后一条额度留给最终答案;用户回复「继续」(或发任何消息)会重置计数恢复发送(见 poller)。\n const sendProgress = (msg: string, opts?: { ackOnly?: boolean }) => {\n cancelAck(); // 任何进度都顶替待发的即时确认\n lastProgressAt = Date.now();\n const b = sends.budgetOf(to);\n if (b.awaiting) return; // 等待「继续」期间不发任何进度\n const now = Date.now();\n visibleProgressSent = true;\n if (!opts?.ackOnly) lastNonAckProgressAt = now; // 5s 确认不能挡住 10s 的第二条\n // 发出一条文本会把客户端刚显示的「正在输入」顶掉 → 消息送达后立刻补发一次 typing,否则接下来的等待窗口看不到指示。\n const sendWithTyping = (text: string) => {\n void sends\n .sendOrdered(to, cx(), text, MessageState.GENERATING)\n .then(() => setTyping(account, to, cx(), 1))\n .catch(() => {});\n };\n if (b.count >= CONSEC_SEND_LIMIT - 2) {\n b.count += 1;\n b.awaiting = true;\n sendWithTyping(\n \"⚠️ 接近微信连续消息上限,我先暂停进度提示、继续在后台处理;完成后会把结果直接发给你。想立刻恢复进度可回复「继续」。\",\n );\n return;\n }\n b.count += 1;\n sendWithTyping(msg);\n };\n // 第 5s 的即时确认:只在之前没有任何可见进度时发(工具/心跳/图片提示都会顶替它)。有图片时图片提示即确认,不再重复发。\n if (imageItems.length === 0) {\n const t = cx();\n log(`turn start to=${to} msgAge=${msgAge()} usingCtx=…${t ? t.slice(-12) : \"NONE\"}`);\n ackTimer = setTimeout(() => {\n ackTimer = undefined;\n if (finalStarted || !shouldShowAck(visibleProgressSent)) return;\n sendProgress(\"🤖 收到,正在处理…\", { ackOnly: true });\n }, PROGRESS_ACK_DELAY_MS);\n ackTimer.unref?.();\n }\n // 第 10s 的第二条:正文已开始→「整理回复」,否则→「还在处理中」;距上一条非确认进度不足 5s 就跳过(工具/心跳刚提示过)。\n secondTimer = setTimeout(() => {\n secondTimer = undefined;\n const now = Date.now();\n if (secondNoticeSent || finalStarted) return;\n if (!shouldShowSecondNotice(now - t0, finalStarted, now - lastNonAckProgressAt)) {\n return;\n }\n secondNoticeSent = true;\n sendProgress(secondNoticeText(answerStarted));\n }, PROGRESS_SECOND_NOTICE_MS);\n secondTimer.unref?.();\n // 心跳:仅当 brain 真正静默(无任何帧)超过心跳间隔才提示——首条用友好措辞,后续才带计时\n let hbCount = 0;\n const heartbeat = setInterval(() => {\n if (Date.now() - lastProgressAt >= heartbeatMsValue) {\n hbCount += 1;\n sendProgress(\n hbCount === 1\n ? \"🤔 还在思考/检索中…\"\n : `⏳ 仍在处理中…(已 ${Math.round((Date.now() - t0) / 1000)} 秒)`,\n );\n }\n }, heartbeatMsValue);\n\n // 收图:下载并解密(失败的跳过)\n const images: Array<{ data: string; mimeType: string }> = [];\n if (imageItems.length > 0) {\n sendProgress(`🖼️ 收到 ${imageItems.length} 张图片,下载识别中…`);\n for (const im of imageItems) {\n try {\n const buf = await downloadImage(im);\n images.push({ data: buf.toString(\"base64\"), mimeType: sniffMime(buf) });\n } catch (e) {\n log(\"image download failed:\", String(e));\n }\n }\n }\n\n // 收图后怎么走:全部下载失败/大脑不支持时,有文字就只按文字答,没文字就直接拒绝(别拿空 prompt 浪费一回合)\n const plan = planImageTurn(imageItems.length, images.length, supportsImages(), !!text.trim());\n if (plan.kind === \"reject\") {\n clearInterval(keepAlive);\n clearInterval(heartbeat);\n finalStarted = true;\n cancelAck();\n cancelSecond();\n await setTyping(account, to, cx(), 2);\n sends.budgetOf(to).count += 1;\n let sendFailed = false;\n try {\n await sends.sendOrdered(to, cx(), plan.text, MessageState.FINISH, undefined, (e) => {\n sendFailed = true;\n sends.holdUndelivered(to, plan.text, e);\n });\n log(\"image unusable and no text; rejected gracefully\");\n } finally {\n sends.flushDeferredTaskPushes(to, latestToken ? latestToken() : cx(), sendFailed);\n activeTurn = null;\n }\n return false; // 没有答成:交接摘要不能被消费掉\n }\n const imageNote = plan.kind === \"text-only\" ? plan.note : \"\";\n if (imageNote) log(`image ignored (${images.length}/${imageItems.length} usable): ${imageNote}`);\n\n // 一次尝试:带进度(步骤编号每次尝试重置)。进度消息节流:避免一回合刷十几条触发 iLink 发送限流(疑似 ret=-2 主因)\n const attempt = (promptText: string, imgs: typeof images): Promise<string> => {\n answerStarted = false; // 每次重试重新判断本轮正文是否开始,避免上一轮失败尝试的残留\n let step = 0;\n let lastToolProgressAt = 0;\n return askBrain(\n to,\n promptText,\n (ev) => {\n if (ev.type === \"tool\") {\n lastProgressAt = Date.now(); // 工具活动刷新心跳基线\n // 首条立即发(让你知道开始干活了),之后每 15s 最多一条,避免刷屏 + 触发限流\n if (Date.now() - lastToolProgressAt >= 15000) {\n lastToolProgressAt = Date.now();\n step += 1; // 只在真正发出时递增,保证步骤号连续(不因节流跳号)\n const desc = toolSummary(ev.kind, ev.title, ev.purpose, ev.detail);\n sendProgress(`${toolIcon(ev.kind)} 步骤 ${step}:${desc}`);\n }\n } else if (ev.type === \"thinking\") {\n lastProgressAt = Date.now();\n } else if (ev.type === \"answer\") {\n answerStarted = true; // 只用于第 10s 第二条选词;不再额外补发「整理回复」\n } else {\n // activity:brain 产出任意帧 → 静默刷新心跳基线,避免流式输出期间误报\"仍在处理\"\n lastProgressAt = Date.now();\n }\n },\n imgs,\n );\n };\n\n // 带分类的重试:provider 侧永久错误只试 1 次;backend(模型后端瞬时错误 -32603 等)最多 3 次;timeout/other 最多 2 次\n const runWithRetry = async (promptText: string, imgs: typeof images): Promise<string> => {\n let n = 0;\n while (true) {\n n++;\n try {\n return await attempt(promptText, imgs);\n } catch (e) {\n const plan = brainFailurePlan(e);\n log(`brain error (try ${n}, ${plan.category}): ${String(e)}`);\n if (n < plan.attemptLimit) {\n sendProgress(\n plan.category === \"backend\"\n ? \"⚠️ 模型后端临时出错,正在重试…\"\n : \"⏳ 响应中断了,正在自动重试…\",\n );\n continue;\n }\n throw e;\n }\n }\n };\n\n const imagesForBrain = plan.kind === \"text-only\" ? [] : images; // 大脑不支持看图时别再塞图片(会 -32602)\n let answer: string;\n let ok = true;\n try {\n answer = await runWithRetry(text, imagesForBrain);\n } catch (e2) {\n ok = false; // 重试也失败 → 只是把错误提示当回复发出去,不算「答成」(交接摘要据此决定留不留)\n answer = brainErrorMessage(e2, getAgent(\"brain\").name);\n } finally {\n clearInterval(keepAlive);\n clearInterval(heartbeat);\n finalStarted = true;\n cancelAck();\n cancelSecond();\n }\n await setTyping(account, to, cx(), 2);\n // 发图:从回复里提取图片引用(markdown  或裸图片 URL),逐个作为图片消息发出,再发剩余文本\n const { images: replyImages, text: cleaned } = extractImages(answer || \"\");\n const failedImgs: ImageFailure[] = [];\n for (const im of replyImages) {\n let buf: Buffer;\n try {\n buf = await resolveImageBytes(im.src);\n } catch (e) {\n log(\"image download failed:\", im.src, String(e));\n failedImgs.push({ src: im.src, reason: \"download\" });\n continue;\n }\n if (!canSendImage(sends.budgetOf(to).count, CONSEC_SEND_LIMIT)) {\n log(\"image skipped (连发额度留给最终文字):\", im.src);\n failedImgs.push({ src: im.src, reason: \"budget\" });\n continue;\n }\n sends.budgetOf(to).count += 1; // 图片也占一条连发额度\n if (await sends.sendImageOrdered(to, cx(), buf)) {\n log(`sent image: ${im.src.slice(0, 80)}`);\n } else {\n log(\"send image failed:\", im.src);\n failedImgs.push({ src: im.src, reason: \"send\" });\n }\n }\n let finalText = cleaned;\n if (imageNote) finalText = finalText ? `${imageNote}\\n${finalText}` : imageNote;\n const note = imageFailureNote(failedImgs);\n if (note) finalText = finalText ? `${finalText}\\n\\n${note}` : note;\n if (!finalText && !replyImages.length) finalText = \"(没有内容)\";\n let finalSendFailed = false;\n if (finalText) {\n sends.budgetOf(to).count += 1; // 最终答案占一条额度(等待段预留的最后一条)\n log(`final send: msgAge=${msgAge()} sent=${sends.budgetOf(to).count} len=${finalText.length}`);\n const ft = latestToken ? latestToken() : undefined;\n const onError = (e: unknown) => {\n finalSendFailed = true;\n sends.holdUndelivered(to, finalText, e);\n };\n if (finalText.length > LONG_REPLY_THRESHOLD) {\n await sends.sendMdOrdered(to, cx(), finalText, ft, onError); // 长回复 → .md 文件(一条消息)\n } else {\n await sends.sendOrdered(to, cx(), finalText, MessageState.FINISH, ft, onError);\n }\n }\n log(`replied to ${to} (msgAge=${msgAge()}): ${(answer || \"\").slice(0, 60)}`);\n sends.flushDeferredTaskPushes(to, latestToken ? latestToken() : cx(), finalSendFailed);\n activeTurn = null; // 回合结束(计数 budgetOf 不清零,留待用户下条消息重置)\n return ok;\n}\n\n// ===== 解耦:拉取器(poller) 与 处理器(worker) 通过队列协作 =====\n// 大脑(ACP session)严格串行,但「拉取消息/抓 token」无需串行。把两者解耦:\n// poller 持续 getUpdates、实时更新 token、把消息入队,永不被大脑阻塞;worker 串行从队列取消息喂大脑(尊重 kiro-cli 串行约束),发送时取最新 token,规避 ret=-2。\ntype QueuedMsg = {\n from: string;\n text: string;\n imageItems: ImageItem[];\n createTimeMs?: number;\n contextToken?: string;\n};\nconst msgQueue: QueuedMsg[] = [];\nlet wakeWorker: (() => void) | null = null;\nfunction notifyWorker(): void {\n const w = wakeWorker;\n wakeWorker = null;\n if (w) w();\n}\n/**\n * 快车道:控制指令与任务指令不进大脑队列。大脑一条回合可能跑十几分钟,若共用队列,\n * 「结束任务」「任务列表」「#n 继续」会一直排在那条回合后面——任务侧明明独立于大脑。\n * 快车道自己串行(命令之间保序),只与大脑并发。\n */\nconst fastQueue: QueuedMsg[] = [];\nlet wakeFast: (() => void) | null = null;\nfunction notifyFast(): void {\n const w = wakeFast;\n wakeFast = null;\n if (w) w();\n}\n\nasync function pollerLoop(\n account: AccountData,\n owner: string,\n state: ServerState,\n sends: SendPipeline,\n): Promise<void> {\n while (true) {\n if (isSessionPaused(account.botId)) {\n await sleep(Math.min(getRemainingPauseMs(account.botId), 60_000) || 60_000);\n continue;\n }\n let resp;\n try {\n resp = await getUpdates({\n baseUrl: account.baseUrl,\n token: account.botToken,\n updatesBuf: state.updatesBuf,\n timeoutMs: 25_000,\n });\n } catch (e) {\n log(\"getUpdates error:\", String(e));\n await sleep(2000);\n continue;\n }\n try {\n if (resp.errcode === SESSION_EXPIRED_ERRCODE) {\n log(\"session expired (-14); pausing 1h. Send a WeChat message to revive after re-login.\");\n pauseSession(account.botId);\n continue;\n }\n if (resp.errcode) {\n log(\"server error:\", resp.errcode, resp.errmsg);\n await sleep(2000);\n continue;\n }\n if (resp.get_updates_buf) state.updatesBuf = resp.get_updates_buf;\n\n const news = (resp.msgs ?? []).filter(\n (m) =>\n m.message_type === MessageType.USER && m.message_id && m.message_id > state.lastMessageId,\n );\n // 先登记本批所有最新 token(worker 发送时取最新)\n for (const m of news) {\n const f = m.from_user_id ?? \"\";\n if (isAuthorizedSender(f, owner, isAllowed))\n log(\n `recv msg id=${m.message_id} ctx=…${m.context_token ? m.context_token.slice(-12) : \"NONE\"} age=${m.create_time_ms ? Math.round((Date.now() - m.create_time_ms) / 1000) + \"s\" : \"?\"}`,\n );\n if (m.context_token) {\n state.contextTokens[`${account.botId}:${f}`] = m.context_token;\n sends.markFresh(f); // 本进程拿到的 token——此后该用户的 -2 不再当「旧 token」挂起\n }\n }\n let enq = false;\n for (const msg of news) {\n state.lastMessageId = Math.max(state.lastMessageId, msg.message_id ?? 0);\n const from = msg.from_user_id ?? \"\";\n if (!isAuthorizedSender(from, owner, isAllowed)) {\n log(\"ignore unauthorized sender:\", from);\n continue;\n }\n const text = extractText(msg);\n const voiceItem = (msg.item_list ?? []).find(\n (it) => it.type === MessageItemType.VOICE,\n )?.voice_item;\n if (voiceItem)\n log(\n `recv voice: text=${voiceItem.text ? `\"${voiceItem.text.slice(0, 30)}\"` : \"(空,无微信转写)\"} playtime=${voiceItem.playtime ?? \"?\"}`,\n );\n const imageItems = (msg.item_list ?? [])\n .filter((it) => it.type === MessageItemType.IMAGE && it.image_item)\n .map((it) => it.image_item as ImageItem);\n if (!text && imageItems.length === 0) {\n log(\"skip empty/unsupported message\");\n continue;\n }\n // 服务端按「距用户上一条消息」计连续发送数 → 任何来自 owner 的消息都重置该用户计数 + 退出等待段。但已经进了 replyChain、会在本条消息之后才上线路的发送(补发、上一回合没发完的)仍占新窗口的额度,所以不能直接清零,而是把它们算进来。\n const b = sends.budgetOf(from);\n const wasAwaiting = b.awaiting;\n b.count = sends.inflightCount(from);\n b.awaiting = false;\n // 「继续」且当前回合正处于「等待继续」段:换用本消息新 token 恢复,且仅作恢复信号、不入队\n if (text.trim() === \"继续\" && activeTurn && activeTurn.from === from && wasAwaiting) {\n activeTurn.token = msg.context_token ?? activeTurn.token;\n log(\"收到「继续」(等待中) → 恢复进度、重置计数、换新 token\");\n continue;\n }\n const queued: QueuedMsg = {\n from,\n text,\n imageItems,\n createTimeMs: msg.create_time_ms,\n contextToken: msg.context_token,\n };\n const facts: TaskFacts = {\n awaiting: awaitingTasks(from).length,\n active: hasActiveTask(from),\n interrupted: interruptedTasks(from).length,\n };\n if (isFastLane(text, facts)) fastQueue.push(queued);\n else msgQueue.push(queued);\n enq = true;\n }\n // 新 token 已到 → 补发旧 token 期间挂起的推送。等本批所有消息的计数重置都做完再补发(否则同一批里后一条消息的 reset 会把补发的计数抹掉);每个发件人一次,用 state 里该用户最新的 token。补发先进 replyChain,排在本批消息触发的进度/答案之前(该循环全程同步,worker 来不及插进来)。\n const flushTo = new Set<string>();\n for (const msg of news) {\n const from = msg.from_user_id ?? \"\";\n if (!msg.context_token || flushTo.has(from) || !isAuthorizedSender(from, owner, isAllowed))\n continue;\n flushTo.add(from);\n const fresh = state.contextTokens[`${account.botId}:${from}`];\n if (fresh) sends.flushHeldPushes(from, fresh);\n }\n const saveErr = finishPollBatch(\n enq,\n () => saveState(state),\n () => {\n notifyWorker();\n notifyFast();\n },\n );\n if (saveErr)\n log(\n \"save state failed:\",\n saveErr instanceof Error ? saveErr.stack || saveErr.message : String(saveErr),\n );\n } catch (e) {\n log(\"poller error:\", e instanceof Error ? e.stack || e.message : String(e));\n }\n }\n}\n\n/** 快车道的单条处理:控制指令 + 任务指令。它们不经过大脑,因此不必排在长回合后面。 */\nasync function handleFastItem(\n account: AccountData,\n sends: SendPipeline,\n state: ServerState,\n owner: string,\n item: QueuedMsg,\n): Promise<void> {\n const from = item.from;\n const token = item.contextToken;\n const latestToken = () => state.contextTokens[`${account.botId}:${from}`];\n /**\n * 快车道答复:正常立刻发;但若本用户的大脑回合正在跑、连发额度已到「接近上限」线,\n * 就先让位——否则会把留给最终答案的最后一条挤掉(答案的 -2 在本进程已收到过消息时按限流处理、不挂起,等于静默丢)。\n * 让位的答复在回合结束(flushDeferredTaskPushes)后发;仍没额度就转挂起队列。\n */\n const notice = (\n text: string,\n ctx: string | undefined = token,\n fallback: string | undefined = latestToken(),\n ): Promise<void> => {\n if (activeTurn?.from === from && sends.budgetOf(from).count >= CONSEC_SEND_LIMIT - 2) {\n const n = sends.deferTaskPush(from, text);\n log(`快车道答复让位给本回合最终答案 → 回合结束后发(累计 ${n} 条,${from})`);\n return Promise.resolve();\n }\n return sends.sendNotice(from, ctx, text, fallback);\n };\n const facts: TaskFacts = {\n awaiting: awaitingTasks(from).length,\n active: hasActiveTask(from),\n interrupted: interruptedTasks(from).length,\n };\n\n // 控制指令:单独处理\n const ctrl = item.text ? parseControl(item.text) : null;\n if (ctrl) {\n // owner-only(切脑/交接):非本人 → 短提示拒绝,**不**转发给大脑(否则大脑可能替用户执行越权动作)\n if (isOwnerOnly(ctrl) && from !== owner) {\n const r = \"这条指令仅登录者可用。发送「帮助」看你自己的指令。\";\n log(\"control: denied(not owner)\", JSON.stringify(ctrl));\n await notice(r);\n return;\n }\n // 补发用**最新**token(切换可能几十秒,期间用户又发过消息;旧 token 可能已失效)\n const notify = (text: string) => notice(text, latestToken() ?? token, latestToken());\n const notifyFile = (text: string) => {\n // 接近上限且本用户回合在跑:不再发文件占额度,退化为文本答复并参与「让位」判定(回合结束再发)。\n if (activeTurn?.from === from && sends.budgetOf(from).count >= CONSEC_SEND_LIMIT - 2)\n return notice(text);\n sends.budgetOf(from).count += 1; // .md 也是一条消息,占同一份额度\n return sends.sendMdOrdered(from, latestToken() ?? token, text, latestToken(), (e) =>\n sends.holdUndelivered(from, text, e),\n );\n };\n let r: string;\n try {\n r = await handleControl(ctrl, from, { owner, notify, notifyFile });\n } catch (e) {\n r = \"处理指令出错:\" + String(e);\n }\n log(\"control:\", JSON.stringify(ctrl), \"->\", r.replace(/\\n/g, \" | \"));\n await notice(r);\n return;\n }\n\n // ── 任务路由 ──\n // 1) 结束任务(可带编号/全部)\n const tcmd = item.text ? parseTaskControl(item.text) : null;\n if (tcmd?.kind === \"stop\") {\n const r = stopTask(from, tcmd.id);\n log(\"task: stop ->\", r.replace(/\\n/g, \" | \"));\n await notice(r);\n return;\n }\n // 2) 任务列表\n if (tcmd?.kind === \"tasks\") {\n await notice(renderTaskList(from));\n return;\n }\n // 3) 给指定编号的任务回话\n if (tcmd?.kind === \"reply\") {\n const r = injectTask(from, tcmd.text, tcmd.id);\n log(`task: inject(#${tcmd.id}):`, tcmd.text.slice(0, 40));\n if (r) await notice(r);\n return;\n }\n // 4) 派发:带 agent 名 = 指定 agent 起一条**新任务**(多任务并行);不带 = 沿用「有任务就发给它,没有就派发」\n if (tcmd?.kind === \"toWorker\" && tcmd.agentToken) {\n // 指定的 agent **只对这条任务生效**,不改默认任务大脑:那是账号级状态(`切换任务大脑` 是 owner-only),顺手改会让「派一条任务」变成一次隐形的全局设置变更,也破坏微信与 CLI(`--agent` 只影响那一条任务)两边的对称。\n const r = dispatchTask(from, tcmd.text, { agentKey: tcmd.agentToken });\n log(`task: dispatch(agent=${tcmd.agentToken}):`, r.replace(/\\n/g, \" | \"));\n await notice(r);\n return;\n }\n if (tcmd?.kind === \"toWorker\") {\n // 明确发给 worker:有活跃任务 → 发给它/追加(唯一一条喂下一轮、running 暂存、多条则回编号提示);\n // 没有活跃任务 → 派发新任务(不「续跑」已中断的:那是「继续」/`#n 继续` 的事,明确派发就该是新的活)。\n if (hasActiveTask(from)) {\n const r = injectTask(from, tcmd.text);\n log(\"task: inject(explicit):\", tcmd.text.slice(0, 40), \"->\", r.replace(/\\n/g, \" | \"));\n if (r) await notice(r);\n } else {\n const r = dispatchTask(from, tcmd.text);\n log(\"task: dispatch:\", r.replace(/\\n/g, \" | \"));\n await notice(r);\n }\n return;\n }\n\n // poller 按**入队时**的任务状态把「继续」类短句送进快车道;出队时状态可能已变(任务刚出完结果),\n // 这时交回大脑队列,保持「到出队才最终判定」的旧语义。\n if (shouldResumeTask(item.text, facts)) {\n const r = injectTask(from, item.text);\n log(\"task: resume(unnumbered):\", item.text.slice(0, 40), \"->\", r.replace(/\\n/g, \" | \"));\n if (r) await notice(r);\n return;\n }\n msgQueue.push(item);\n notifyWorker();\n}\n\n/** 快车道循环:与大脑回合并发,命令之间保持入队顺序。 */\nasync function fastWorkerLoop(\n account: AccountData,\n sends: SendPipeline,\n state: ServerState,\n owner: string,\n): Promise<void> {\n while (true) {\n if (isSessionPaused(account.botId)) {\n await sleep(Math.min(getRemainingPauseMs(account.botId), 30_000) || 30_000);\n continue;\n }\n if (fastQueue.length === 0) {\n await new Promise<void>((r) => {\n wakeFast = r;\n if (fastQueue.length > 0) {\n wakeFast = null;\n r();\n }\n });\n continue;\n }\n const item = fastQueue.shift() as QueuedMsg;\n try {\n await handleFastItem(account, sends, state, owner, item);\n } catch (e) {\n log(\"fast command error:\", e instanceof Error ? e.stack || e.message : String(e));\n await sends\n .sendNotice(\n item.from,\n item.contextToken,\n \"处理这条指令时出错了,请再发一次试试。\",\n state.contextTokens[`${account.botId}:${item.from}`],\n )\n .catch(() => {});\n }\n }\n}\n\nasync function workerLoop(\n account: AccountData,\n sends: SendPipeline,\n state: ServerState,\n): Promise<void> {\n while (true) {\n if (isSessionPaused(account.botId)) {\n await sleep(Math.min(getRemainingPauseMs(account.botId), 30_000) || 30_000);\n continue;\n }\n if (msgQueue.length === 0) {\n await new Promise<void>((r) => {\n wakeWorker = r;\n if (msgQueue.length > 0) {\n wakeWorker = null;\n r();\n }\n });\n continue;\n }\n const item = msgQueue.shift() as QueuedMsg;\n const from = item.from;\n const token = item.contextToken; // 用这条消息自己带的 context_token 回复(每条消息各有各的 token)\n const latestToken = () => state.contextTokens[`${account.botId}:${from}`]; // 该用户最新缓存 token(仅作 -2 参数错误时的兜底)\n\n // 快车道消息(控制/任务指令)不会进这个队列;这里只剩大脑消息。\n // 1) 明确「问大脑」逃逸前缀 → 去前缀后强制走 brain(即使 worker 在等你)\n const brainEscape = item.text ? parseBrainEscape(item.text) : null;\n // 2) 无前缀消息**默认给大脑**;只有「继续」类短句 + 有可续跑任务(waiting 任意;interrupted 需没有活跃任务)才交给任务(多条时 injectTask 回编号清单)。\n // 2026-11-02 前是「只要有一条 waiting 就接管任意无编号消息」——任务出完结果还躺在 waiting,会把用户找大脑的日常消息吞掉(真机踩到两次:#26、#28)。\n // 任务侧的追问走显式寻址:`#<编号> 正文` / `> 正文` / `任务 …`(解析层已分流,不会到这里)。\n if (\n !brainEscape &&\n shouldResumeInBrainQueue(item.text, item.imageItems.length, {\n awaiting: awaitingTasks(from).length,\n active: hasActiveTask(from),\n interrupted: interruptedTasks(from).length,\n })\n ) {\n const r = injectTask(from, item.text);\n log(\"task: resume(unnumbered):\", item.text.slice(0, 40), \"->\", r.replace(/\\n/g, \" | \"));\n if (r) await sends.sendNotice(from, token, r, latestToken());\n continue;\n }\n\n const brainText = brainEscape ?? item.text;\n // 交接注入:切换大脑后,发给**新**大脑的第一条消息带上前一个大脑留下的摘要(切回来源大脑、别人、\n // 过期都不带上,规则见 handoff.ts)。作为前缀带上 = 不额外占一个回合、不多一次模型调用。\n // 切换进行中时按**目标**大脑判断(这条消息会在门口等新大脑就绪,实际答话的是它)。\n const pending = loadHandoff(DATA_DIR);\n const goal = switchInFlightTarget() ?? getAgent(\"brain\").name;\n const htext = takeHandoffFor(pending, goal, from);\n const withHandoff =\n pending && htext ? handoffPrefix(pending.fromAgent, htext) + brainText : brainText;\n if (htext) log(`handoff injected from=${pending?.fromAgent} to=${goal} chars=${htext.length}`);\n try {\n const answered = await handleMessage(\n account,\n sends,\n token,\n from,\n withHandoff,\n item.imageItems,\n item.createTimeMs,\n latestToken,\n );\n // 答成才消费:失败/被图片挡下就留着,下次还能用。\n // 切回来答话(回滚到来源大脑,或切换目标就是它)也不消费——那是它自己写的摘要,留着下次切走时用。\n const answeredBy = getAgent(\"brain\").name;\n if (htext && answered && answeredBy !== pending?.fromAgent && clearHandoff(DATA_DIR))\n log(`handoff consumed by ${answeredBy}`);\n } catch (e) {\n log(\"handleMessage error:\", e instanceof Error ? e.stack || e.message : String(e));\n await sends.sendNotice(from, token, \"处理这条消息时出错了,请再发一次试试。\", latestToken());\n }\n }\n}\n\n// ===== 本地命令入口(文件桥):CLI 写 task-inbox/*.json,daemon 扫描分发到 task-manager,结果写回 task-out/*.json =====\nconst TASK_STATE_FILE = path.join(DATA_DIR, \"task-state.json\");\nconst TASK_SCAN_MS = Number(process.env.WEMIND_TASK_SCAN_MS) || 1000;\n\n/** 读裸 pid 文件(daemon.pid / wrapper.pid);不存在/坏内容 → null。 */\nfunction readPidFile(file: string): number | null {\n try {\n const n = Number.parseInt(fs.readFileSync(file, \"utf-8\").trim(), 10);\n return Number.isInteger(n) && n > 0 ? n : null;\n } catch {\n return null;\n }\n}\n\nexport async function runDaemon(): Promise<void> {\n // 单实例:同账号只允许一个 daemon 消费同一个 bot token。\n // `wimind start` 有 wrapper.pid 守卫;这里挡住直接 `wimind daemon` / `npm run daemon`,以及两套 runner 并存。\n const pidFile = path.join(DATA_DIR, \"daemon.pid\");\n const existing = otherOurProcessPid(readPidFile(pidFile), process.pid, CLI_PID_MARKERS);\n if (existing) {\n log(`another daemon is already running for this account (pid=${existing}); exit.`);\n process.exit(DAEMON_ALREADY_RUNNING_EXIT_CODE);\n }\n // 启动即校验大脑:早失败、说人话——避免「进程看着正常、直到第一条消息才炸」(历史上表现为反复「⏳ 响应中断了,正在自动重试…」,实为默认大脑的二进制没装)。\n // 先种槽位再校验:此后运行期一律从槽位取 agent(热切换会更新它,见 agent-slot.ts),启动期与运行期同一个来源。\n seedSlots(loadBrainConfig());\n const agent = getAgent(\"brain\");\n const brainProblem = agentProblem(agent);\n if (brainProblem) {\n log(`FATAL 大脑不可用:${brainProblem}`);\n process.exit(BRAIN_CONFIG_EXIT_CODE); // 后台守护进程据此停止自动重启(配置错误重拉无益)\n }\n log(\n `brain=${agent.name} bin=${agent.bin} args=[${agent.args.join(\" \")}] shell=${agent.shell} (source: ${agent.source})`,\n );\n // 运行时身份:list 在 daemon 活着时据此显示「实际生效的大脑」(brain.pid 要等 ACP 懒启动才有)\n writeBrainState(DATA_DIR, agent);\n const wagent = getAgent(\"worker\");\n if (wagent.name !== agent.name)\n log(\n `worker=${wagent.name} bin=${wagent.bin} args=[${wagent.args.join(\" \")}] (source: ${wagent.source})`,\n );\n // 任务大脑不可用只警告、不退出:只聊天的用户不受影响,但派发任务会失败——早说清楚比任务静默失败好。\n const workerProblem = agentProblem(wagent);\n if (workerProblem) log(`WARN 任务大脑不可用(派发任务会失败):${workerProblem}`);\n const account = loadAccount();\n if (!account?.botToken) {\n log(\"not logged in; run the MCP login_qrcode flow first.\");\n process.exit(1);\n }\n const owner = process.env.WECHAT_OWNER_ID?.trim() || account.userId; // 登录者:始终授权(兜底校验)\n if (owner) addAllowFrom(owner, DATA_DIR); // 种入登录者白名单(自动迁移老账号);新登录在 finalize 已配对\n // 写 PID 文件,供管理命令(logout/delete)精确定位并停止本账号的 daemon\n try {\n fs.writeFileSync(pidFile, String(process.pid), \"utf-8\");\n } catch {\n /* best-effort */\n }\n const cleanupPid = () => {\n try {\n fs.unlinkSync(pidFile);\n } catch {\n /* ignore */\n }\n };\n // 退出时连带收掉池里的全部 worker:Windows 上子进程不会随父进程退出,漏掉就是一堆孤儿 agent 进程。\n // (任务表不在这里改状态:下次启动统一标「已中断」——那才是它真实的处境。)\n const cleanupAll = () => {\n cleanupPid();\n killWorkerPool();\n };\n process.on(\"exit\", cleanupAll);\n process.on(\"SIGINT\", () => {\n cleanupAll();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n cleanupAll();\n process.exit(0);\n });\n // 防御:任何未捕获的 promise 拒绝/异常(多为瞬时网络 fetch failed 或回合内 send 被拒)只记录、不退出,避免 daemon 崩溃丢掉在途回合、用户卡在心跳。此前的 code=1 崩溃即源于此类未捕获拒绝。\n process.on(\"unhandledRejection\", (reason) => {\n log(\n \"unhandledRejection:\",\n reason instanceof Error ? reason.stack || reason.message : String(reason),\n );\n });\n process.on(\"uncaughtException\", (err) => {\n log(\"uncaughtException:\", err?.stack || String(err));\n });\n ensureBrainHome(); // 准备 brain 沙箱(默认空 MCP)\n ensureWorkerHome(); // 准备 worker 沙箱(继承全量 MCP 除 wechat + 记忆/技能/settings)\n log(`up. bot=${account.botId} owner=${owner} allowFrom=${readAllowFrom(DATA_DIR).length}`);\n await notifyStart({ baseUrl: account.baseUrl, token: account.botToken }).catch(() => {});\n\n const state: ServerState = loadState();\n const sends = createSendPipeline(account);\n // worker 任务推送:用该用户最新 token 经串行发送层发出(与 brain 共用 sendOrdered,保证不乱序)\n setTaskPush((to, text) => {\n const tok = state.contextTokens[`${account.botId}:${to}`];\n // task 推送也占用连续发送额度(与 brain 回合共用 budgetOf):task 在后台运行,可能与 brain 回合并发,若不计数,本地 budget 会低估服务端真实连续发送数 → brain 的 sendProgress 误判「还能发」→ 撞 ret=-2 限流、结果丢失。\n // 注:task 推送是「本轮结果」(必须送达),故只累加计数、不被 awaiting 暂停(暂停只作用于可丢弃的 brain 进度提示)。\n const b = sends.budgetOf(to);\n // 但大脑回合已接近上限时,最后一条额度要留给最终答案:先延后,回合结束后再发(见 flushDeferredTaskPushes)。\n if (shouldDeferTaskPush(activeTurn?.from, to, b.count, CONSEC_SEND_LIMIT)) {\n const n = sends.deferTaskPush(to, text);\n log(`任务推送遇大脑回合额度将满 → 延后到回合结束(累计 ${n} 条,${to})`);\n return;\n }\n b.count += 1;\n void sends.sendOrdered(to, tok, text, MessageState.FINISH, tok, (e) =>\n sends.holdUndelivered(to, text, e),\n );\n });\n // worker 跑一轮期间持续「正在输入中」(typing 不占连续发送上限),避免长任务静默让用户以为卡了\n setTaskTyping((to) => {\n void setTyping(account, to, state.contextTokens[`${account.botId}:${to}`], 1);\n });\n // 任务状态写入磁盘:worker 状态记录原子写 task-state.json,供本地 `manage.js task-status` 读\n setTaskStateSink((snap: TaskStateSnapshot) => {\n try {\n const tmp = `${TASK_STATE_FILE}.tmp-${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(snap, null, 2), \"utf-8\");\n fs.renameSync(tmp, TASK_STATE_FILE);\n } catch {\n /* best-effort */\n }\n });\n setTaskAckSink((id, text) => writeTaskAck(DATA_DIR, id, text)); // 本地命令的回结果通道(`wemind task` 等着的「到底派上了没有」)\n // 本地命令入口:启动清空收件箱与答复目录(作废重启前残留命令、防重放),再起独立定时器扫描分发(不挂 poller,避免被 25s 长轮询阻塞)\n clearTaskDirs(DATA_DIR);\n const taskInboxOps = {\n dispatch: dispatchTask,\n inject: injectTask,\n stop: stopTask,\n writeAck: (ackId: string | undefined, text: string) => writeTaskAck(DATA_DIR, ackId, text),\n log,\n };\n setInterval(() => scanTaskInbox(DATA_DIR, owner, taskInboxOps), TASK_SCAN_MS);\n // 控制桥:同样「启动清空 + 独立定时器扫描」(作废重启前的残留命令,防重放)\n clearControlDirs(DATA_DIR);\n markBridgeReady(DATA_DIR, process.pid); // 凭据:CLI 据此判断「在跑的这个 daemon 认不认识控制桥」\n const controlInbox = new ControlInboxRunner(DATA_DIR, {\n handle: handleControlRequest,\n log,\n });\n setInterval(() => void controlInbox.scan(owner), TASK_SCAN_MS);\n // 任务表:读回上次的(未完成的标「已中断」,不自动恢复);编号接着排\n const restored = initTasks(DATA_DIR);\n if (restored.interrupted.length)\n log(`restored tasks: interrupted=[${restored.interrupted.join(\",\")}](回「继续」可续跑)`);\n // 会话登记表:把当前会话表里有、登记表里还没有的旧会话补进去(功能上线前的老账号,一次性、幂等)\n const backfilled = syncFromPointerMap(DATA_DIR);\n if (backfilled) log(`session registry backfilled: ${backfilled} 条(来自当前会话表)`);\n // 进程池清扫:空闲超时的 worker 回收(默认 5 分钟)\n const poolSweep = setInterval(() => sweepWorkerPool(), 30_000);\n poolSweep.unref?.();\n // 解耦运行:拉取器持续抓消息(永不被大脑阻塞),大脑与快车道各一个处理器并发\n void pollerLoop(account, owner, state, sends);\n void fastWorkerLoop(account, sends, state, owner);\n await workerLoop(account, sends, state);\n}\n\n// daemon 本体由 cli.ts 的 `wemind daemon` 调用 runDaemon() 启动,此处不再顶层自启。\n","// pm2 封装:`wemind start --runner pm2` / `stop` 委托 pm2 做常驻(后台守护进程 / 开机自启 / 多账号)。\n// 默认 runner 是自带后台守护进程(watchdog-runner.ts);本模块只在用户显式选 pm2 时使用。\nimport { spawnSync, type SpawnSyncReturns } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { accountDir } from \"@wemind/wechat-ilink-core\"; // 与 daemon 自身用的是同一个根(尊重 WEMIND_HOME)\n\nimport { BRAIN_CONFIG_EXIT_CODE } from \"./brain-agents.js\";\nimport { DAEMON_ALREADY_RUNNING_EXIT_CODE } from \"./watchdog-runner.js\";\n\n/*\n * 统一用「单条命令字符串 + shell」调用 pm2,而不是 spawnSync(cmd, args[], { shell: true })。\n * 后者在 Node 20+ 会触发 DEP0190 弃用警告(shell:true 同时传 args 数组、参数仅拼接不转义)。\n * 这里命令中只拼入受控值:固定的 cliPath(import.meta 解析得到)、账号 key 派生的 name 与日志路径,且路径都加引号处理空格——无任何外部不可信输入,无注入风险。\n */\nfunction pm2Run(\n cmd: string,\n opts: { quiet?: boolean; env?: NodeJS.ProcessEnv } = {},\n): SpawnSyncReturns<Buffer> {\n return spawnSync(`pm2 ${cmd}`, {\n shell: true,\n stdio: opts.quiet ? \"ignore\" : \"inherit\",\n env: opts.env ?? process.env,\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n}\n\n/** pm2 是否已全局安装、可用。 */\nexport function pm2Available(): boolean {\n try {\n return pm2Run(\"-v\", { quiet: true }).status === 0;\n } catch {\n return false;\n }\n}\n\n/** 账号的 daemon 日志路径,与 run-daemon.ps1 / ecosystem 方式一致:`~/.wemind/<key>/daemon.log`。 */\nfunction daemonLogPath(key: string): string {\n return path.join(accountDir(key), \"daemon.log\");\n}\n\n/**\n * 生成 pm2 的 app 声明(`{ apps: [decl] }`,写进账号目录的 `pm2.app.json`)。\n *\n * **为什么用 JSON 声明而不是把参数堆在 `pm2 start` 命令行上**:\n * - `restart_delay` / `stop_exit_codes` / `max_restarts` 在命令行上是 `--restart-delay` / `--stop-exit-codes`(**变参**)/ `--max-restarts`,还要和 `-- daemon` 这个「脚本参数分隔符」挤在同一条命令里——解析顺序、变参吞掉后续 token 的风险都只能靠真机试;\n * - JSON 声明没有解析层:键值原样进 `pm2_env`(`stop_exit_codes` 在 `God.handleExit` 里既接受数字数组也接受数字字符串,这里给数字数组),与 `ecosystem.config.cjs` 同源同义;\n * - 路径全走 JSON 转义,不再依赖 shell 引号。\n *\n * 与 `ecosystem.config.cjs` 保持一致的项:app 名 `wemind-<key>`、崩溃 5s 重拉、最多 50 次、退出码 78(大脑配置错误)不重启、日志合并到账号 `daemon.log`。导出供单测直接断言(不需要装 pm2)。\n */\nexport function buildAppDeclaration(\n key: string,\n cliPath: string,\n logPath: string,\n): Record<string, unknown> {\n return {\n name: `wemind-${key}`,\n script: cliPath,\n args: \"daemon\", // 等价于 `cli.js daemon`;不必再依赖 `-- daemon` 分隔符\n interpreter: \"node\",\n cwd: os.homedir(), // 不继承 `pm2 start` 时的 shell 目录(换了目录再 start 行为才一致)\n autorestart: true,\n restart_delay: 5000, // 与 builtin 后台守护进程 / run-daemon.ps1 的 5s 重拉一致\n max_restarts: 50, // 防崩溃风暴\n stop_exit_codes: [BRAIN_CONFIG_EXIT_CODE, DAEMON_ALREADY_RUNNING_EXIT_CODE], // 78 = 大脑配置错误;75 = 同账号已有实例,重拉无益\n out_file: logPath,\n error_file: logPath,\n merge_logs: true,\n watch: false, // daemon 是长轮询常驻进程\n // 只声明账号;其余(DEEPSEEK_API_KEY、WEMIND_HOME 等)由 pm2 从执行 `pm2 start` 的进程环境合并进来(`Common.prepareAppConf`:shell env 打底、声明里的 env 覆盖)——与改动前一致。\n env: { WECHAT_ACCOUNT: key },\n };\n}\n\n/**\n * 把声明写进账号目录,返回文件路径。单独导出便于测试(不触碰 pm2);`dir` 可覆盖目标目录——单测传临时目录,避免动真实 `~/.wemind`。\n */\nexport function writeAppDeclaration(\n key: string,\n cliPath: string,\n logPath: string,\n dir: string = accountDir(key),\n): string {\n fs.mkdirSync(dir, { recursive: true });\n const file = path.join(dir, \"pm2.app.json\");\n const apps = [buildAppDeclaration(key, cliPath, logPath)];\n fs.writeFileSync(file, JSON.stringify({ apps }, null, 2) + \"\\n\", \"utf-8\");\n return file;\n}\n\n/** 用 pm2 启动某账号的 daemon:app 名 `wemind-<key>`,注入 WECHAT_ACCOUNT,日志归一到账号 daemon.log。 */\nexport function pm2Start(key: string, cliPath: string): { ok: boolean; msg: string } {\n if (!pm2Available()) {\n return {\n ok: false,\n msg: \"未检测到 pm2。请先安装:npm i -g pm2,再重试 `wemind start`。\",\n };\n }\n const log = daemonLogPath(key);\n const name = `wemind-${key}`;\n // 已存在同名进程则先删,确保按最新声明重启\n pm2Run(`delete ${name}`, { quiet: true });\n const decl = writeAppDeclaration(key, cliPath, log);\n const r = pm2Run(`start \"${decl}\"`, { env: { ...process.env, WECHAT_ACCOUNT: key } });\n if (r.status !== 0) {\n return { ok: false, msg: `pm2 启动失败(exit=${r.status}),声明文件:${decl}` };\n }\n pm2Run(\"save\", { quiet: true });\n return {\n ok: true,\n msg:\n `已用 pm2 启动 ${name}(日志:${log})。\\n` +\n \" 崩溃 5s 重拉、退出码 78(大脑配置错误)与 75(同账号已有实例)不再重启,与自带后台守护进程一致。\\n\" +\n \" 开机自启(首次):执行 `pm2 startup`——Linux/macOS 按提示操作;Windows 用计划任务跑 `pm2 resurrect`。\",\n };\n}\n\n/** 停止某账号的 daemon(pm2 delete)。 */\nexport function pm2Stop(key: string): { ok: boolean; msg: string } {\n if (!pm2Available()) return { ok: false, msg: \"未检测到 pm2。\" };\n const name = `wemind-${key}`;\n const r = pm2Run(`delete ${name}`, { quiet: true });\n pm2Run(\"save\", { quiet: true });\n return r.status === 0\n ? { ok: true, msg: `已停止 ${name}。` }\n : { ok: false, msg: `${name} 未在运行,或停止失败。` };\n}\n","import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { parseJsonc } from \"./agent-mcp.js\";\nimport { agentEnv, authHint, prepareAgent, type ResolvedAgent } from \"./brain-agents.js\";\nimport { copyProfilePatch } from \"./profile-model.js\";\nimport { spawnAgent } from \"./spawn-agent.js\";\nimport { handleReverseRequest } from \"./acp-reverse.js\";\nimport { LineDecoder } from \"./line-decoder.js\";\n\n/**\n * 「大脑连通性自检」:真正起一个 ACP agent 进程并跑完 initialize / session/new 握手,用来证明「这个大脑不只是找得到,而且能说 ACP」——比单纯「文件存在」更硬的证据。\n *\n * 用**临时沙箱目录**(避免污染真实配置与账号沙箱),结束后杀进程、删临时目录。\n * 供 `wemind brain check` 使用;不影响正在运行的 daemon(各起各的进程)。\n */\nexport type CheckResult = { ok: boolean; detail: string; ms: number };\n\nexport async function checkAgent(ag: ResolvedAgent, timeoutMs = 60_000): Promise<CheckResult> {\n const started = Date.now();\n const home = fs.mkdtempSync(path.join(os.tmpdir(), \"wemind-brain-check-\"));\n const env = agentEnv(ag, home); // 父进程 env → 临时沙箱目录 → 预设 env → 预设 envDefaults(与 daemon 的 spawn 口径一致)\n\n // 需要登录的 agent(DSH / Qoder)缺凭据会在 session/new 报认证失败——把凭据带进临时沙箱,否则自检会把「没登录」误报成「二进制不可用」。标识类文件(如 Qoder 的 machine_id)必须一致。\n const cfgDir = path.join(home, ag.configSubdir);\n for (const f of [...ag.credentialFiles, ...ag.identityFiles]) {\n try {\n const src = path.join(ag.realHome ?? \"\", f);\n if (!ag.realHome || !fs.existsSync(src)) continue;\n fs.mkdirSync(path.dirname(path.join(cfgDir, f)), { recursive: true });\n fs.copyFileSync(src, path.join(cfgDir, f));\n } catch {\n /* best-effort */\n }\n }\n\n // 设置基底(settingsBase:Qoder / gemini 这类「设置与 MCP 同文件」的 agent):与 daemon 沙箱一致地带模型/认证等非 MCP 设置。\n // 必须剥掉 mcpServers——自检会在临时目录里真起 agent,带上真实 MCP 等于启动第二个 wechat 消费者(回环)。\n if (ag.settingsBase && ag.realHome) {\n try {\n const parsed = parseJsonc(fs.readFileSync(path.join(ag.realHome, ag.settingsBase), \"utf-8\"));\n if (parsed) {\n const base = parsed;\n for (const k of ag.settingsStrip) delete base[k];\n fs.mkdirSync(cfgDir, { recursive: true });\n fs.writeFileSync(\n path.join(cfgDir, ag.settingsBase),\n JSON.stringify(base, null, 2),\n \"utf-8\",\n );\n }\n } catch {\n /* best-effort(与 daemon 的 settingsBase 一致:缺失/损坏 → 交给 agent 的默认值) */\n }\n }\n\n // 与 daemon 的大脑沙箱对齐:配了 realProfile 的 agent(DSH)启动时就解析 profiles/<名>/cordis.patch.yml,\n // 语法错误会直接 exit 1(initialize 都不会发生)。不复制的话,坏 profile 上自检仍报 ✅,而 daemon 每条消息都起不来。\n if (ag.inherit && ag.realProfile && ag.realHome) {\n try {\n const rel = path.join(\"profiles\", ag.realProfile, \"cordis.patch.yml\");\n if (fs.existsSync(path.join(ag.realHome, rel)))\n copyProfilePatch(ag.realHome, ag.realProfile, home);\n } catch {\n /* best-effort(与 daemon 的 syncProfilePatch 一致) */\n }\n }\n\n prepareAgent(ag); // 与 daemon 对齐:DSH 的 session-root patch 必须在 spawn 前存在\n const proc = spawnAgent(ag, { cwd: home, env });\n\n const stdoutLines = new LineDecoder();\n let seq = 0;\n const pending = new Map<number, (frame: any) => void>();\n const settleAll = (msg: string): void => {\n for (const [id, done] of pending) {\n pending.delete(id);\n done({ id, error: { message: msg } });\n }\n };\n\n proc.stdout!.on(\"data\", (d) => {\n for (const line of stdoutLines.push(d)) {\n let frame: any;\n try {\n frame = JSON.parse(line);\n } catch {\n continue;\n }\n // 反向请求(带 method + id)必须先应答;它与我们的请求 id 分属两个空间,数值可能撞车。\n // 以前这里只处理「响应」,agent 一发权限请求就没人回 → `brain check` 白等 60s,误报「不可用」。\n if (frame.method && frame.id !== undefined) {\n handleReverseRequest(frame, (payload) => proc.stdin!.write(JSON.stringify(payload) + \"\\n\"));\n continue;\n }\n if (typeof frame.id === \"number\" && pending.has(frame.id)) {\n const done = pending.get(frame.id)!;\n pending.delete(frame.id);\n done(frame);\n }\n }\n });\n const stderrTail: string[] = [];\n proc.stderr!.on(\"data\", (d) => {\n stderrTail.push(d.toString());\n if (stderrTail.length > 20) stderrTail.shift();\n });\n proc.on(\"error\", (e) => settleAll(`无法启动:${String(e)}`));\n proc.on(\"exit\", (code) => settleAll(`进程已退出(code=${code})`));\n\n const req = (method: string, params: unknown): Promise<any> =>\n new Promise((resolve, reject) => {\n const id = ++seq;\n const t = setTimeout(() => {\n pending.delete(id);\n reject(new Error(`${method} 超时(${timeoutMs}ms)`));\n }, timeoutMs);\n pending.set(id, (frame) => {\n clearTimeout(t);\n if (frame?.error)\n reject(\n new Error(typeof frame.error === \"string\" ? frame.error : JSON.stringify(frame.error)),\n );\n else resolve(frame?.result);\n });\n proc.stdin!.write(JSON.stringify({ jsonrpc: \"2.0\", id, method, params }) + \"\\n\");\n });\n\n try {\n await req(\"initialize\", { protocolVersion: 1, clientCapabilities: {} });\n const s = (await req(\"session/new\", { cwd: home, mcpServers: [] })) as { sessionId?: string };\n // 没有 sessionId 就等于没有会话:daemon 第一条消息就会失败。以前这里照样报 ✅(detail 里只写 `sessionId=?`),自检失去意义。\n if (!s?.sessionId)\n throw new Error(\"session/new 没有返回 sessionId——agent 的 ACP 实现不完整,无法建立会话\");\n // 预设声明了 sessionMode(免审批)就一并验证:不发这一步的话,模式不可用(如 claude-code 以 root 跑且没设 IS_SANDBOX=1)在这里永远显示 ✅\n let modeNote = \"\";\n if (ag.sessionMode) {\n try {\n await req(\"session/set_mode\", { sessionId: s.sessionId, modeId: ag.sessionMode });\n modeNote = `,mode=${ag.sessionMode}`;\n } catch (e) {\n modeNote = `,但 session/set_mode ${ag.sessionMode} 失败(${e instanceof Error ? e.message : String(e)})——免审批模式可能不可用,回合仍可跑但会按 agent 自己的审批流程走`;\n }\n }\n return {\n ok: true,\n detail: `ACP 握手成功(initialize + session/new,sessionId=${s.sessionId}${modeNote})`,\n ms: Date.now() - started,\n };\n } catch (e) {\n // 只留末尾会把「Error: dsh: failed to parse overlay … YAMLException」这种头行切掉(栈尾看不出原因),\n // 所以同时保留第一行错误信息与末尾上下文。\n const all = stderrTail.join(\"\").trim();\n const lines = all\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter(Boolean);\n const head =\n lines.find((l) => /^(Error|YAMLException)\\b/.test(l)) ??\n lines.find((l) => /failed to parse|YAMLException/i.test(l)) ??\n lines[0];\n const msg = e instanceof Error ? e.message : String(e);\n const hint = authHint(ag.name, msg);\n const tail = all.slice(-400);\n const detail = `${msg}${head ? `\\n stderr: ${head}` : \"\"}${tail && !head?.includes(tail.trim()) ? `\\n …${tail}` : \"\"}${hint ? `\\n 提示:${hint}` : \"\"}`;\n return {\n ok: false,\n detail,\n ms: Date.now() - started,\n };\n } finally {\n try {\n if (process.platform === \"win32\" && proc.pid)\n spawnSync(\"taskkill\", [\"/PID\", String(proc.pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true,\n });\n else proc.kill();\n } catch {\n /* ignore */\n }\n try {\n fs.rmSync(home, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport {\n accountDir,\n getDefaultKey,\n listAccounts,\n loadAccount as readAccount,\n} from \"@wemind/wechat-ilink-core\";\nimport { CLI_PID_MARKERS, isOurProcess } from \"./proc-identity.js\";\nimport {\n awaitControlResponse,\n bridgePidOf,\n controlTimeoutMs,\n newRequestId,\n writeControlRequest,\n type ControlKind,\n type ControlRequest,\n} from \"./control-bridge.js\";\nimport { resolveAgent } from \"./brain-agents.js\";\nimport { loadBrainConfig } from \"./brain-config.js\";\n\n/** 读裸 pid 文件(daemon.pid / wrapper.pid / brain.pid);不存在/坏内容 → null。 */\nexport function readPid(dir: string, name: string): number | null {\n try {\n const n = parseInt(fs.readFileSync(path.join(dir, name), \"utf-8\").trim(), 10);\n return Number.isFinite(n) ? n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * 该 pid 是否「活着**且确实是我们自己的** CLI 进程」。\n * 只判存活不够:pid 会复用(尤其重启后),陈旧 pid 文件可能指向**别人的**进程 → 会误杀/误判。\n */\nexport function aliveOurs(pid: number | null, markers: string[][] = CLI_PID_MARKERS): boolean {\n return isOurProcess(pid, markers);\n}\n\n/** key 或 label → key(label 唯一匹配才行)。 */\nexport function resolveKey(arg: string): string {\n if (!arg) throw new Error(\"需要 <key 或 label>\");\n const accs = listAccounts();\n if (accs.some((a) => a.key === arg)) return arg;\n const byLabel = accs.filter((a) => a.label === arg);\n if (byLabel.length === 1) return byLabel[0].key;\n if (byLabel.length > 1)\n throw new Error(\n `label「${arg}」对应多个账号,请改用 key:${byLabel.map((a) => a.key).join(\", \")}`,\n );\n throw new Error(`未找到账号:${arg}(既不是 key 也不是 label)`);\n}\n\n/**\n * 选账号:显式 key/label 优先,否则用默认账号(`accounts.json` 的 `default`;缺失时回落第一个登记的账号,见 core 的 `getDefaultKey`)。\n * `session`/`task*` 早就是这个口径;`start`/`stop` 也走这里——所以「省略账号」= 默认账号。\n */\nexport function pickKey(sel?: string): string {\n if (sel) return resolveKey(sel);\n const d = getDefaultKey();\n if (!d) throw new Error(\"无默认账号,请指定 <key|label> 或先 login\");\n return d;\n}\n\n/** 预检 daemon 存活(本地命令需 daemon 在跑才有人消费收件箱)。 */\nexport function requireDaemonAlive(key: string): void {\n const pid = readPid(accountDir(key), \"daemon.pid\");\n if (!aliveOurs(pid))\n throw new Error(\n `账号 ${key} 的 daemon 未运行。先启动:wemind start ${key}(用 wemind list 查看状态)`,\n );\n}\n\n/** daemon 是否活着(且确实是我们自己的进程)。读命令不需要它,写命令优先走它。 */\nexport function daemonAlive(key: string): boolean {\n return aliveOurs(readPid(accountDir(key), \"daemon.pid\"));\n}\n\n/**\n * daemon 活着**且认识控制桥**(升级 CLI 后没重启的旧 daemon 在这里判为不可用)。\n * 决定「走桥还是走文件」都该问这个而不是 daemonAlive:对旧 daemon 发送只会白等到超时。\n */\nexport function bridgeReady(key: string): boolean {\n const dir = accountDir(key);\n return daemonAlive(key) && bridgePidOf(dir) === readPid(dir, \"daemon.pid\");\n}\n\n/**\n * 经控制桥让 daemon 执行一条命令并等回话。\n * 超时**如实**报「没等到回复」——桥的价值就在于「CLI 说的」和「daemon 做的」是同一件事,绝不假装成功。\n */\nexport async function viaBridge(\n key: string,\n req: Omit<ControlRequest, \"id\" | \"at\">,\n): Promise<string> {\n const dir = accountDir(key);\n // 先验「在跑的这个 daemon 认不认识桥」:升级 CLI 后 daemon 常常还在跑旧版本,径直发送只会干等到超时。\n // 凭据是 daemon 启动时写的 control-out/.bridge(内容 = 它自己的 pid)——pid 对上才算数。\n const dpid = readPid(dir, \"daemon.pid\");\n if (bridgePidOf(dir) !== dpid)\n throw new Error(\n `账号 ${key} 的 daemon 还在跑**旧版本**(不认识控制桥),换大脑/会话命令发送过去只会白等超时。\\n` +\n `重启一次即可:wemind stop ${key} && wemind start ${key}\\n` +\n `(只想改配置不动进程:wemind brain set <agent 名>,但它要等下次启动才生效。)`,\n );\n const timeoutMs = controlTimeoutMs(req.kind as ControlKind);\n const now = Date.now();\n // 带上截止时间:CLI 放弃后 daemon 不再执行(否则「CLI 说超时、daemon 照做」两边说法相反)。\n const full: ControlRequest = {\n id: newRequestId(now),\n at: now,\n ...req,\n expiresAt: now + timeoutMs,\n };\n writeControlRequest(full, dir);\n const res = await awaitControlResponse(full.id, dir, timeoutMs);\n if (!res)\n throw new Error(\n `等 daemon 回复超时(${req.kind})。它可能正忙或已退出,用 wemind list 看状态、daemon.log 看日志。`,\n );\n if (!res.ok) process.exitCode = 1; // ❌ 开头的答复 = 失败(脚本据此判成败)\n return res.text;\n}\n\n/** 会话命令归属的人:登录者(与 daemon 里 owner 的定义一致)。 */\nexport function ownerOf(key: string): string {\n return process.env.WECHAT_OWNER_ID?.trim() || readAccount(accountDir(key))?.userId || \"\";\n}\n\n/** daemon 没在跑时用来定会话归属(按 agent 分开存)的大脑名(与 daemon 启动期同一个解析来源:配置 + 环境变量 + 自动探测)。 */\nexport function agentNameOf(key: string): string {\n return resolveAgent(loadBrainConfig(accountDir(key)), accountDir(key)).name;\n}\n\n/**\n * 末尾那个「长得像账号」的位置参数摘出来当账号选择(`session list 工作号`)。\n * 认不出就当普通参数——session 的名字可能跟账号别名撞脸,宁可把它当名字。\n */\nexport function splitKeyArg(args: string[]): { args: string[]; sel?: string } {\n const last = args[args.length - 1];\n if (args.length && last && listAccounts().some((a) => a.key === last || a.label === last))\n return { args: args.slice(0, -1), sel: last };\n return { args };\n}\n","import { accountDir } from \"@wemind/wechat-ilink-core\";\nimport {\n ACP_AGENT_PRESETS,\n AUTO_PROBE_ORDER,\n resolveAgent,\n agentProblem,\n presetAvailable,\n acpArgsWarning,\n type ResolvedAgent,\n} from \"./brain-agents.js\";\nimport {\n loadBrainConfig,\n updateBrainConfig,\n clearBrainConfig,\n brainConfigPath,\n type BrainConfig,\n} from \"./brain-config.js\";\nimport { checkAgent } from \"./brain-check.js\";\nimport { acpModelHint, acpOverrideHint } from \"./profile-model.js\";\nimport {\n aliveOurs,\n bridgeReady,\n daemonAlive,\n pickKey,\n readPid,\n requireDaemonAlive,\n viaBridge,\n} from \"./cli-context.js\";\nimport type { BrainRuntime } from \"./brain-state.js\";\n\n// ===== 大脑(ACP agent)选择:账号级持久配置 brain.json,见 brain-config.ts =====\nconst BRAIN_SRC_LABEL: Record<ResolvedAgent[\"source\"], string> = {\n env: \"环境变量 WEMIND_BRAIN*\",\n file: \"账号 brain.json\",\n auto: \"自动探测(未配置)\",\n};\n\n/** list 用:账号「配置 / 自动探测」得到的大脑标签(不看当前 shell 的 env,避免误导)。 */\nfunction configAgentLabel(dir: string): string {\n const cfg = loadBrainConfig(dir);\n if (cfg?.agent && cfg.agent.toLowerCase() !== \"auto\") return cfg.agent;\n const picked = AUTO_PROBE_ORDER.find((k) => presetAvailable(k));\n return picked ? `${picked}(自动)` : \"(无可用)\";\n}\n\n/**\n * list 用的 agent 列:daemon **在跑**时优先显示它实际生效的大脑(`brain-runtime.json`,\n * 来自 daemon 自己的解析,含 `WEMIND_BRAIN` 覆盖/热切换结果);daemon 没跑时仍只显示配置/自动探测。\n * 运行时与配置不一致时把两者都写出来,避免「看着是 deepseek、实际在跑 cline」。\n */\nexport function agentLabel(dir: string, runtime: BrainRuntime | null = null): string {\n const cfg = configAgentLabel(dir);\n if (!runtime?.agent?.trim()) return cfg;\n const name = runtime.agent.trim();\n const cfgName = cfg.replace(/([^)]*)$/, \"\");\n if (name === cfgName) return cfg;\n return `${name}(运行,来源 ${runtime.source};配置 ${cfg})`;\n}\n\n/** `wemind brain`(show):打印某账号大脑的「配置」与「生效」情况。 */\nfunction describeBrain(key: string): string {\n const dir = accountDir(key);\n const cfg = loadBrainConfig(dir);\n const ag = resolveAgent(cfg, dir);\n const problem = agentProblem(ag);\n const cfgLine = cfg\n ? `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 !== undefined ? ` shell=${cfg.shell}` : \"\"}${cfg.model ? ` model=${cfg.model}` : \"\"}`\n : \"(未设置 brain.json)\";\n const lines = [\n `账号 ${key}`,\n ` 配置:${cfgLine}`,\n ` 请求:${ag.requestedKey} 来源:${BRAIN_SRC_LABEL[ag.source]}`,\n ` 生效: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 ?? \"(无)\"}`,\n ` 可用:${problem ? `❌ ${problem}` : `✅ ${ag.binPath ?? ag.bin}`}`,\n ` 可选 agent:${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}`,\n ` 自动探测顺序:${AUTO_PROBE_ORDER.join(\" → \")}`,\n ];\n // D:整段替换 args 后漏了 ACP 模式参数时提醒(只是提醒;运行时另有握手失败的诊断提示)\n const argsWarn = acpArgsWarning(ag.name, ag.args, ag.binOverridden);\n if (argsWarn) lines.push(` ⚠️ ${argsWarn}`);\n // 用户层 profile 缺 `- id: acp`(或它的 model 已被桌面端模型目录淘汰)时给出「你自己改」的提示(WeMind 只读,不替 agent 合成)\n const hint =\n ag.realProfile && ag.realHome && ag.inherit\n ? (acpOverrideHint(ag.realHome, ag.realProfile) ?? acpModelHint(ag.realHome, ag.realProfile))\n : null;\n if (hint) lines.push(\"\", hint);\n return lines.join(\"\\n\");\n}\n\n/** `wemind brain set <agent 名|auto>`:把选择按账号写入磁盘(可选 --bin/--args/--extra-args/--shell/--model;--restart 立即重启 daemon)。 */\nasync function cmdBrainSet(\n agentName: string,\n key: string,\n opts: {\n bin?: string;\n args?: string;\n extraArgs?: string;\n shell?: boolean;\n model?: string;\n restart: boolean;\n },\n deps: BrainCliDeps,\n): Promise<string> {\n const agent = agentName.trim().toLowerCase();\n if (agent !== \"auto\" && !ACP_AGENT_PRESETS[agent])\n throw new Error(\n `未知 agent「${agentName}」。可选:auto、${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}(自定义二进制用 --bin 搭配任一 agent)`,\n );\n // auto 不绑定具体 agent:写进 presets.auto 永远读不到,顶层覆盖会下发给探测到的任意 agent(把 A 的 bin/args 发给 B)。\n if (\n agent === \"auto\" &&\n (opts.bin !== undefined ||\n opts.args !== undefined ||\n opts.extraArgs !== undefined ||\n opts.shell !== undefined)\n )\n throw new Error(\n \"`auto` 是「自动探测」,不绑定具体 agent,不能配 --bin/--args/--extra-args/--shell;请指定具体 agent(如 brain set kiro --bin …)\",\n );\n if (opts.model !== undefined) {\n if (!opts.model.trim()) throw new Error(\"--model 缺少参数值(清空/跟随默认请写 --model auto)\");\n // auto 只是「自动探测」,解析时会被替换成探测到的具体 agent:写进 presets.auto 永远读不到,\n // 顶层的 model 又会下发给探测出来的任意 agent(把 DSH 的模型值发给 opencode 之类)。\n if (agent === \"auto\")\n throw new Error(\n \"`auto` 是「自动探测」,不绑定具体 agent,不能配 --model;请指定具体 agent(如 brain set deepseek --model …)\",\n );\n }\n const dir = accountDir(key);\n const customized =\n opts.bin !== undefined ||\n opts.args !== undefined ||\n opts.extraArgs !== undefined ||\n opts.shell !== undefined ||\n opts.model !== undefined;\n // 只选 agent(没带自定义启动参数)+ 没要求重启时,写入磁盘和「热切换」其实是同一件事:daemon 在跑且认桥就交给它一次做完——\n // 它先启动新大脑、成功才写 brain.json(失败退回,盘上不留半截结果),这比「写入磁盘 + 等下次重启」对用户更实在(决策⑪)。\n // 带 --bin/--args/--shell/--model 则**不能**当场应用:控制桥只接受 agent 名(见 control-bridge.ts 的安全边界)。\n if (!opts.restart && !customized && bridgeReady(key)) {\n const r = await cmdBrainSwitch(agent, key, false);\n // 热切换失败时**没有写入磁盘**(持久化放最后,见 brain-switch.ts):把这点说清楚,否则用户会以为 brain.json 已经改了。\n return r.startsWith(\"❌\")\n ? `${r}\\n(brain.json **没有被改写**:换大脑失败就不写入磁盘——这是「失败就退回原来那个」的代价。想强制写入再重启:wemind brain set ${agent} --restart)`\n : r;\n }\n const patch: BrainConfig = { agent };\n if (opts.bin) patch.bin = opts.bin;\n if (opts.args !== undefined) patch.args = opts.args.trim() ? opts.args.trim().split(/\\s+/) : [];\n if (opts.extraArgs !== undefined)\n patch.extraArgs = opts.extraArgs.trim() ? opts.extraArgs.trim().split(/\\s+/) : [];\n if (opts.shell !== undefined) patch.shell = opts.shell;\n if (opts.model !== undefined) patch.model = opts.model.trim();\n // 读-改-写:保留 worker 段(任务侧独立选择)与 presets 段(按 agent 名存的启动覆盖),旧 agent 的覆盖自动迁移。\n const before = loadBrainConfig(dir);\n const cfg = updateBrainConfig(patch, dir);\n const lines = [\n `已设置 ${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}` : \"\"}`,\n `配置文件:${brainConfigPath(dir)}`,\n ];\n // D:整段替换 args 时检查 ACP 模式标记(只提醒,不阻止写入)\n if (opts.args !== undefined) {\n const binOverridden =\n opts.bin !== undefined ||\n !!before?.presets?.[agent]?.bin ||\n (before?.agent?.trim().toLowerCase() === agent && !!before?.bin);\n const warn = acpArgsWarning(agent, cfg.args ?? [], binOverridden);\n if (warn) lines.push(`⚠️ ${warn}`);\n }\n if (opts.restart) {\n const dpid = readPid(dir, \"daemon.pid\");\n if (aliveOurs(dpid)) {\n deps.startDaemon(key);\n lines.push(`已重启 daemon(账号 ${key})。`);\n } else {\n lines.push(`daemon 未在运行,未重启(用 wemind start ${key} 启动)。`);\n }\n } else if (customized && bridgeReady(key)) {\n // --bin/--args/--extra-args/--model 的改动其实可以由 `brain switch <同名 agent>` 当场应用(sameAgent 按这几项判「是不是同一个」);--shell 不在判据里,只能重启。\n const viaSwitch =\n opts.bin !== undefined ||\n opts.args !== undefined ||\n opts.extraArgs !== undefined ||\n opts.model !== undefined;\n lines.push(\n `(带 --bin/--args/--extra-args/--shell/--model 的改动不能由 brain set 当场应用:控制桥只认 agent 名。重启后生效——wemind stop ${key} && wemind start ${key}${viaSwitch ? `;不想重启的话,接着执行 wemind brain switch ${agent} 也能当场应用(它按 name/bin/args/model 判断是不是同一个,改过就会真切换)` : \"\"})`,\n );\n } else if (daemonAlive(key)) {\n lines.push(\n `(daemon 在跑但不是当前版本(不认控制桥):盘已写好,重启一次才生效——wemind stop ${key} && wemind start ${key})`,\n );\n } else {\n lines.push(`(daemon 需重启才生效:wemind start ${key})`);\n }\n return lines.join(\"\\n\");\n}\n\n/** `wemind brain unset`:清除账号级配置,回退到自动探测/默认。 */\nfunction cmdBrainUnset(key: string): string {\n const ok = clearBrainConfig(accountDir(key));\n return ok\n ? `${key} 已清除 brain.json(回退到自动探测/默认)。daemon 需重启才生效。`\n : `${key} 未设置 brain.json(无需清除)。`;\n}\n\n/** `wemind brain check`:真起一个进程跑 ACP 握手,验证大脑「不仅找得到、而且能说 ACP」。 */\nasync function cmdBrainCheck(key: string): Promise<string> {\n const dir = accountDir(key);\n const ag = resolveAgent(loadBrainConfig(dir), dir);\n const problem = agentProblem(ag);\n if (problem) {\n process.exitCode = 1; // 自检失败必须让脚本/CI 看得见(以前只打印 ❌,退出码仍是 0)\n return `❌ ${key} 的大脑不可用:${problem}`;\n }\n const r = await checkAgent(ag);\n if (!r.ok) process.exitCode = 1;\n return `${r.ok ? \"✅\" : \"❌\"} ${key} 大脑自检(${ag.name},bin=${ag.bin}):${r.detail}(${r.ms}ms)`;\n}\n\nexport type BrainCliDeps = {\n /** `--restart` 重启账号 daemon:由入口注入,避免 CLI 模块反向依赖 manage.ts。 */\n startDaemon: (key: string) => void;\n};\n\n/** brain 子命令派发:show(默认)/ set / unset / check / switch / handoff。 */\nexport async function cmdBrain(rest: string[], deps: BrainCliDeps): Promise<string> {\n const argv = [...rest];\n const FLAGS = new Set([\n \"--bin\",\n \"--args\",\n \"--extra-args\",\n \"--shell\",\n \"--no-shell\",\n \"--model\",\n \"--restart\",\n ]);\n const takeFlag = (name: string): string | undefined => {\n // 同时认 `--model X` 与 `--model=X`:以前只按整 token 匹配 `--model`,等号写法会被当成未知开关静默丢掉(命令还报成功)。\n const eq = argv.find((t) => t.startsWith(`${name}=`));\n if (eq !== undefined) {\n const v = eq.slice(name.length + 1);\n if (!v) throw new Error(`${name} 缺少参数值`);\n argv.splice(argv.indexOf(eq), 1);\n return v;\n }\n const i = argv.indexOf(name);\n if (i < 0) return undefined;\n const v = argv[i + 1];\n // `--bin` 后面没值、或紧跟的是另一个开关(`brain set kiro --bin --restart`)时,以前会静默把 `--restart` 当成 bin 的值,看起来「设置成功」但存进去的是错的。\n // 值本身允许以 `--` 开头(如 `--args \"--experimental-acp\"`),所以只对已知开关报错。\n if (v === undefined || FLAGS.has(v)) throw new Error(`${name} 缺少参数值`);\n argv.splice(i, 2);\n return v;\n };\n const bin = takeFlag(\"--bin\");\n const argsOverride = takeFlag(\"--args\");\n const extraArgsOverride = takeFlag(\"--extra-args\");\n const model = takeFlag(\"--model\");\n const restart = argv.includes(\"--restart\");\n if (restart) argv.splice(argv.indexOf(\"--restart\"), 1);\n const shell = argv.includes(\"--shell\") ? true : argv.includes(\"--no-shell\") ? false : undefined;\n for (const f of [\"--shell\", \"--no-shell\"]) {\n const i = argv.indexOf(f);\n if (i >= 0) argv.splice(i, 1);\n }\n while (argv.includes(\"--worker\")) argv.splice(argv.indexOf(\"--worker\"), 1); // 只对 switch 有意义(用 rest 判断),不留成未知开关\n // 未知开关直接报错:以前一律被 pos 的过滤静默丢掉,`--modle` 这种拼错会「看起来设置成功」。\n const unknown = argv.filter((x) => x.startsWith(\"--\"));\n if (unknown.length)\n throw new Error(\n `未知参数:${unknown.join(\" \")}(可用:--bin --args --extra-args --shell --no-shell --model --restart)`,\n );\n const pos = argv.filter((x) => x && !x.startsWith(\"--\"));\n const first = (pos[0] || \"\").toLowerCase();\n const SUBCOMMANDS = new Set([\"show\", \"set\", \"unset\", \"check\", \"switch\", \"handoff\"]);\n if (first && !SUBCOMMANDS.has(first)) {\n // 省略子命令 → 视为 `brain show <key|label>`(如 `wemind brain 工作号`)。\n return describeBrain(pickKey(pos[0]));\n }\n const sub = first || \"show\";\n switch (sub) {\n case \"show\":\n return describeBrain(pickKey(pos[1]));\n case \"set\": {\n if (!pos[1])\n throw new Error(\n '用法: brain set <agent 名|auto> [--bin X] [--args \"…\"] [--extra-args \"…\"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]',\n );\n return await cmdBrainSet(\n pos[1],\n pickKey(pos[2]),\n { bin, args: argsOverride, extraArgs: extraArgsOverride, shell, model, restart },\n deps,\n );\n }\n case \"switch\": {\n // 桥只接受**agent 名**(安全边界:见 control-bridge.ts)。带 --bin/--args/--shell/--model 就走 set(只写入磁盘;--bin/--args/--model 可随后用同名 switch 当场应用,--shell 只能重启)。\n if (\n bin !== undefined ||\n argsOverride !== undefined ||\n extraArgsOverride !== undefined ||\n shell !== undefined ||\n model !== undefined\n )\n throw new Error(\n \"`brain switch` 只接受 agent 名(换大脑是「换一个已装好的 agent」,不能带 --bin/--args/--extra-args/--model)。\\n\" +\n \"需要自定义二进制用 `brain set <agent 名> --bin …`(那是配置变更,只写入磁盘——之后 `wemind brain switch <同名 agent>` 可当场应用,或重启 daemon);追加参数用 `brain set <agent 名> --extra-args …`;换模型用 `brain set <agent 名> --model …`。\",\n );\n if (!pos[1]) throw new Error(\"用法: brain switch <agent 名|auto> [--worker] [<key|label>]\");\n const worker = rest.includes(\"--worker\");\n return await cmdBrainSwitch(pos[1], pickKey(pos[2]), worker);\n }\n case \"handoff\":\n return await cmdBrainHandoff(pickKey(pos[1]));\n case \"unset\":\n return cmdBrainUnset(pickKey(pos[1]));\n case \"check\":\n return await cmdBrainCheck(pickKey(pos[1]));\n default:\n throw new Error(\n `未知 brain 子命令「${sub}」(可用:show / set / switch / handoff / unset / check)`,\n );\n }\n}\n\n/**\n * `brain switch <agent 名|auto>`:**热切换**——daemon 在跑就走控制桥(不重启、不打断在途回合);\n * daemon 没在跑就只写 brain.json(下次启动生效)。\n * `--worker` 设默认任务大脑:那是 daemon 进程内才有的状态(不写入磁盘),没有 daemon 就没有意义,必须先启动。\n */\nasync function cmdBrainSwitch(agentName: string, key: string, worker: boolean): Promise<string> {\n const agent = agentName.trim().toLowerCase();\n if (agent !== \"auto\" && !ACP_AGENT_PRESETS[agent])\n throw new Error(\n `未知 agent「${agentName}」。可选:auto、${Object.keys(ACP_AGENT_PRESETS).join(\"、\")}`,\n );\n const dir = accountDir(key);\n if (worker) {\n requireDaemonAlive(key); // 「默认任务大脑」是进程内状态,不写入磁盘\n return await viaBridge(key, { kind: \"brain.worker\", target: agent });\n }\n if (daemonAlive(key)) return await viaBridge(key, { kind: \"brain.switch\", target: agent });\n updateBrainConfig({ agent }, dir);\n return `daemon 未在运行:已把 ${key} 的大脑写进 brain.json(agent=${agent},下次启动生效)。\\n运行中换大脑:先 wemind start ${key}`;\n}\n\n/** `wemind brain handoff`:让当前大脑写一份交接摘要(与微信「交接」同一段实现)。 */\nasync function cmdBrainHandoff(key: string): Promise<string> {\n requireDaemonAlive(key);\n return await viaBridge(key, { kind: \"brain.handoff\" });\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { spawn } from \"node:child_process\";\nimport {\n WEMIND_ROOT,\n accountDir,\n deriveAccountKey,\n registerAccount,\n listAccounts,\n fetchQRCode,\n pollQRStatus,\n saveQrState,\n saveAccountTo,\n clearSessionPause,\n notifyStart,\n addAllowFrom,\n loadAccount as readAccount,\n} from \"@wemind/wechat-ilink-core\";\nimport { bridgeReady, viaBridge } from \"./cli-context.js\";\n\nconst BASE = process.env.WEMIND_ILINK_BASE?.trim() || \"https://ilinkai.weixin.qq.com\";\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * 官方状态机里 WeMind 尚未实现的四种高级态。以前它们落进默认分支按「等待」轮询到 15 分钟超时,\n * 用户只看到二维码一直转、不知道需要做什么;这里直接失败并给出可执行的下一步。\n * 导出仅供单测。\n */\nexport function loginUnsupportedHint(status: string, redirectHost?: string): string {\n if (status === \"need_verifycode\")\n return \"本次登录需要输入验证码,wemind login 暂不支持该步骤。请稍后重试;若反复出现,请先确认微信侧没有待处理的登录验证。\";\n if (status === \"verify_code_blocked\")\n return \"验证码尝试次数过多,微信暂时阻止了这次登录。请稍后(通常几十分钟后)再重新扫码。\";\n if (status === \"scaned_but_redirect\" || status === \"binded_redirect\") {\n const host = redirectHost ? `(微信要求切换到 ${redirectHost})` : \"\";\n return `本次登录需要切换到微信指定的接入点${host},wemind login 暂不支持该步骤。请稍后重试。`;\n }\n return `登录遇到暂不支持的状态「${status}」。请稍后重试。`;\n}\n\nfunction openFile(p: string): void {\n try {\n if (process.platform === \"win32\")\n spawn(\"cmd\", [\"/c\", \"start\", \"\", p], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: true,\n }).unref();\n else\n spawn(process.platform === \"darwin\" ? \"open\" : \"xdg-open\", [p], {\n detached: true,\n stdio: \"ignore\",\n }).unref();\n } catch {\n /* ignore */\n }\n}\n\nexport type LoginCliDeps = {\n /** 登录成功后自动启动(`--start`):由入口注入,模块不反向依赖 manage.ts。 */\n startDaemon: (key: string, runner?: string) => void;\n};\n\n/**\n * 每次登录一个独立暂存目录:两个 `wimind login` 并发时不能互删二维码/凭证。\n * 用 `mkdtemp`(POSIX 0700)而不是固定 `.staging/`,也不写进任何已登记账号的数据目录。\n * 导出仅供单测。\n */\nexport function createLoginStagingDir(): string {\n fs.mkdirSync(WEMIND_ROOT, { recursive: true });\n return fs.mkdtempSync(path.join(WEMIND_ROOT, \".staging-\"));\n}\n\n/**\n * 把本次登录的暂存目录按 botId 收进账号目录 + 登记。\n * 目录已存在(同一微信号重新扫码)时**并入**而不是报错:只覆盖本次新写的凭证,保留会话/白名单/大脑配置——\n * `logout` 明确保留目录以便重登,若这里拒绝,那条路径与文档承诺的「重新登录」都无法执行。\n */\nexport function finalizeStaging(staging: string, label?: string): { key: string; msg: string } {\n const acc = readAccount(staging);\n if (!acc?.botId) throw new Error(`staging 无有效登录(${staging})`);\n const key = deriveAccountKey(acc.botId);\n const dest = accountDir(key);\n const existed = fs.existsSync(dest);\n const prevLabel = existed ? listAccounts().find((a) => a.key === key)?.label : undefined;\n if (existed) {\n if (!fs.statSync(dest).isDirectory())\n throw new Error(`账号路径已存在但不是目录: ${dest}(请先处理该路径再重登)`);\n // 逐项覆盖暂存目录里的文件(account.json 等),不删除目标目录中的其它内容。\n fs.cpSync(staging, dest, { recursive: true, force: true });\n fs.rmSync(staging, { recursive: true, force: true });\n } else {\n fs.renameSync(staging, dest);\n }\n for (const f of [\n \"qrcode.png\",\n \"qrcode.txt\",\n \"qr_login.json\",\n \"poll.out\",\n \"poll.err\",\n \"auto.out\",\n \"auto.err\",\n ]) {\n try {\n fs.unlinkSync(path.join(dest, f));\n } catch {\n /* ignore */\n }\n }\n const meta = registerAccount({\n key,\n botId: acc.botId,\n userId: acc.userId,\n baseUrl: acc.baseUrl,\n label: label || prevLabel || key, // 重登不带 --label 时保留原别名,避免一次扫码把用户起的名字改回 key\n });\n if (acc.userId) addAllowFrom(acc.userId, dest); // 配对:扫码登录者自动授权\n return {\n key,\n msg: `已登录:key=${key} label=${meta.label} botId=${acc.botId}\\n目录:${dest}${existed ? \"(已并入现有账号,会话与白名单保留)\" : \"\"}`,\n };\n}\n\n/**\n * 懒加载 `qrcode`:只有扫码登录要画二维码,`list` / `brain` / `task` / `daemon` 全用不上。\n * 顶层 `import` 会让**每次**启动都付约 36ms(实测)解析 qrcode + yargs 依赖树;这里在第一次画码时才加载,之后同进程内复用(登录过程会因过期刷新多次)。\n * 用函数声明而非 `const QRCode = await import(...)`:后者在 ESM 顶层要多一层 TDZ 心智负担。\n */\nlet qrcodeMod: typeof import(\"qrcode\") | undefined;\n\nasync function loadQrCode(): Promise<typeof import(\"qrcode\")> {\n if (!qrcodeMod) {\n try {\n qrcodeMod = await import(\"qrcode\");\n } catch (e) {\n throw new Error(\n `无法加载二维码库 qrcode(依赖缺失或安装损坏):${e instanceof Error ? e.message : String(e)}\\n` +\n \"请重试安装:npm i -g @wemind/cli\",\n );\n }\n }\n return qrcodeMod;\n}\n\n/** 一条命令完成扫码登录:自刷新二维码 + 终端码 + 自动开图 + 自动登记(→ 可选自动启动)。 */\nexport async function cmdLogin(\n label: string | undefined,\n autoStart: boolean,\n runner: string | undefined,\n deps: LoginCliDeps,\n): Promise<void> {\n const staging = createLoginStagingDir();\n try {\n await loginInto(staging, label, autoStart, runner, deps);\n } finally {\n // 归档成功时目录已被 rename 走(这里是空操作);超时/Ctrl+C/失败时清掉二维码与临时凭证。\n try {\n fs.rmSync(staging, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n }\n}\n\nasync function loginInto(\n staging: string,\n label: string | undefined,\n autoStart: boolean,\n runner: string | undefined,\n deps: LoginCliDeps,\n): Promise<void> {\n const QRCode = await loadQrCode(); // 只有登录路径才加载 qrcode\n const png = path.join(staging, \"qrcode.png\");\n const overall = Date.now() + 15 * 60 * 1000;\n let code = \"\";\n let opened = false;\n\n const refresh = async (): Promise<void> => {\n const qr = await fetchQRCode(BASE);\n code = qr.qrcode;\n saveQrState(\n { qrcode: qr.qrcode, qrcodeUrl: qr.qrcode_img_content, createdAt: Date.now() },\n staging,\n );\n await QRCode.toFile(png, qr.qrcode_img_content, { width: 480, margin: 2 });\n const term = await QRCode.toString(qr.qrcode_img_content, { type: \"terminal\", small: true });\n console.log(\"\\n用要登录的微信扫下面的二维码(过期会自动刷新):\\n\");\n console.log(term);\n console.log(\"图片(已自动打开): \" + png);\n console.log(\"链接: \" + qr.qrcode_img_content + \"\\n\");\n if (!opened) {\n openFile(png);\n opened = true;\n }\n };\n\n await refresh();\n console.log(\"等待扫码确认…(最多 15 分钟,Ctrl+C 取消)\");\n while (Date.now() < overall) {\n let s;\n try {\n s = await pollQRStatus(BASE, code);\n } catch {\n await sleep(2000);\n continue;\n }\n if (s.status === \"confirmed\") {\n if (!s.bot_token || !s.ilink_bot_id || !s.ilink_user_id)\n throw new Error(\"confirmed 但数据不全\");\n // 走 core 的 saveAccountTo:POSIX 600 + Windows 一次性明文告警。\n // 之前这里是裸 writeFileSync,导致「凭证受权限保护」的安全承诺在 CLI 登录路径上不成立。\n saveAccountTo(staging, {\n botToken: s.bot_token,\n botId: s.ilink_bot_id,\n userId: s.ilink_user_id,\n baseUrl: s.baseurl ?? BASE,\n savedAt: Date.now(),\n });\n clearSessionPause(s.ilink_bot_id);\n await notifyStart({ baseUrl: s.baseurl ?? BASE, token: s.bot_token }).catch(() => {});\n for (const f of [\"qrcode.png\", \"qr_login.json\"]) {\n try {\n fs.unlinkSync(path.join(staging, f));\n } catch {\n /* ignore */\n }\n }\n console.log(\"\\n登录成功,正在登记…\");\n const { key, msg } = finalizeStaging(staging, label);\n console.log(msg);\n await unpauseIfRunning(key);\n if (autoStart) {\n deps.startDaemon(key, runner);\n console.log(`已启动 daemon(账号 ${key})。`);\n } else {\n console.log(`启动:wemind start ${key}`);\n }\n return;\n }\n if (s.status === \"expired\") {\n console.log(\"二维码过期,自动刷新…\");\n await refresh();\n continue;\n }\n if (\n s.status === \"need_verifycode\" ||\n s.status === \"verify_code_blocked\" ||\n s.status === \"scaned_but_redirect\" ||\n s.status === \"binded_redirect\"\n )\n throw new Error(loginUnsupportedHint(s.status, s.redirect_host));\n await sleep(1500);\n }\n throw new Error(\"超时(15 分钟)未完成登录\");\n}\n\n/** 登录成功后让**正在运行的** daemon 解除 -14 冷却(`clearSessionPause` 是进程内状态,CLI 自己清不到它)。 */\nasync function unpauseIfRunning(key: string): Promise<void> {\n if (!bridgeReady(key)) return;\n try {\n await viaBridge(key, { kind: \"session.unpause\" });\n } catch {\n /* 尽力而为:daemon 忙/正在退出/旧版本不认识,都不影响登录本身 */\n }\n}\n","import { accountDir } from \"@wemind/wechat-ilink-core\";\nimport {\n currentSessionIdOf,\n deleteSession,\n listSessions,\n renderSessionList,\n renameSession,\n} from \"./session-registry.js\";\nimport { brainSessionMapPath, clearSessionPointer, sessionKey } from \"./session-map.js\";\nimport {\n agentNameOf,\n bridgeReady,\n daemonAlive,\n ownerOf,\n pickKey,\n requireDaemonAlive,\n splitKeyArg,\n viaBridge,\n} from \"./cli-context.js\";\n\n/**\n * `wemind session <new|list|resume|rename|delete>`。\n * 读写规则:**读**永远可用(直接读登记表);**写**优先走 daemon(单一写者,且当前会话记录/agent 侧状态都是它的活状态)。\n * 例外:`rename`/`delete` 只动登记表这一个文件,daemon 没跑时直接改也是安全的;`resume` 必须真让 agent load,没 daemon 做不了。\n */\nexport async function cmdSession(rest: string[]): Promise<string> {\n const argv = [...rest];\n const pi = argv.indexOf(\"--page\");\n let page: number | undefined;\n if (pi >= 0) {\n const raw = argv[pi + 1];\n if (raw === undefined || raw.startsWith(\"--\")) throw new Error(\"--page 缺少参数值\");\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 1) throw new Error(`--page 必须是正整数:${raw}`);\n page = n;\n argv.splice(pi, 2);\n }\n const pos = argv.filter((x) => x && !x.startsWith(\"--\"));\n const sub = (pos[0] ?? \"\").toLowerCase();\n const { args, sel } = splitKeyArg(pos.slice(1));\n const USAGE =\n \"用法: session list [--page N] | session new [名字] | session resume <序号|名字|id前缀> | \" +\n \"session rename <序号|名字> <新名字> | session delete <序号|名字|id前缀> [<key|label>]\";\n\n if (sub === \"list\") {\n const key = pickKey(sel);\n // 读优先走桥(daemon 里在用的那个大脑才是真的会话归属);桥不可用就退回直接读文件——读永远不该失败。\n if (bridgeReady(key)) return await viaBridge(key, { kind: \"session.list\", page });\n const dir = accountDir(key);\n const agent = agentNameOf(key);\n const userId = ownerOf(key);\n if (!userId) throw new Error(`账号 ${key} 没有 userId(account.json 不全),先重新登录。`);\n const { entries, others, currentSessionId } = listSessions(agent, userId, dir);\n const why = daemonAlive(key)\n ? \"(daemon 是升级前启动的旧版本,没法问它:以上按 brain.json/自动探测的「下次会用的大脑」列出;重启 daemon 后以进程内在用的为准。)\"\n : \"(daemon 未运行:按 brain.json/自动探测得到的「下次会用的大脑」列出。)\";\n return (\n renderSessionList(entries, { agent, page, currentSessionId, others, verbose: true }) +\n \"\\n\" +\n why\n );\n }\n\n if (sub === \"new\") {\n const key = pickKey(sel);\n const name = args.join(\" \").trim() || undefined;\n if (daemonAlive(key))\n return await viaBridge(key, { kind: \"session.new\", ...(name ? { name } : {}) });\n // 没 daemon:能做的只有「丢掉当前会话记录」——下条消息会开新的。名字要等会话真的建好再 rename。\n const dir = accountDir(key);\n const userId = ownerOf(key);\n if (!userId) throw new Error(`账号 ${key} 没有 userId(account.json 不全),先重新登录。`);\n const cleared = clearSessionPointer(\n brainSessionMapPath(dir),\n sessionKey(agentNameOf(key), userId),\n );\n return [\n cleared ? \"已清空当前会话记录\" : \"当前本来就没有会话记录\",\n \"(daemon 未运行:下次启动后你发的第一条消息会开一个新会话)\",\n name ? `名字等新会话建好后再登记:wemind session rename 1 ${name}` : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n }\n\n if (sub === \"resume\") {\n const key = pickKey(sel);\n if (!args[0]) throw new Error(USAGE);\n requireDaemonAlive(key); // 恢复要真的让 agent load 那条会话:只有 daemon 里有 ACP 连接,做不了假\n return await viaBridge(key, { kind: \"session.resume\", ref: args.join(\" \") });\n }\n\n if (sub === \"rename\" || sub === \"delete\") {\n const key = pickKey(sel);\n const [ref, ...nameRest] = args;\n if (!ref || (sub === \"rename\" && !nameRest.length)) throw new Error(USAGE);\n if (bridgeReady(key))\n return await viaBridge(\n key,\n sub === \"rename\"\n ? { kind: \"session.rename\", ref, name: nameRest.join(\" \") }\n : { kind: \"session.delete\", ref },\n );\n // 只动登记表(+ 删除时可能清当前会话记录),daemon 不在也不影响它下次读到的结果\n const dir = accountDir(key);\n const userId = ownerOf(key);\n if (!userId) throw new Error(`账号 ${key} 没有 userId(account.json 不全),先重新登录。`);\n const agent = agentNameOf(key);\n if (sub === \"delete\") {\n const r = deleteSession(agent, userId, ref, { dir });\n if (!r.ok) {\n process.exitCode = 1;\n return `❌ ${r.error}`;\n }\n return `已从列表删除:${r.entry.name ?? r.entry.title ?? r.entry.sessionId.slice(0, 8)}。${\n r.clearedPointer ? \"它就是当前会话,下一条消息会开一个新的。\" : \"当前会话没有变。\"\n }`;\n }\n const r = renameSession(agent, userId, ref, nameRest.join(\" \"), dir);\n if (!r.ok) {\n process.exitCode = 1;\n return `❌ ${r.error}`;\n }\n return `已重命名:${r.entry.name}(当前 brain=${agent} 的会话)`;\n }\n\n throw new Error(USAGE);\n}\n\n/** 当前会话记录(供 CLI 展示;不常用但排查时值钱)。 */\nexport function cmdSessionCurrent(key: string): string {\n const agent = agentNameOf(key);\n const userId = ownerOf(key);\n const sid = userId ? currentSessionIdOf(agent, userId, accountDir(key)) : undefined;\n return sid\n ? `${key} 当前会话:${sid}(agent=${agent})`\n : `${key} 当前没有会话记录(下一条消息会开新会话)。agent=${agent}`;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { accountDir } from \"@wemind/wechat-ilink-core\";\nimport { awaitTaskAck, type TaskAck, writeTaskCmd as writeTaskCommand } from \"./task-inbox.js\";\nimport { daemonAlive, pickKey, requireDaemonAlive } from \"./cli-context.js\";\n\n// ===== 本地任务命令(文件桥):写 task-inbox/*.json,由 daemon 扫描执行;状态读 task-state.json,结果读 task-out/*.json =====\n\n/** 原子写一个命令文件到收件箱(临时文件 + rename,避免 daemon 读到半截);返回本次命令的答复编号。 */\nfunction writeTaskCmd(key: string, obj: Record<string, unknown>): string {\n return writeTaskCommand(accountDir(key), obj);\n}\n\n/**\n * 把任务答复翻成 CLI 文案与成败(导出仅供单测)。\n * `ok=false` = daemon 明确拒绝/失败(达上限、「没有 #9 号任务」),文案照打,但调用方要把退出码置 1;\n * 等不到答复不算失败(老版 daemon / 刚启动),只多一句说明。\n */\nexport function taskAckOutcome(\n a: Pick<TaskAck, \"ok\" | \"text\"> | null,\n fallback: string,\n): { text: string; failed: boolean } {\n if (!a)\n return {\n text: `${fallback}\\n(没等到 daemon 答复:它可能还在跑旧版本,或刚启动。用 task-list / task-status 核对实际状态。)`,\n failed: false,\n };\n return { text: a.text || fallback, failed: !a.ok };\n}\n\n/**\n * 等这次命令的答复(daemon 把真实结果写进 `task-out/<id>.json`)。\n * 三种情形分得很清楚:\n * - 有答复、有文案 → 打印 daemon 的那句话(失败原因、「没有 #9 号任务」这类只有它知道的事);\n * - 有答复、文案为空 → 命令成功但没有额外要说的,用调用方自己那句话(**立刻**,不干等);\n * - 没有答复 → 老版 daemon 不认这条通道(或它正在退出):回退到「已发送」措辞并说明一句,\n * 不把「没等到」当成「成功了」——达上限被拒与「派发成功」在旧措辞里长得一模一样。\n */\nasync function taskOutcome(\n key: string,\n ackId: string,\n fallback: string,\n timeoutMs = 5000,\n): Promise<string> {\n const o = taskAckOutcome(await awaitTaskAck(accountDir(key), ackId, timeoutMs), fallback);\n if (o.failed) process.exitCode = 1; // daemon 明确拒绝/失败(达上限、没有 #9 号任务):文案照打,退出码非 0\n return o.text;\n}\n\nexport function resolveTaskCwd(cwd: string | undefined, base: string = process.cwd()): string {\n const p = path.resolve(base, cwd || \".\");\n let st: fs.Stats;\n try {\n st = fs.statSync(p);\n } catch {\n throw new Error(`工作目录不存在:${p}`);\n }\n if (!st.isDirectory()) throw new Error(`--cwd 不是目录:${p}`);\n return p;\n}\n\nexport async function cmdTask(\n text: string,\n cwd: string | undefined,\n sel?: string,\n agentKey?: string,\n): Promise<string> {\n if (!text) throw new Error('用法: task \"<任务描述>\" [--cwd 目录] [--agent <名字>] [<key|label>]');\n const key = pickKey(sel);\n requireDaemonAlive(key);\n const useCwd = resolveTaskCwd(cwd);\n // 答复等 daemon:**起进程有结果之后**它才发(达上限/agent 起不来是那时才知道的),\n // 所以这里不能自己编「已派发」——旧写法轮询 task-state.json 的 active,会把「达上限被拒」和\n // 甚至上一条任务的编号报成成功。\n const ackId = writeTaskCmd(key, {\n kind: \"dispatch\",\n text,\n cwd: useCwd,\n ...(agentKey ? { agent: agentKey } : {}),\n });\n return await taskOutcome(\n key,\n ackId,\n `已发送任务(cwd=${useCwd})。看进展用 task-list。`,\n // 派发的答复要等「起进程有结果」:冷启动一个 agent 可能要几十秒,等得久一点好过说假话\n 30_000,\n );\n}\n\n/** 任务编号必须是正整数:0/负数以前被当成「没给编号」,会落到唯一那条任务上(发错目标)。 */\nexport function parseTaskId(raw: string): number {\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 1) throw new Error(`任务编号必须是正整数:${raw}`);\n return n;\n}\n\nexport async function cmdTaskReply(text: string, sel?: string, id?: number): Promise<string> {\n if (!text) throw new Error('用法: task-reply [\"#编号\"] \"<内容>\" [<key|label>]');\n const key = pickKey(sel);\n requireDaemonAlive(key);\n const ackId = writeTaskCmd(key, { kind: \"reply\", text, ...(id ? { id } : {}) });\n return await taskOutcome(key, ackId, `已发送给任务${id ? ` #${id}` : \"\"}:${text.slice(0, 40)}`);\n}\n\nexport async function cmdTaskStop(sel?: string, id?: number | \"all\"): Promise<string> {\n const key = pickKey(sel);\n requireDaemonAlive(key);\n const ackId = writeTaskCmd(key, { kind: \"stop\", ...(id ? { id } : {}) });\n const fallback =\n id === \"all\" ? \"已发送「结束全部任务」指令。\" : `已发送「结束任务${id ? ` ${id}` : \"\"}」指令。`;\n return await taskOutcome(key, ackId, fallback);\n}\n\ntype WmTaskRow = {\n id: number;\n userId: string;\n agent: string;\n status: string;\n turns: number;\n startedAt: number;\n lastOutput?: string;\n cwd?: string;\n};\nconst STATUS_ZH: Record<string, string> = {\n running: \"运行中\",\n waiting: \"等回复\",\n interrupted: \"已中断\",\n};\n\n/**\n * 读任务表(daemon 没跑也能读:文件就是唯一真相)。\n * 先看 `tasks.json`——那是任务表的**持久**副本(重启后也在,编号跨重启不归零);\n * 老版本只写过 `task-state.json`(状态记录),仍兼容读它。\n */\nfunction readTaskRows(key: string): WmTaskRow[] | null {\n const dir = accountDir(key);\n for (const name of [\"tasks.json\", \"task-state.json\"]) {\n try {\n const s = JSON.parse(fs.readFileSync(path.join(dir, name), \"utf-8\")) as {\n tasks?: WmTaskRow[];\n };\n if (Array.isArray(s.tasks)) return s.tasks;\n } catch {\n /* 换下一个 */\n }\n }\n return null;\n}\n\nexport function cmdTaskList(sel?: string): string {\n const key = pickKey(sel);\n const rows = readTaskRows(key);\n if (!rows)\n return `账号 ${key}:无任务表(tasks.json / task-state.json 都不存在,daemon 未跑过多任务)。`;\n // 任务表是 daemon 的活状态:它没在跑(或还在跑旧版)时,表里的「运行中」只是上次留下的,别当成现在时。\n const stale = daemonAlive(key)\n ? \"\"\n : \"\\n(daemon 未运行:以下状态是它上次留下的记录;未完成的任务会在下次启动时标为「已中断」,回「继续」可续跑。)\";\n if (!rows.length) return `账号 ${key}:当前没有任务。${stale}`;\n const lines = [`账号 ${key} 任务(${rows.length} 条):`];\n for (const t of rows) {\n const age = t.startedAt ? `${Math.round((Date.now() - t.startedAt) / 1000)}s` : \"?\";\n lines.push(\n ` #${t.id} [${t.agent}] ${STATUS_ZH[t.status] ?? t.status} · ${t.turns} 轮 · ${age}${t.cwd ? ` · cwd=${t.cwd}` : \"\"}`,\n );\n if (t.lastOutput) lines.push(` ${t.lastOutput.replace(/\\n/g, \" \").slice(0, 140)}`);\n }\n return lines.join(\"\\n\") + stale;\n}\n\nexport function cmdTaskStatus(sel?: string): string {\n const key = pickKey(sel);\n const rows = readTaskRows(key);\n if (rows && rows.length) return cmdTaskList(sel);\n const stateFile = path.join(accountDir(key), \"task-state.json\");\n let s: {\n active?: boolean;\n status?: string;\n turns?: number;\n startedAt?: number;\n lastOutput?: string;\n };\n try {\n s = JSON.parse(fs.readFileSync(stateFile, \"utf-8\"));\n } catch {\n return `账号 ${key}:无任务状态(task-state.json 不存在,daemon 可能未跑过任务)。`;\n }\n if (!s.active) return `账号 ${key}:当前无活跃任务(idle)。`;\n const since = s.startedAt ? `${Math.round((Date.now() - s.startedAt) / 1000)}s` : \"?\";\n return [\n `账号 ${key} 任务状态:`,\n ` 状态:${s.status} 轮次:${s.turns ?? 0} 已运行:${since}`,\n ` 最近输出:${s.lastOutput ? s.lastOutput.slice(0, 300) : \"(无)\"}`,\n ].join(\"\\n\");\n}\n","import { readOwnVersion } from \"@wemind/wechat-ilink-core\";\n\n/**\n * CLI 版本号与包身份(`wemind version` / `wemind --version` / help 头部)。\n *\n * **版本号唯一来源是 `package.json`**,这里在运行时读它,不再手写常量。\n * 为什么:以前版本号散落在 package.json 和本文件两处,`npm version` 只会改前者,漏改本文件会导致「发了 1.2.0、`wemind version` 还报 1.1.0」这种极难排查的不一致。\n * 现在 `npm run release:patch|minor|major` 一步到位,本文件自动跟着变。\n *\n * 查找逻辑在 `@wemind/wechat-ilink-core` 的 `readOwnVersion()`(与 MCP 包共用一份):\n * 必须把**本文件的** `import.meta.url` 传进去,否则会命中的是 core 自己的 package.json。\n */\n\n/** 期望的 npm 包名。这是**独立常量**,故意不从 package.json 读——它是校验基准。 */\nexport const CLI_PKG_NAME = \"@wemind/cli\";\n\n/**\n * 包版本。正常情况下等于 `package.json` 的 `version`;若查找失败(不该发生),退化为可识别的占位值,单测会把它当失败信号。\n */\nexport const CLI_VERSION = readOwnVersion(import.meta.url, CLI_PKG_NAME) ?? \"0.0.0-unknown\";\n\n/** 单行版本串(供 --version 输出):命令名 + 版本,包名放括号里便于反馈问题时对上 npm 页面。 */\nexport function versionLine(): string {\n return `wemind ${CLI_VERSION} (${CLI_PKG_NAME}, node ${process.versions.node}, ${process.platform}-${process.arch})`;\n}\n","#!/usr/bin/env node\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n WEMIND_ROOT,\n accountDir,\n listAccounts,\n registerAccount,\n unregisterAccount,\n setLabel,\n getDefaultKey,\n setDefaultKey,\n loadAccount as readAccount,\n notifyStop,\n addAllowFrom,\n removeAllowFrom,\n readAllowFrom,\n} from \"@wemind/wechat-ilink-core\";\nimport { pm2Start, pm2Stop } from \"./pm2-runner.js\";\nimport { watchdogStart } from \"./watchdog-runner.js\";\nimport {\n CLI_PID_MARKERS,\n brainPidMarkers,\n commandLines,\n isAlive,\n matchesMarkers,\n} from \"./proc-identity.js\";\nimport { resolveAgent } from \"./brain-agents.js\";\nimport { loadBrainConfig } from \"./brain-config.js\";\nimport { agentLabel, cmdBrain } from \"./cli-brain.js\";\nimport { cmdLogin, finalizeStaging } from \"./cli-login.js\";\nimport { cmdSession, cmdSessionCurrent } from \"./cli-session.js\";\nimport { daemonAlive, pickKey, readPid, resolveKey } from \"./cli-context.js\";\nimport {\n cmdTask,\n cmdTaskList,\n cmdTaskReply,\n cmdTaskStatus,\n cmdTaskStop,\n parseTaskId,\n} from \"./cli-task.js\";\n\nexport { pickKey } from \"./cli-context.js\";\nexport { agentLabel } from \"./cli-brain.js\";\nexport { createLoginStagingDir } from \"./cli-login.js\";\nexport { resolveTaskCwd, taskAckOutcome } from \"./cli-task.js\";\nimport { readPidRecords, recordMarkers } from \"./pid-record.js\";\nimport { BRAIN_RUNTIME_FILE, readBrainState } from \"./brain-state.js\";\nimport { CLI_VERSION, versionLine } from \"./version.js\";\nimport { renderHelp } from \"./chat-commands.js\";\n\n/**\n * WeMind 多账号管理 CLI。\n * 账号 key = 由微信 botId 派生的唯一标识;每个账号独立目录 ~/.wemind/<key>。\n * label = 人类可读别名(可改、可重复);管理命令可用 key 或 label 选择账号。\n * 推荐用 `login` 一条命令完成:自刷新二维码 → 扫码 → 自动登记(→ 可选自动启动)。\n */\n\n/**\n * 大脑进程的身份记录(`brain.pid` 现在是 JSON,见 pid-record.ts):首条非 legacy 记录。\n * 记录里存的是**起进程时真实用的** bin/args —— 比「按当前配置猜」可靠:\n * `wemind brain set X` 之后、热切换窗口内、以及 brain≠worker 时,配置值都可能与实际在跑的进程不一致。\n * (导出仅供单测。)\n */\nexport function brainRecord(dir: string): {\n pid: number | null;\n markers: string[][] | null;\n agent: string;\n} {\n const rec = readPidRecords(path.join(dir, \"brain.pid\"))[0];\n if (!rec) return { pid: null, markers: null, agent: \"\" };\n const markers = recordMarkers(rec);\n return { pid: rec.pid, markers: markers.length ? markers : null, agent: rec.agent };\n}\n/** 阻塞式 sleep(CLI 是同步流程;Atomics.wait 不占 CPU、不比 spawn 一个进程贵)。 */\nfunction sleepSync(ms: number): void {\n try {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n } catch {\n /* ignore */\n }\n}\n\n/**\n * 某 pid 的全部后代(POSIX)。Windows 不需要——`taskkill /T` 自己会走进程树。\n * 用 `ps -A -o pid=,ppid=` 一次取全表再在内存里建树,比逐层 `pgrep -P` 少起进程。\n * (导出仅供单测;CLI 内部不对外。)\n */\nexport function descendantPids(pid: number): number[] {\n try {\n // procps(Linux)与 BSD/macOS 的 ps 都认 `-A`;个别精简环境(BusyBox)只有 `-e`,故兜底一次。\n const psArgs = [\n [\"-A\", \"-o\", \"pid=,ppid=\"],\n [\"-e\", \"-o\", \"pid=,ppid=\"],\n ];\n let stdout = \"\";\n for (const args of psArgs) {\n const r = spawnSync(\"ps\", args, { encoding: \"utf8\", timeout: 5000 });\n if (r.status === 0 && r.stdout) {\n stdout = r.stdout;\n break;\n }\n }\n const childrenOf = new Map<number, number[]>();\n for (const line of stdout.split(/\\r?\\n/)) {\n const m = line.trim().match(/^(\\d+)\\s+(\\d+)$/);\n if (!m) continue;\n const child = Number(m[1]);\n const parent = Number(m[2]);\n const bucket = childrenOf.get(parent);\n if (bucket) bucket.push(child);\n else childrenOf.set(parent, [child]);\n }\n const out: number[] = [];\n const walk = (p: number): void => {\n for (const c of childrenOf.get(p) ?? []) {\n out.push(c);\n walk(c);\n }\n };\n walk(pid);\n return out;\n } catch {\n return [];\n }\n}\n\n/**\n * 杀「pid 及其整棵进程树」。\n *\n * Windows:`taskkill /T /F` 本来就连带子树。\n * POSIX:`process.kill(pid)` **只**结束该进程,不动它的子进程——daemon 被停后,它拉起的 ACP 大脑 / worker 会留下来继续占着会话与文件句柄(Windows 的 `taskkill /T` 会连带子树,所以这个缺陷只在 POSIX 暴露)。\n * 所以这里先收集后代、由叶子往根 SIGTERM(daemon 的 SIGTERM 处理器会自己清 pid 文件),等一小会儿再对仍存活的补 SIGKILL。\n */\nfunction killProcessTrees(pids: number[]): void {\n const unique = [...new Set(pids.filter((p) => isAlive(p)))];\n if (!unique.length) return;\n if (process.platform === \"win32\") {\n for (const pid of unique)\n spawnSync(\"taskkill\", [\"/PID\", String(pid), \"/T\", \"/F\"], {\n stdio: \"ignore\",\n windowsHide: true, // 避免瞬时闪出控制台窗口\n });\n return;\n }\n // 先收集所有后代(按叶子优先排序:后出现的更深,反过来就是叶子先)\n const targets: number[] = [];\n for (const pid of unique) targets.push(...descendantPids(pid).reverse(), pid);\n const all = [...new Set(targets)].filter((p) => isAlive(p));\n for (const pid of all) {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n /* ignore */\n }\n }\n sleepSync(800); // 给 SIGTERM 处理器一点时间(daemon 会清 pid 文件、关子进程)\n for (const pid of all) {\n if (!isAlive(pid)) continue;\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n /* ignore */\n }\n }\n}\n/** 启动某账号的 daemon(方案 a:委托 pm2 常驻,跑 `wemind daemon`,注入 WECHAT_ACCOUNT)。 */\nfunction startDaemon(key: string, runner?: string): void {\n // 用 process.argv[1](即正在执行的 cli.js 绝对路径),而不是 dirname(import.meta.url):\n // 发布后 tsdown 把 manage.ts 内联进 dist/cli.js,两者恰好相同;但若将来拆成多 chunk,argv[1] 才是「用户装的那个 bin」,dirname(import.meta.url) 可能是 dist/chunks/ 下。\n const cliPath =\n process.argv[1] || path.join(path.dirname(fileURLToPath(import.meta.url)), \"cli.js\");\n // start 前先停现有任何 runner(pm2 app / builtin 后台守护进程),避免同账号双跑抢 token\n stopDaemon(key);\n const useRunner = (runner || process.env.WEMIND_RUNNER || \"builtin\").toLowerCase();\n const r = useRunner === \"pm2\" ? pm2Start(key, cliPath) : watchdogStart(key, cliPath);\n if (!r.ok) process.exitCode = 1; // runner 起不来时别让「已启动」的文案配一个成功退出码\n console.log(r.msg);\n}\n\nexport type StopResult = {\n msg: string;\n /** kill 后所有目标都确认不在了。false 时调用方**不能**删账号目录(进程可能还在写)。 */\n ok: boolean;\n};\n\n/** 汇总 stop 的三类结果。`survivors` 是 kill 后仍活着的目标;导出仅供单测。 */\nexport function stopSummary(stopped: string[], skipped: string[], survivors: string[]): StopResult {\n const parts: string[] = [];\n if (stopped.length) parts.push(`已停止 ${stopped.join(\" + \")}`);\n if (skipped.length)\n parts.push(`跳过 ${skipped.join(\" + \")}(非本程序,未杀;已清陈旧 pid 文件)`);\n if (survivors.length) parts.push(`未能停止 ${survivors.join(\" + \")}(仍在运行)`);\n return { msg: parts.length ? parts.join(\";\") : \"无运行中的进程\", ok: survivors.length === 0 };\n}\n\n/**\n * 停止某账号的常驻:先 pm2 delete(方案 a),再按 pid 兜底杀 wrapper/daemon/brain/worker(兼容直接启动的)。停的是**整条链**:除列出的进程外,还连带它们的子进程树。\n * 只有确认全部目标都停下才清 pid 文件:否则 `delete` 的存活检查会在文件被删后失去依据(旧实现先删文件再查,必然判「已停止」)。\n */\nfunction stopDaemon(key: string): StopResult {\n pm2Stop(key); // best-effort:停掉 pm2 托管的 app(若有)\n const dir = accountDir(key);\n const ag = resolveAgent(loadBrainConfig(dir), dir);\n // brain:优先用身份记录里「起进程时真实用的」bin/args 算特征串;没有记录(旧账号/未起过)才回退配置。\n const brec = brainRecord(dir);\n const brainMarkers = brec.markers ?? brainPidMarkers(ag.bin, ag.args);\n const cand: Array<{ pid: number | null; name: string; markers: string[][] }> = [\n { pid: readPid(dir, \"wrapper.pid\"), name: \"wrapper\", markers: CLI_PID_MARKERS },\n { pid: readPid(dir, \"daemon.pid\"), name: \"daemon\", markers: CLI_PID_MARKERS },\n { pid: brec.pid ?? readPid(dir, \"brain.pid\"), name: \"brain\", markers: brainMarkers },\n ];\n // worker:现在可能**多条并存**(多任务/多 agent),身份记录 `workers.pid.json` 里每条都带着起进程时真实用的 bin/args\n // → 逐条按自己的特征串判定。旧账号只有裸整数 `worker.pid`(无 bin/args)→ 回退「按配置算特征串」的老办法。\n const wrecs = readPidRecords(path.join(dir, \"workers.pid.json\"));\n if (wrecs.length) {\n for (const w of wrecs)\n cand.push({\n pid: w.pid,\n name: `worker${w.taskId ? `#${w.taskId}` : \"\"}(${w.agent || \"?\"})`,\n markers: recordMarkers(w),\n });\n } else {\n cand.push({\n pid: readPid(dir, \"worker.pid\"),\n name: \"worker\",\n markers: brainPidMarkers(ag.bin, ag.args),\n });\n }\n // 只杀「活着**且身份确认**」的:pid 会被复用,陈旧 pid 文件可能指向别人的进程 → 绝不误杀。\n // 命令行一次批量查(Windows 下较慢);正常情况(陈旧 pid 早已不存在)根本不会发起查询。\n const lines = commandLines(cand.map((c) => c.pid));\n const stopped: string[] = [];\n const skipped: string[] = [];\n const toKill: number[] = [];\n const nameOf = new Map<number, string>();\n const seen = new Set<number>();\n for (const c of cand) {\n if (!c.pid || seen.has(c.pid) || !isAlive(c.pid)) continue;\n seen.add(c.pid);\n if (matchesMarkers(lines.get(c.pid), c.markers)) {\n toKill.push(c.pid);\n nameOf.set(c.pid, c.name);\n stopped.push(`${c.name} ${c.pid}`);\n } else {\n skipped.push(`${c.name} ${c.pid}`); // 活着的 pid 但不是我们的(pid 复用)→ 不杀\n }\n }\n // 身份校验都通过了才动手:一次性杀掉这些进程**及其子进程树**(见 killProcessTrees 注释)。\n killProcessTrees(toKill);\n // kill 之后仍活着的目标(权限不足、进程卡住等):保留 pid 文件让下一次 stop 还能找到它。\n const survivors = toKill\n .filter((pid) => isAlive(pid))\n .map((pid) => `${nameOf.get(pid) ?? \"进程\"} ${pid}`);\n if (!survivors.length)\n for (const f of [\n \"wrapper.pid\",\n \"daemon.pid\",\n \"brain.pid\",\n \"worker.pid\",\n \"workers.pid.json\",\n BRAIN_RUNTIME_FILE,\n ]) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n return stopSummary(stopped, skipped, survivors);\n}\n\n/** 旧流程的固定暂存目录:仅 `wimind finalize` 兼容命令使用;新登录走 createLoginStagingDir()。 */\nconst LEGACY_STAGING = path.join(WEMIND_ROOT, \".staging\");\n\nfunction cmdList(): string {\n const def = getDefaultKey();\n const accs = listAccounts();\n if (!accs.length) return \"(无已登记账号)。用 `login` 添加。\";\n // 先收集各账号的 pid + 大脑特征,再**一次性**批量查命令行(Windows 下每次查询较慢);只凭「pid 活着」显示状态会在重启后 pid 复用时骗人,所以要带身份校验。\n const entries = accs.map((a) => {\n const dir = accountDir(a.key);\n const ag = resolveAgent(loadBrainConfig(dir), dir);\n const brec = brainRecord(dir);\n return {\n a,\n dir,\n dpid: readPid(dir, \"daemon.pid\"),\n bpid: brec.pid ?? readPid(dir, \"brain.pid\"),\n // 身份记录里是「起进程时真实用的」bin/args,比配置值可靠(brain set 未重启 / 热切换窗口 / brain≠worker)\n brainMarkers: brec.markers ?? brainPidMarkers(ag.bin, ag.args),\n brainAgent: brec.agent,\n // daemon 启动/热切换后写入的「实际生效大脑」;只在 daemon 活着时采信(下面按 dpid 判)\n brainRuntime: readBrainState(dir),\n // 多 task worker:`workers.pid.json` 是记录数组(每条一个 agent/任务),逐条按自己的身份校验\n workers: readPidRecords(path.join(dir, \"workers.pid.json\")).map((w) => ({\n pid: w.pid,\n agent: w.agent,\n state: w.state,\n taskId: w.taskId,\n markers: recordMarkers(w),\n })),\n };\n });\n const lines = commandLines(\n entries.flatMap((e) => [e.dpid, e.bpid, ...e.workers.map((w) => w.pid)]),\n );\n const aliveOursIn = (pid: number | null, markers: string[][]): boolean =>\n !!pid && matchesMarkers(lines.get(pid), markers);\n return entries\n .map((e) => {\n const runningNow = aliveOursIn(e.dpid, CLI_PID_MARKERS);\n const running = runningNow ? `运行中(pid ${e.dpid})` : \"已停止\";\n const brainPid = aliveOursIn(e.bpid, e.brainMarkers)\n ? ` brain(pid ${e.bpid}${e.brainAgent ? ` ${e.brainAgent}` : \"\"})`\n : \"\";\n const live = e.workers.filter((w) => aliveOursIn(w.pid, w.markers));\n const pool = live.length\n ? ` pool[${live.map((w) => `${w.agent}${w.taskId ? `#${w.taskId}` : \"\"}:${w.state}`).join(\", \")}]`\n : \"\";\n 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}`;\n })\n .join(\"\\n\");\n}\n\nfunction cmdAdopt(key: string, label?: string): string {\n if (!key) throw new Error(\"用法: adopt <key> [--label 名]\");\n const acc = readAccount(accountDir(key));\n if (!acc?.botId) throw new Error(`${accountDir(key)} 无有效 account.json`);\n const meta = registerAccount({\n key,\n botId: acc.botId,\n userId: acc.userId,\n baseUrl: acc.baseUrl,\n label: label || key,\n });\n if (acc.userId) addAllowFrom(acc.userId, accountDir(key)); // 接管时把登录者种入白名单\n return `已登记:key=${key} label=${meta.label} botId=${acc.botId}`;\n}\n\nasync function cmdLogout(sel: string): Promise<string> {\n const key = resolveKey(sel);\n const dir = accountDir(key);\n const acc = readAccount(dir);\n const stop = stopDaemon(key);\n if (acc?.botToken) {\n try {\n await notifyStop({ baseUrl: acc.baseUrl, token: acc.botToken });\n } catch {\n /* ignore */\n }\n }\n for (const f of [\"account.json\", \"state.json\", \"qr_login.json\", \"qrcode.png\", \"qrcode.txt\"]) {\n try {\n fs.unlinkSync(path.join(dir, f));\n } catch {\n /* ignore */\n }\n }\n return `账号 ${key} 已登出(${stop.msg})。目录与会话保留,可重新登录。`;\n}\n\n/**\n * `delete` 前的最后一道闸:stop 失败/被跳过时 daemon 可能还活着,此时删目录等于删它正在用的文件。\n * 导出仅供单测。\n */\nexport function deleteGuard(key: string, alive: boolean): string | null {\n return alive\n ? `账号 ${key} 仍有 WeMind 进程未能停止(仍在运行),为避免删掉它正在使用的文件,本次**没有删除目录**。\\n先确认并结束它:wemind stop ${key}(或看 wemind list / 进程管理器),再执行 delete。`\n : null;\n}\n\nfunction cmdDelete(sel: string): string {\n const key = resolveKey(sel);\n const stop = stopDaemon(key);\n // 用 stop 的实际结果判定,而不是再看 daemonAlive:旧实现先删 pid 文件再查,永远得到「已停止」。\n const blocked = deleteGuard(key, !stop.ok || daemonAlive(key));\n if (blocked) throw new Error(blocked);\n try {\n fs.rmSync(accountDir(key), { recursive: true, force: true });\n } catch (e) {\n throw new Error(`删除目录失败: ${String(e)}`);\n }\n unregisterAccount(key);\n return `账号 ${key} 已删除(${stop.msg},已移除目录与注册)。`;\n}\n\nfunction cmdStart(sel?: string, runner?: string): string {\n const key = pickKey(sel); // 省略账号 = 默认账号\n startDaemon(key, runner);\n return `已启动 daemon(账号 ${key})。用 list 查看运行状态。`;\n}\n\nexport async function runManage(argv: string[]): Promise<void> {\n const cmd = argv.shift();\n const rest = [...argv];\n // 取开关值:缺值(`--cwd` 后面没东西,或紧跟另一个开关)直接报错——以前会静默当成「没写」,用默认值继续跑。\n const takeValue = (flag: string): string | undefined => {\n const i = rest.indexOf(flag);\n if (i < 0) return undefined;\n const v = rest[i + 1];\n if (v === undefined || v.startsWith(\"--\")) throw new Error(`${flag} 缺少参数值`);\n rest.splice(i, 2);\n return v;\n };\n const label = takeValue(\"--label\");\n const si = rest.indexOf(\"--start\");\n const autoStart = si >= 0;\n if (autoStart) rest.splice(si, 1);\n const cwd = takeValue(\"--cwd\");\n const runner = takeValue(\"--runner\");\n if (runner && ![\"builtin\", \"pm2\"].includes(runner.toLowerCase()))\n throw new Error(`未知 runner「${runner}」(可用:builtin / pm2)`);\n const agentKey = takeValue(\"--agent\");\n\n switch (cmd) {\n case \"version\":\n case \"--version\":\n case \"-v\":\n console.log(versionLine());\n break;\n case \"login\":\n await cmdLogin(label, autoStart, runner, { startDaemon });\n break;\n case \"list\":\n console.log(cmdList());\n break;\n case \"brain\":\n console.log(await cmdBrain(rest, { startDaemon }));\n break;\n case \"session\": {\n const sub = (rest[0] ?? \"\").toLowerCase();\n if (sub === \"current\") console.log(cmdSessionCurrent(pickKey(rest[1])));\n else console.log(await cmdSession(rest));\n break;\n }\n case \"adopt\":\n console.log(cmdAdopt(rest[0], label));\n break;\n case \"start\":\n console.log(cmdStart(rest[0], runner));\n break;\n case \"stop\": {\n const k = pickKey(rest[0]); // 省略账号 = 默认账号(答复带上解析到的账号,避免「静默换目标」)\n console.log(`账号 ${k}:${stopDaemon(k).msg}`);\n break;\n }\n case \"task\":\n console.log(await cmdTask(rest[0], cwd, rest[1], agentKey));\n break;\n case \"task-reply\": {\n // 兼容两种写法:`task-reply \"#3 内容\"` 与 `task-reply 3 \"内容\"`\n const first = rest[0] ?? \"\";\n const m = first.match(/^[##](\\d+)\\s*([\\s\\S]*)$/);\n if (m) console.log(await cmdTaskReply(m[2], rest[1], parseTaskId(m[1])));\n else if (rest.length >= 2 && /^\\d+$/.test(first))\n console.log(await cmdTaskReply(rest[1], rest[2], parseTaskId(first)));\n else console.log(await cmdTaskReply(first, rest[1]));\n break;\n }\n case \"task-list\":\n console.log(cmdTaskList(rest[0]));\n break;\n case \"task-status\":\n console.log(cmdTaskStatus(rest[0]));\n break;\n case \"task-stop\": {\n const a = rest[0];\n const id: number | \"all\" | undefined =\n a === \"all\" || a === \"全部\" ? \"all\" : a && /^\\d+$/.test(a) ? parseTaskId(a) : undefined;\n console.log(await cmdTaskStop(id === undefined ? a : rest[1], id));\n break;\n }\n case \"label\": {\n const k = resolveKey(rest[0]);\n setLabel(k, rest.slice(1).join(\" \"));\n console.log(`已设置 ${k} 的别名为「${rest.slice(1).join(\" \")}」`);\n break;\n }\n case \"set-default\": {\n const k = resolveKey(rest[0]);\n setDefaultKey(k);\n console.log(`默认账号 = ${k}`);\n break;\n }\n case \"logout\":\n console.log(await cmdLogout(rest[0]));\n break;\n case \"delete\":\n console.log(cmdDelete(rest[0]));\n break;\n case \"finalize\": {\n const { msg } = finalizeStaging(LEGACY_STAGING, label);\n console.log(msg);\n break;\n } // 兼容旧流程(staging 已有登录时直接收进账号目录)\n case \"allow\": {\n const k = resolveKey(rest[0]);\n const uid = rest[1];\n if (!uid) throw new Error(\"用法: allow <key|label> <userId>\");\n const list = addAllowFrom(uid, accountDir(k));\n console.log(`已加入白名单 ${uid} → ${k}\\n当前清单(${list.length}):${list.join(\", \")}`);\n break;\n }\n case \"revoke\": {\n const k = resolveKey(rest[0]);\n const uid = rest[1];\n if (!uid) throw new Error(\"用法: revoke <key|label> <userId>\");\n const list = removeAllowFrom(uid, accountDir(k));\n console.log(\n `已从白名单移除 ${uid} → ${k}\\n当前清单(${list.length}):${list.join(\", \") || \"(空)\"}`,\n );\n break;\n }\n case \"allowlist\": {\n const k = resolveKey(rest[0]);\n const list = readAllowFrom(accountDir(k));\n console.log(\n `${k} 登录者白名单(${list.length}):\\n${list.map((u) => \" - \" + u).join(\"\\n\") || \" (空)\"}`,\n );\n break;\n }\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n console.log(helpText());\n break;\n default:\n // 拼错命令不能静默成功:脚本里 `wemind strat x` 以前 exit 0,看起来像成功。\n // 提示与帮助都走 stderr(stdout 保持为空),并以 2 退出(2 = 用法错误,与「运行出错」的 1 区分)。\n console.error(`未知命令:${cmd}`);\n console.error(helpText());\n process.exitCode = 2;\n }\n}\n\nexport function helpText(): string {\n return `WeMind 多账号管理(wemind v${CLI_VERSION})\n用法: wemind <command> 其余:wemind version | help\n\n login [--label 名] [--start] 一条命令完成扫码登录:自刷新二维码 + 自动登记(--start 顺带启动 daemon)\n list 列出账号、别名、运行状态(含 brain 进程与 worker 池)\n start [key|label] 启动某账号的 daemon(省略 = 默认账号)\n stop [key|label] 停止某账号的 daemon(连带 brain 与全部 worker 进程树;省略 = 默认账号)\n brain set <agent 名|auto> [--bin X] [--args \"…\"] [--extra-args \"…\"] [--shell|--no-shell] [--model X|auto] [--restart] [<key|label>]\n 改账号大脑**配置**(写 brain.json;只给 agent 名且 daemon 认桥时当场应用;带 --bin/--args/--extra-args/--shell/--model 只写盘——除 --shell 外可用同名 brain switch 当场应用;--extra-args 追加在预设参数后,不会漏掉 ACP 子命令)\n brain unset|check|show [<key|label>] 清除配置 / 真机 ACP 自检 / 查看配置与生效情况\n logout <key|label> 停 daemon + 通知下线 + 清凭证(保留目录,可重登)\n delete <key|label> 停 daemon + 删除目录 + 移除注册\n label <key|label> <新别名> 改别名\n set-default <key|label> 设默认账号\n adopt <key> [--label 名] 把已存在的账号目录登记进注册表\n allowlist <key|label> 查看该账号的登录者白名单(allowFrom)\n allow <key|label> <userId> (兜底)把某 userId 加进白名单(正常只会有登录者本人)\n revoke <key|label> <userId> (兜底)从白名单里移除某 userId\n\n【与微信等价的操作指令】以下都能带 [<key|label>](省略则用默认账号)。\n运行中的命令经**控制桥**交给 daemon 执行(CLI 与微信走同一段代码,行为一致);daemon 没跑时,\n读命令照常可用,部分写命令会只改文件或提示先启动。\n\n${renderHelp(\"owner\", \"cli\", { sections: [\"brain\", \"session\", \"task\", \"misc\"] })}\n\n说明:key = 由微信 botId 派生的唯一标识(目录名);label = 人类可读别名。多数命令可用 key 或 label。\n省略账号的命令(start/stop 与会话/任务类)用**默认账号**:首个登记的账号自动成为默认,可用 set-default 改、list 里看 [默认]。\nlogout/delete 等破坏性命令仍必须显式指定账号。`;\n}\n// 入口由 cli.ts 调用 runManage(argv);此处不再顶层自启。\n","#!/usr/bin/env node\n/**\n * wemind —— 完整应用的命令行入口(bin)。\n * - 子命令 `daemon`:跑 daemon 本体(长轮询 + 大脑 + worker),通常由 pm2 调用,用户不直接用。\n * - 其余子命令(login/list/start/stop/task…):交给 manage 派发。\n */\nimport { cliHook } from \"./cli-dispatch.js\";\nimport { runDaemon } from \"./index.js\";\nimport { runManage } from \"./manage.js\";\nimport { runWatchdog } from \"./watchdog-runner.js\";\n\nconst argv = process.argv.slice(2);\nconst hook = cliHook(argv);\n\nif (hook === \"daemon\") {\n runDaemon().catch((err: unknown) => {\n console.error(\"[wemind/wechat-daemon] fatal:\", err);\n process.exit(1);\n });\n} else if (hook === \"watchdog\") {\n runWatchdog().catch((err: unknown) => {\n console.error(\"[wemind/wechat-daemon/watchdog] fatal:\", err);\n process.exit(1);\n });\n} else {\n runManage(argv).catch((e: unknown) => {\n console.error(\"error:\", e instanceof Error ? e.message : e);\n process.exit(1);\n });\n}\n"],"mappings":"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"}
|