09 - 注意事项与常见问题¶
README 与代码的差异(重要)¶
项目根目录的 README 面向商店用户,只保留安装/配置/命令/工具手册,开发文档见 docs/01-项目概览 与 docs/07-开发指南。README 会随发版同步,发布前按 07-开发指南「开发完成检查清单」核对;若发现 README 与本章或源码不一致,以源码为准,并同步 README。
- 旧版 README"开发文档"中的目录结构(
src/tools/、src/AI/)、ConfigManager、ToolManager、tool_choice字段、cmdInfo等均为旧版架构,已不存在;README 已移除该章节。 - README 配置手册的模型配置为一份 JSON(v5),与代码一致;旧的「api连接」「模型规则」两个 TOML 框已废弃(4.25.0 起不再读取,也不做迁移)。发布流程为推送
v*标签自动发版(见 07-开发指南「CI 与发布」)。
代码中的已知不一致¶
src/config/static_config/meta.ts的VERSION与src/changelog/<版本>.ts的最新版本条目需保持一致(当前 4.28.0);checkUpdate按VERSION比较输出对应更新日志。聚合索引src/changelog.ts由npm run changelog:gen生成。- 实际构建产物是
dist/aiplugin4.js(esbuild 单文件);package.json的main/typings/engines字段与陈旧build/目录均已清理(不参与构建)。 - 实际构建/测试依赖较新 Node(CI 用 20);本地若无
dist/先npm run build。 - 4.7 已删除未注册的开发示例文件(
sub_cmd/sample.ts、agent/agents/samples.ts等);新增示例不要注册到运行时。 src/note.txt是作者备忘(TODO 与重构思路),不是运行代码,其中的方案可能与现状不符。
运行与部署注意事项¶
- SealDice v1.4.6 分离部署(napcat/llonebot)下图片相关功能存在问题,建议 v1.5.0+。
run_core_command需要启动ob11-core-bridge,并在「后端 → 后端配置」的backends.ob11-core-bridge填/pluginWebSocket 地址(SealDice 正向连接/core);run_ext_command在插件内本地直调扩展solve,无需中间件/MCP。核心指令白名单使用core|指令名,但core|ext无需加入白名单即可查看扩展。两条路径都复用多消息收集器(空闲窗口 + 最大消息数 + 超时)。- ob11 相关工具(发消息、取用户/群信息、禁言、打卡等)需要安装 ob11 网络连接依赖或 HTTP 依赖;未安装时相关工具返回"未找到ob11网络连接依赖"。
- ⚠️ 4.25.0 破坏性升级注意(模型配置 v4 → v5):「模型」页的「api连接」「模型规则」两个 TOML 框已废弃——两个配置项已注销、UI 上不再出现,插件不再读取它们,也不做自动迁移。原因是海豹的
seal.ext没有配置写入 API,"读旧→转换→写新"的第三步永远做不到,兼容垫片只能退化成每次启动跑一遍、旧配置删不掉、用户也看不到转换结果。升级后请用配置生成器重新生成一份 JSON 粘进「模型配置」(或按 README「模型」章手写);不重建的话插件会载入出厂 DeepSeek 示例并打 WARN 提示,原有模型全部不可用。字段对照:api_key→providers[].apiKey、base_url→providers[].baseURL(必填、不再兜默认值)、provider→通常不用填(由baseURL的 host 自动识别)、models→providers[].models[]({id, name, kind})、[types]→models[].kind、ignore = 1→providers[].enabled = false、[request]→providers[].request(连接级)或models[].request.request(模型级)、规则的use组→顶层purposes+groups、规则的[body]/[params]/[prompt]/[response]与models定向→models[].request.{body,params,prompt,response} - ⚠️ 4.27.0 升级注意(后端配置 JSON 化 / 论坛独立页签):「后端」页签的 8 个独立地址项合并为单框 JSON「后端配置」(v1),旧键不再注册、UI 隐藏、不再读取——不迁移、不兼容;升级后请启动 aiplugin4-backends WebUI,点「📋 复制后端配置」后整段粘贴。空的「后端配置」= 全部使用出厂默认地址,不报错;校验失败整份拒绝并回退默认。论坛地址 / 论坛API Token / 论坛签名密钥移到新的「论坛」页签(键名不变,值保留)。不支持降级,升级前请备份
config.json。同时修复 QQ 官方 bot 图片 URL 的&未解码问题(插件侧在解析消息图片时还原),并补上 image-host 的upstream_status: 4xx永久失败识别;MCPimage_search返回的图床 id 可直接写[img:ID]发送(插件用图床稳定地址,不落 storage)。图床对外地址已写进配置(image-host.publicUrl):url只给插件访问图床接口(可为127.0.0.1),publicUrl给 QQ 协议端与模型(填 IP 或域名);启动器「复制后端配置」的「图床对外地址」会自动生成它(留空自动探测本机 IP),不用复制后手改。没有publicUrl且url是回环时,插件降级为「发图落本地文件、模型用 base64」并在启动探测时 WARN。 - ⚠️ 4.26.1 升级注意(角色/技能/MCP/知识库配置 JSON 化): 四项配置从多框模板改为单框 JSON,使用新键名「角色设定」「技能列表」「MCP服务器」「知识库配置」;旧键「角色扮演设定」「技能配置」「MCP服务器配置」「知识库」不再注册、UI 隐藏、插件不再读取,不做自动迁移(海豹没有配置写入 API,原因同模型 v5)。升级后请用配置生成器对应页签重新生成并粘贴;旧值仍留在
config.json里但不会生效。不支持降级(旧版本读不了新的 String 配置),升级前请备份config.json。MCP 同时改成标准mcpServersJSON,且插件默认配置为空(不预置服务器),保留「是否启用MCP」总开关(默认关闭);技能的标准SKILL.mdfrontmatter 只在生成器文件导入时使用,插件运行时只读 JSON。 - ⚠️ 4.25.0 另一处行为变更(视觉不再按命名猜测):模型类型里只有视觉不再自动推断——
gpt-4o、gemini、qwen-vl、*vision这类名字不会被认成视觉模型,一律归text。原因是一批正则最容易过期(新模型名永远追不上),且猜错会让用户误以为模型已具备视觉能力。要识图必须在「模型配置」里显式写"kind": "vision"(配置生成器会自动预填);接口自报的能力位(vision)仍作正向证据。嵌入/生图/语音/reranker 仍按命名白名单推断。 - 4.24.0 升级注意(语音/生图依赖移除):生成音频(tts)与生成图片(tti)依赖插件已废弃,完整包不再附带(只留 ob11 网络连接)。请卸载旧的 tts/tti 依赖插件——插件不再读取
globalThis.tts/globalThis.tti/globalThis.aiDrawing。语音合成与文生图改由「模型」页模型配置声明(给模型写kind: "tts"/"gen"即进入tts/image-generation用途候选):请求结构由插件内置的供应商预设提供(逐条迁移自旧依赖插件的 MODELS.md,覆盖 OpenAI/硅基流动/智谱/百炼/Google Cloud/Gemini/MiniMax/FishAudio/ElevenLabs/火山方舟/腾讯 TokenHub/Stability/百度/本地自建等),所以一般什么都不用写——只在接口与预设不一致时才写该模型的request.body(默认逐键合并,bodyMode: "replace"改回整体替换)或request.request/request.response(逐键覆盖);命令入口由旧的.tts/.tti改为.ai tts/.ai tti,并有.ai media查看/取消任务队列。注意baseURL推荐填根地址(如https://api.siliconflow.cn/v1),完整接口地址也能用(已以预设路径结尾时不再重复拼接)。 - 「ai语音使用的音色」下拉的归属(易误解):该下拉服务的是协议端自带的 AI 语音(NapCat/Lagrange 的 AI 语音 API,角色名如小新/猴哥/妲己),与插件 TTS 无关;这条链路本就无需配置模型,现在 AI 直接用
call_ob11_api调get_ai_characters(查角色,取character_id)/send_group_ai_record、send_private_ai_record(发送)/get_ai_record(只取语音 URL),用法与常见角色名见默认知识库 ob11-api。下拉里的「自定义」选项对应的才是插件 TTS 链路(当年需额外的 tts 依赖 + ffmpeg),现已内置:内置预设里已经带{voice}占位符,改该模型request.params.voice的default或按次传--voice(命令)/voice(工具参数)即可。两者互不影响,协议端不支持这些 action 时改走generate_audio。 - 中间件捕获消息默认转发到协议端;若希望 AI 内部执行且不打扰真实 QQ,需在工具参数中设置
capture.forward=false。海豹核心连接/core,协议端由中间件出站主动连接(WebUI「⚙ 配置」填地址,重启生效),插件通过/pluginWebSocket 调用。纯 OB11 没有 trace_id,同一群并发动作可能返回ambiguous=true;中间件已按群/私聊 lane 串行化,并以空闲窗口、最大消息数和超时结束收集。 - 简单配置(开关/数值/单行字符串/纯字符串数组/「后端配置」JSON)修改后自动生效(缓存最多 1 分钟,无需重载 JS);复杂配置(模型「模型配置」、触发/忽略正则、评分触发、「角色设定」、「MCP服务器」、MCP 总开关、「知识库配置」、本地资源路径、音乐服务)启动时解析一次、常驻内存,修改后需重载 JS 才生效(其中「MCP服务器」「知识库配置」两类也可用
.ai mcp refresh/.ai kb refresh立即生效);「技能列表」修改后下一条消息自动生效(也可用.ai skill refresh强制重解析)。 - 嵌入输出维度取自该模型
request.body的dimensions(默认 1024,须与后端一致,如 text-embedding-v4 为 1024);未配置维度时记忆检索自动降级为关键词/分数检索、知识库检索自动降级为关键词匹配(嵌入仅作候选重排,加载知识库本身不请求嵌入)。 - 流式输出需要自建或使用公共后端,并在「后端 → 后端配置」的
backends.stream-output配置 URL;在 chat 用途组groups[].body里设stream = true的模型才会走流式(anthropic-messages协议不支持,会自动回退非流式)。 - 模型类型判定顺序:
models[].kind显式声明 → 命名终值(reranker/生图/语音/嵌入白名单) → 接口自报能力位(vision/embed) → 兜底纯文本。⚠️ 视觉不在自动推断之列:网关自命名模型(如my-embed-1)与视觉模型(如inhouse-vl、gpt-4o、gemini)都要在「模型配置」里显式写kind(取text/vision/embed/gen/tts),否则会被当纯文本模型、甚至被选为默认对话模型。非 OpenAI 兼容的语音/生图服务(如 Google Cloud TTS)没有/models列表,需把模型写死在models里声明kind才能接入。 请求超时时限同时约束模型请求与工具调用,过小会导致长回复/慢工具超时。语音/图片生成不受此限:它们是异步任务,提交后立即返回,HTTP 超时由该模型request.request.timeout(或连接级request.timeout)控制,因此慢出图(SD 等)不会被工具层 180s 截断。- 4.25.0:AI 调
generate_audio/text_to_image时先等一段同步窗口(「工具 → 媒体生成同步等待秒数」,默认 5 秒,0=关闭)——窗口内出结果就把结果当场返回给 AI(图片给[img:图片ID]、语音给 record 段发送指引),不再走异步通知;超时才返回任务号,生成完由通知链路唤醒投递。命令.ai tts/.ai tti不走该窗口(始终由插件直接投递)。若把该值调大,AI 的这轮响应会相应变慢,但省掉一次唤醒与模型调用;调成 0 则一律走异步。 - 4.25.0 媒体送达的三段式(改动该链路时必须保持):工具来源的媒体结果必须满足"要么已呈现给 AI、要么仍在
noticeQueue待呈现",不得两头落空。三段缺一不可:① 入队(带taskId,notified只在真正被消费时回置);② 会话忙则 defer,不假定"本轮 flushNotices 一定会注入";③Agent.run()/runStream()的finally里resumePending()之后调用MediaTaskManager.wakePendingNotices()补触发。详见 03-核心模块详解 的硬约束说明。 - 4.26.0 上下文成本治理的配置与行为变化:待机群(会话待机或全局待机)的普通消息入库改为冷态零 LLM(只追加 + 纯代码截断,不再每条消息压一次);归档 / 窗口移出改为只写一次的分片(
read_raw kind=user id=arc:序号/id=raw:序号回查),.ai forget --all可清空;记忆结算改为按「结算水位 + 空闲 / 转冷」触发。新增.ai ctx/.ai ctxset/.ai tk use,以及「上下文」页的冷态窗口字数、冷态单条截断字数、归档保留原文、窗口上限字数、结算水位 / 最小门槛 / 空闲门槛 / 单次结算调用上限、原文分片保留天数 / 上限、待机入库配额等配置。注意:「单次结算调用上限」限制的是分片数,不是 LLM 调用数;冷态阈值改为连续 10 分钟无相关活动才转冷。 - 4.26.0 联网搜索:新增「联网 → 联网配置」(v1 JSON,可接 10 家 provider + generic,留空 = 内置 SearXNG,开箱可用);旧「后端 → 联网搜索」配置项已删除、不再读取;
web_search参数与新增的web_fetch(正文 Markdown、start_index续读)见 README「联网」章。坏 JSON / 校验失败只记日志、本次不生效(不抛异常、也不回退默认);修改后需重载 JS 生效。
常见问题排查¶
不回复 / 触发不生效¶
按以下顺序检查:
- 日志级别设为"调试",观察触发日志(
trigger reply: ...)与请求日志。 - 检查"触发正则表达式"与"触发需要满足的条件"(豹语表达式)是否命中;触发条件命中为 1 才触发。
- 检查"触发次数上限/补充间隔"(令牌桶),桶为空会跳过回复(
bucket empty, skip reply)。 - 检查消息是否被"忽略正则"或"忽略消息豹语条件"拦下(日志有
忽略字样)。 - 检查消息是否含白名单之外的 CQ 码类型(
CQ_TYPES_ALLOW之外的 type 不处理)。 - 确认模型配置可访问:url/API Key/模型名正确、余额充足、支持工具调用。
回复中的字面量 \n 没有换行¶
AI 回复中的字面量 \n 或 \r\n 会在发送前转换为真实换行;如果需要发送多条独立消息,使用 \f 作为分隔符。代码块或示例中的 \n 也按此规则转换。
工具调用失败¶
- 查看日志中的
调用函数失败:原因:未注册/未经许可/参数缺失/类型不符/会话类型不符/超时等。 - 用
.ai tool查看开关状态,.ai tool help <函数名>查看参数,.ai tool call <函数名> --参数=值手动试用。 - 工具在"禁止调用的函数"列表中时无法开启;新会话中"默认关闭的函数"需要
.ai tool on <函数名>手动开启。
记忆/知识库检索不到¶
- 记忆检索:确认已有嵌入模型可用(「模型配置」里有
kind: "embed"的模型,且.ai model text-embedding已绑定或存在可用默认),该模型request.body.dimensions与后端输出一致(默认 1024),且"启用长期记忆"开关打开;检索有内置相似度下限(0.8)过滤,条目太旧(衰减)或相似度过低不会展示。模型名无法被自动识别为嵌入时(网关自命名),在「模型配置」里显式写"kind": "embed"。 - 知识库:"启用知识库记忆"开关打开后,内容来自「知识库配置」配置(Markdown 正文,
#条目标题、##/###小节,超长自动分块);不按角色加载,修改配置后需.ai kb refresh或重载 JS 生效,可让 AI 通过 knowledge_search / knowledge_read 工具检索验证;嵌入未配置时自动降级为关键词匹配,不影响基本检索。
语音/图片生成不生效¶
按以下顺序检查(4.24.0 起为内置能力,与对话模型同一套连接):
.ai model tts/.ai model image-generation查看该用途候选与当前模型:为空说明「模型配置」里没有kind为tts/gen的模型(命名白名单未命中时显式写kind)。- 先看 vendor(由
baseURL的 host 自动识别)是否在内置预设覆盖范围内(README「媒体用途」表):在范围内就不用写任何请求覆盖,只填连接 +kind即可;不在范围内(例如文心一格生图、自建服务)才需要在request.body里自行描述结构。预设里没有的 vendor 一律按 OpenAI 兼容处理。 - 百度短文本语音合成已内置(
baseURL为tsn.baidu.com时识别为baidu):它是access_token鉴权,而连接只有apiKey一个密钥字段,故约定apiKey填"APIKey:SecretKey"(冒号分隔,密钥含冒号用\:转义),插件按连接换取并缓存access_token(30 天、带 5 分钟提前量、同一连接不重复换);直接填access_token也支持。该服务无/models列表,models里要写死一个模型并声明kind: "tts"。 - 百度返回失败时仍是 HTTP 200,靠
Content-Type(成功以audio开头、失败为application/json)区分,业务错误在err_msg(err_no取值不固定,故只取err_msg),预设已配error_message_path = "err_msg",故失败会显示百度的原始原因而不是"未找到音频数据"。 - 语音报"当前运行时无法读取二进制响应体":该接口是「整个响应体即音频流」(OpenAI 兼容
/audio/speech、百度、阿里 CosyVoice 等),而海豹的 JS 引擎(goja)的 fetch 只有text()/json(),没有arrayBuffer(),且text()会按 UTF-8 解码把音频字节替换成 U+FFFD 永久损坏——所以必须由后端代读。启动 aiplugin4-backends 的media-fetch后端(默认端口 46679),并在「后端 → 后端配置」的backends.media-fetch确认地址即可。配置了 media-fetch 时插件直接走后端、不再先直连一次(同一上游只被打一次);上游 4xx 非 JSON 的错误原文由后端bodyText带回(需 media-fetch ≥ 1.0.1)。返回 JSON 的接口不受影响(MiniMax 走data_path=hex、Google Cloud 走audioContent、绝大多数生图接口),不需要该后端。 - 检查
request.body的占位符是否写全:{text}/{prompt}是正文,{model}是模型名;request.response.data_path指错会报"响应中未找到音频/图片数据",整响应即音频时留空data_path并设data_type = "base64"。 - 用
.ai media看队列状态与失败原因;.ai tts 测试/.ai tti 测试可绕过 AI 直接验证。 - 参数不生效时检查该参数是否真的出现在最终模板(预设或你的
request.body)的{占位符}里——只有模板里出现的键才接受覆写,其余一律忽略(这是刻意的:让模型不支持的参数不会污染请求)。 - 地址不对时优先不用改请求覆盖:
baseURL填根地址即可(如https://api.siliconflow.cn/v1);确实要换地址再写request.request.url(绝对地址)或request.request.urlSuffix(urlSuffix = ""表示baseURL原样即接口地址)。
MCP 服务器 image-host 注册失败 unauthorized¶
image-host 的 /mcp 需要访问 token;日志里的「服务器未初始化:unauthorized」实际是 401 鉴权失败(不是没启动)。处理:
- 启动器 WebUI → image-host 卡片「⚙ 配置」→ 设置 Token(与插件的图床 token 一致),保存后重启 image-host(token 是启动时注入的);
- 重新点「📋 复制 MCP 配置」,确认 image-host 条目带
headers.Authorization: Bearer …,再粘到「MCP → MCP服务器」保存; .ai mcp refresh(或重载 JS);- 不需要 image-host 的 MCP 工具(搜索/重命名/打标签/收图)时,直接从 MCP 配置里删掉
image-host条目即可——「后端配置」里的图床不受影响。
后端 401 / token 不一致¶
任意后端都可以在启动器卡片「⚙ 配置」里设访问 token(AIPLUGIN4_BACKEND_TOKEN)。设置后:
- 重启该后端(token 是启动时注入的);
- 重新点「📋 复制后端配置」,确认
backends.<后端名>.token与卡片里的 token 一致,整段粘贴到「后端配置」; - 等约 60 秒(或重载 JS)后重试。
日志/回复里的 401 会提示去哪个 backends.<名>.token 核对;流式输出、图片转base64、媒体中转、用量图表、图床、核心桥都适用。复制的 JSON 含明文 token,不要公开分享。
token 模式下「.ai token chart」裂图/失败¶
图表图片由 QQ 协议端拉取,带不上 Authorization。插件改为让后端直接返回 base64(seal.base64ToImage 落本地图)。要求 usage-chart ≥ 1.0.1 且插件 ≥ 4.27.0;旧版后端在 token 模式下会返回 401 的临时图 URL(表现为裂图)。升级后端后重新「复制后端配置」即可。
AI 调工具生成的语音/图片"没动静"(没有自动发出来)¶
AI 侧(generate_audio / text_to_image)完成后的行为是确定的:① 把完成通知写入会话队列(这步先做且不会因后续失败而丢);② 直接触发一轮对话投递(不判断忙/待机/全局待机——这是用户请求的结果送达,不是主动搭话)。命令侧(.ai tts / .ai tti)由插件直接投递,不经过 AI。日志按模块标签 [media] 过滤即可看到每一步:
媒体任务 #N 完成,触发会话 <sid> 起一轮对话→ 已起新对话,AI 该在下一轮发出;媒体任务 #N 直接投递失败:...→ 协议端离线/端点异常,结果仍在任务队列(可用.ai media看),下一轮对话会呈现;媒体任务 #N 通知/触发对话失败(结果仍在任务队列,可用 media_tasks 查看):...→ 同上,run侧出错也不丢结果。- 该触发免触发令牌桶(桶空也送达),所以"触发次数上限"导致的不发言已排除;若仍不发,查
chat是否被.ai stop中止或模型侧报错。 - 若 AI 收到了通知却发不出去:
[audio:xxx]只认「资源」页配置的本地语音资源名,生成结果的路径/URL 要用call_ob11_api的 record 段;图片用[img:图片ID],且该图片必须已注册进Image.imageMap(任务落地时会自动Image.save)。
图片识别异常¶
- 确认图片 URL 可在浏览器访问(过期或 QQ 图床 bug 时更换协议端版本)。
- 模型不支持 QQ 图床时,把"识别图片时将url转换为base64"设为"总是"或"自动"。
- 图片转文字依赖视觉模型:先在「模型配置」里给视觉模型显式写
"kind": "vision"(⚠️ 视觉不再按模型名自动识别,glm-4v/gemini/pixtral这类名字也不会被自动认成视觉模型),再用.ai model image-understanding <模型>绑定。 - 图片直链失效不会再卡死会话:QQ 直链的 rkey 过期(图床返回
download url has expired,后端与模型都拿不到图)时,插件会把这些图片降级为[img:ID]文本标签,不再产生 image_url,模型也就不会因"下载图片失败"整轮 400;日志里出现图片xxx的URL已确认不可用或[model-error] 模型无法下载上下文图片即为此场景,属于正常兜底。 - 若历史上下文里已留下坏图片(如旧版本遗留)导致该会话一直不回复,执行
.ai forget清上下文后即可恢复(新版会自动把这类图片降级,不再需要手工清理)。
HTTP 请求出错¶
- 日志中的
请求失败! 状态码: xxx对照 HTTP 错误码与大模型文档排查;常见原因:url 填错、API Key 错、模型名错、不支持工具调用、余额不足、请求频繁。
待机群相关(上下文成本治理)¶
背景:待机群里所有群聊消息会累积进同一条 user 消息块;旧版本每来一条消息都会重判"整块是否超过「消息压缩阈值」",超过就调一次压缩智能体——群里越热闹花钱越多,哪怕机器人一句话都没说。现在改为冷态入库零 LLM。
-
为什么待机群不再"每条消息都压缩"? 待机(会话待机或全局待机)且无在途对话时,消息只追加 + 纯代码中段截断,
compression调用为 0(用.ai ctx的"本会话调用:压缩"确认)。压缩只发生在热态,且遵守三条规则:同一批只尝试一次(含失败)、只压尾部连续未压缩段、结果必须比原文更短才采纳。若你确实想要压缩,用@让会话转热即可。 -
怎么看花了多少钱?
.ai ctx(权限 U)显示:当前冷/热状态、预算与冷态窗口、上下文条数与估算 token、归档分片数、本会话compression/summarization/judge调用次数、全局用途用量与缓存命中。全局分模型用量仍看.ai tk。 -
归档分片怎么清? 归档出来的原文写进
session_<会话ID>:arc:<三位序号>分片(只写一次、永不重写)。.ai forget只清上下文,不碰分片(分片是留档,供read_raw回查)。分片数量上限默认 200,超出时最旧的分片会被置空。按会话清理分片用.ai forget --all(清空本会话 arc/raw 分片并重置结算水位,不影响已沉淀的长期记忆);分片 TTL 见「原文分片保留天数」。 -
「归档保留原文」开关怎么用? 开(默认):归档时把原文写进分片(
read_raw kind=user id=arc:序号可回查)并调总结智能体沉淀记忆。关:归档不留原文、不写分片(省存储),但仍然会调总结智能体沉淀记忆——历史照样进长期记忆,只是之后无法回查原文。注意:关掉它不会省钱(总结调用照旧),只省存储。 -
待机群"记不住"更早的内容怎么调? 冷态下模型默认只看到最近「冷态窗口字数」(默认 5000 字)的内容,更早内容折叠成纯代码摘要(零 LLM)。调大该值可让模型默认看到更多;设 0 = 不折叠(注意会直接抬高每轮 token)。消息里出现"它/这个/刚才/继续"这类指代词时会自动升档多装一段,"总结/回顾"这类明确要求回顾的则不折叠。更早的原文可让 AI 用
read_raw/grep_raw回查。
关键行为不变量(实现约束)¶
以下是从历史设计/方案文档(子代理、模型配置 v5、上下文成本治理、联网搜索)中提炼的、仍属当前行为的硬约束;完整历史推导见 Git 历史(v4.26.0 及更早提交)。
子代理(DSH 对齐)¶
- 委派深度:主会话按「最大委派深度」(默认 3;0=禁止委派),child 不允许再委派;超限返回错误工具结果,但委派工具保持可见。注意:实际只有 1 层——depth>0 时
effectiveMaxDepth恒返回 0,该配置是「允许/禁止主会话委派」的开关,默认 3 不代表可以委派 3 层。 - 强制非流式:child 激活统一走
sendChatRequest(..., {stream:false}),即使 chat 用途groups[].body.stream=true也只取一次性 JSON;不要给 headless child 接流式。 - 工具面:
Tool.handleToolCall的child.toolState是唯一执行闸门,不得绕过(含call_tool内层、提示词工程路径);spawn 原生模式只向 child 注入list_tools/search_tools/call_tool,fork 继承父已完成轮次;「子代理禁止调用工具」只收窄、deny 优先。 - 结算与通知:
settle只在一次激活结束时触发一次;前台结果直接回父(工具结果),continuable 入父noticeQueue,one-shot 后台 job 不投 notice(只能用job_output取);父运行中每轮flushNotices注入;父空闲且会话/全局均非待机才唤醒(待机不唤醒,notice 留存),令牌桶空则本次不跑;未 flush 的队列重载丢弃,运行中子代理记为stopped(可续状态以存储记录为准)。 - 停止原因→结果映射固定:completed 回 output;aborted/error/max-tokens/refusal/timeout 抛错并附
Diagnostic:与Partial output:;前台Promise.allSettled后必执行dispose(),不半成功。 - 断点续跑:每轮请求前落 checkpoint(tool 配对 + reasoning 无损编解码),续跑原样回放已存 messages 再追加新任务;
send_message只对 idle/interrupted 续跑,done/stopped 回「不再唤醒」。 - 中断/清理:
.ai stop递归中止全部子代理(只停当前轮、可续);重载后running对账为「已中断·可续」;每父会话记录保留 100 条(自动清理最旧已结束记录),.ai subagent clean手动清理;child 请求不得二次 acquirerequestLimiter(会自锁)。 - 空回复:headless 轮最终文本为空则不发送、不入上下文,仅记日志。
- 子代理请求照常计入
usageMap用量(按用途),与主会话同口径。
模型配置 v5¶
- 解析期一次完成全部校验,任一条失败整份拒绝(不做部分生效);错误带
$.providers[0].models[1].name式路径、一次报全部;version固定 5。 - 校验要点:模型
id跨 provider 全局唯一;purposes引用必须存在且kind与用途匹配(isEligible精确:embed 只进 text-embedding、vision 只进 image-understanding、tts 只进 tts、对话类排除 embed/gen/tts,缺该类型返回 null、不退而求其次);apiKey非空且字符 ∈[\x21-\x7E](HTTP 头可携带集);baseURL必填且http(s)://;api∈ 3 值、kind∈ 5 值、bodyMode∈ 2 值;groups[].purposes元素须为 8 用途之一。 - 协议按
api分派(openai-completions/anthropic-messages),不再按厂商名猜;openai-responses只接受枚举、运行时明确报错(不静默走错协议)。provider只用于baseURL的 host→vendor 反查(媒体预设/余额),一般不用填。百度双密钥是唯一保留的厂商特例,靠request.auth_token = "baidu-token"触发,而不是provider === 'baidu'。 bodyMode默认值:媒体用途(tts / image-generation)默认replace(整体替换预设结构),写merge才逐键合并;非媒体的groups[].body才默认逐键合并。深合并只在两侧都是普通对象时进行(否则会把字符串占位符"{text}"展开成{0:'{',1:'t'…})。- 模型类型判定:显式
kind最高优先;命名白名单只保留 rerank/生图/语音/嵌入终值;视觉不再按名字推断(gpt-4o/gemini/*vision一律归text,要识图必须写"kind": "vision");接口自报能力位只作正向证据,不产出text。 - 列表与用途:
models留空/缺省 = 启动拉取/models;写了 = 钉住清单、跳过拉取;单连接失败只降级该连接;.ai model pull强制重拉。用途解析顺序:运行时覆盖(storage)→ 配置purposes→ 该用途候选第一个。purposes未列出的同类型模型仍是候选但排在列出项之后;失败回退pickNextModel只往后走(pool.slice(idx+1),末尾返回 null,不绕回开头,避免 A→B→A 来回跳)。层失效时保留现场、记日志并回退下一层。 - vendor 识别是 4 步且回落不报错:显式
provider> host 反查 > 路径尾段(只收"恰好一个 vendor 拥有"的路径,跳过通用预设与含{}的路径)>''(按 OpenAI 兼容)。共用 host 的 alibaba/dashscope 必须显式写;识别失败时媒体请求会静默套通用结构,原生厂商会发错 body 且不报错。 - 媒体细节:异步轮询兜底 15s × 20 次,成功值
SUCCEEDED/SUCCESS/COMPLETED;numOrDefault只接受 number/数字字符串(Number(null)===0会让poll_interval=0打爆接口)。识图/嵌入没有 vendor 预设机制(MediaPresetUse仅 tts/image-generation);media_presets保留内置;媒体用途跳过 token 用量与校准。 - 不做 v4 兼容/迁移:旧的两个 TOML 框已注销且不读取;海豹无配置写入 API,只能用配置生成器重建(升级时日志会提示)。
上下文成本治理¶
- 状态是算出来的、不持久化:非待机恒
HOT;在途任务(running||starting||activeRuns>0)冻结衰减;COLD_AFTER_MS=10 分钟、COOL_DOWN_MS=1 分钟、HOT_HOLD_MS=9 分钟;时钟回拨用max(0, now-lastRelevantAt)防负值。Session.state已被角色状态占用,不要用state作新字段名。 - 「相关活动」只认
session.chat()(@、触发、judge SPEAK、媒体/子代理送达、定时任务);普通消息入库与系统事件不算。 - 冷态入库:零 LLM;单条超「冷态单条截断字数」用
truncateMiddle留头 2/3 + 尾 1/3(保住末尾[img:]/[record:]不被切断),原文进rawText+ 「原文已截断」标记;冷态不做合并压缩。 - 压缩三规则(仅热态):同批只尝试一次(含失败)、只压尾部连续未压缩段、结果必须比原文短才采纳。
addUserMessage用每会话 promise 链串行化(运行时状态放模块级 WeakMap),防止并发 await 期间互相覆盖 contentItems。 - 渲染折叠:预算 = 冷态窗口字数;L0 = 窗口 60% + 折叠摘要 40%(摘要下限 200 字)、L1 = L0 + 20000、L2 = 不折叠;档位只看本轮最新一条真实用户发言(跳过系统条目,防「继续/总结」系统文案带偏);最新一条/最新原子组必留(整包可能略超预算);非待机不折叠(
renderFoldBudget=0);折叠只影响渲染、不改持久化消息。 - 归档/窗口移出分片:
session_<id>:arc:<seq>(token 超限归档)与raw:<seq>(窗口上限移出)都是只写一次、永不重写;先落分片再移除;写入/读异常返回failed时不移除来源条目(宁留重试不丢原文),exists也推进序号;移出后必须立即save()(延迟写会导致崩溃后消息复活 + 重复留档);分片超上限要清「所有超限序号」(只清一个会让旧分片永久滞留),按「原文分片上限」(默认 200)与「原文分片保留天数」(默认 30,惰性)回收。归档连续失败ARCHIVE_FAIL_LIMIT=3次后降级丢弃最早轮次(不写记忆)以恢复自愈。 - 工具配对是硬不变量:窗口移出/折叠/归档不得拆散
assistant(tool_calls)与其tool结果(否则请求 400);实现是粗选窗口 +alignToToolPairing配对修复,连续 / 非连续 / 前导悬空三种形态一致成立。上下文内字段是 camelCasetoolCallId,tool_call_id只出现在出站请求体,配对检查统一走toolResultCallId()。提示词工程模式不得做孤儿剔除(PE 把 tool 转成 user 文本,无配对 tool 合法,删掉会丢工具结果)。 - 结算三条件:积压 ≥「结算水位」(默认 3000)且(空闲 ≥「结算空闲门槛」600s 或 会话转冷)且上下文 ≥「结算最小门槛」(20000 tokens);积压按全部未结算分片计;
settledSeq水位幂等,失败按 5/15/60 分钟退避(仅运行时);转冷时也会结算一次;「单次结算调用上限」(默认 2)限制的是分片数,不是 LLM 调用数(0=关闭自动结算)。 - judge 体积上界:
renderWindowedHistory复用主对话折叠规则 + head 硬截断…(更早内容已省略),预算为 0 时退冷态窗口字数 → 内置 5000;judge 是冷态零 LLM 的常规例外(gate ≤20 次/小时)。 - 保存节流:只有高频入库路径
save(true)(5 秒 / 20 条合并);回复、命令、归档、挂起队列始终立即写。节流窗口内进程被杀最多丢最近 ≤5 秒 / ≤20 条冷态入库。 - 「归档保留原文」关 = 只省存储、不省 LLM(仍 summarize);
.ai forget --all只清本会话 arc/raw 分片与水位,不影响已沉淀长期记忆(memv2:*);.ai ctxset只影响本会话。 npm run sim(15 项)/sim:matrix(76 项,90 组顺序×预算)/fuzz(确定性 PRNG,可 seed 回放)是代码路径级模拟:能抓回归,但不能替代真机(覆盖不到真实厂商 API 请求体校验、goja storage 行为、QQ 消息形态、多会话并发时序)。
联网搜索¶
- 路由与回退精确矩阵:
providers顺序即尝试顺序(routes.search/fetch可覆盖);跳过enabled:false、需要 key 但为空/占位符(如sk-xxxx)的 provider;联网搜索失败回退=false只试第一个(排障用)。401/402/403 直接换下一家;429/5xx/网络/超时在本家重试 1 次(固定 300ms,不读Retry-After)再换下一家;其余 4xx(含 400/404/422)都不换家,直接报错(参数/配置错,换家只会掩盖问题);全部失败返回逐家原因汇总。 - 空配置 = 内置 SearXNG(
https://searxng.fishwhite.top),开箱可用;「联网配置」为 v1 JSON,version≠1整体拒绝,坏 JSON / 校验失败只记日志、本次不生效(不回退默认),修改后需重载 JS。 - generic 模板:占位符
{query}{count}{need_content}{page}{offset}{site}{time_range}{ori_question}{apiKey}{baseURL};整值占位符保留原始类型(数字/布尔)、混合串拼接、为空则删键;results_path走getByPath(a.b.0.c);fetch 支持response_type: json|text。模板在src/web/template.ts自实现,不得反向依赖model/media_template。 web_search:query必填(兼容旧q)/count/offset/detail/allowed_domains/blocked_domains/site/time_range/page;参数未指定时使用「联网」页的联网搜索返回条数/联网搜索详细程度;detail=content必须逐条按联网搜索正文截断(默认 5000)截断(summary 每条 600~800 字,10 条可到 8k 字符),整体仍走「工具响应截断字数」兜底。web_fetch:实参url/max_chars/start_index(续读),默认转 Markdown;截断附「可传 start_index=N 继续读取」,到末尾给「没有更多内容」。(原方案里的raw:true返回原始 HTML 未实现。)- 两条禁改约定:工具名必须保持
web_search(session.toolState按工具名持久化,改名会让所有会话开关失效);provider不进模型可见 schema(模型爱乱填,强制指定走内部调用)。 - 厂商托管搜索(server-side)属模型请求参数、不进联网
providers,与web_search同开有重复计费风险(应二选一);结果缓存/去重/二次 LLM 总结、WebUI 管理页、.ai web命令、QPSmin_interval、自建抓取 SSRF/robots.txt 加固均未实现(见 10-TODO)。
维护建议¶
- README 与本知识库互补:README 面向普通用户(安装/配置/命令),
docs/面向开发者(架构/源码/扩展)。代码结构变化时优先同步docs/。 - 发版前统一
VERSION、header.txt @version、package.json/package-lock.json的version与src/changelog/<版本>.ts最新条目(sealpack/info.toml由打包脚本自动同步);npm run version:check可一键校验全部来源。 - 架构约定:依赖只向下(L0→L4)、宿主能力走
getHost()、双向协作走src/core/ports/、配置解析/应用分离;改动后跑npm run deps:check,新增目录先补LAYER_RULES。