跳转至

03 - 核心模块详解

本章按目录逐模块说明职责与关键类型。代码路径均相对仓库根目录 src/。

入口与装配(src/index.ts)

  • 注册 Handlebars Helper:index(从 1 开始)、json_stringify、time。
  • 依次初始化:Config.registerConfig()、initAgents()、checkUpdate()、Tool.registerTool()、TimerManager.init()、knowledgeService.init()、registerCmd()、PrivilegeManager.reviveCmdPriv()。
  • 写入存储版本标记 storage_version(当前 STORAGE_VERSION = 2,结构变更时递增)。
  • 绑定九个 SealDice 事件:onPoke、onNotCommandReceived、onCommandReceived、onMessageSend、onGroupMemberJoined、onGroupLeave、onMessageDeleted、onBecomeFriend、onGroupJoined,消息与戳一戳委托给 MessagePipeline,原生通知事件统一交给 MessagePipeline.handleNativeNoticeEvent(与 ob11 依赖通知共用白名单/去重,避免双录)。

消息管线(src/pipeline.ts)

MessagePipeline 是唯一的事件处理入口:

  • handleNonCommand(ctx, msg):执行忽略/触发判定,见 02-架构设计。
  • handleCommand(ctx, msg, cmdArgs):处理收到的指令消息;可选录入上下文。扩展指令工具所需 CmdArgs 不从这里缓存,而是在 run_ext_command 调用现场构造。
  • handleBotMessage(ctx, msg):转发给工具监听;可选录入上下文。
  • 通知/请求事件(handleOb11NoticeEvent/handleOb11RequestEvent/handleNativeNoticeEvent):转文本提示词,仅当会话待机或全局待机开启时录入上下文(仅背景、不触发 AI),原生与 ob11 依赖共用白名单与 3 秒事件级去重避免双录——去重 key 含整包内容指纹(连续但不同的事件不会互吞),重复/双路径到达时"后到覆盖先到"就地替换已入库条目;机器人自身入群/退群由原生回调覆盖、依赖侧跳过。事件原始数据随条目挂载(不渲染给模型、不设条数/长度上限,随消息清理/归档失效),AI 经 read_raw kind=event 读取完整 JSON(单次返回条数上限 20、总字符有硬上限);事件提示词文本超长仅纯代码 head 截断(2000 字)兜底(原 get_event_detail 工具已删除)。
  • 会话统一通过 getSession(sid) 获取;sid 私聊为用户 ID、群聊为群 ID。
  • CQ 码白名单 CQ_TYPES_ALLOW = ["at","image","reply","face","poke"],含白名单之外 CQ 类型的消息不处理。

配置(src/config/)

config.ts

  • 通过 seal.ext.find/new/register 创建扩展实例 ext(插件名 aiplugin4)。
  • configMap 集中登记 18 个配置分组(按注册顺序,「错误处理」固定最后):base、model、role、context、received、trigger、reply、tool、mcp、skills、subagent、memory、knowledgeBase、image、backend、event、resource、error。
  • 每个分组由 register()(向 SealDice 注册配置项)与 get()(读取并返回类型化对象)构成。
  • 读取带缓存:CONFIG_CACHE_TTL = 60000 毫秒,Object.defineProperty 把 Config.<分组> 变为缓存 getter;ModelConfig.get() 在注册时预热:同步解析「模型配置」(v5 JSON,校验失败整份拒绝)→ 写死 models 的连接即时生效,models 留空的连接异步拉取模型列表(首条真实消息前 await Model.ensureLoaded())。
  • getRegexConfig():把模板配置的多行正则拼接为单个 RegExp,失败时返回永不匹配的 /(?!)/。

static_config/(静态常量)

  • VERSION(当前 4.27.0)、AUTHOR、NAME、STORAGE_VERSION(当前 2)、CONFIG_CACHE_TTL、CQ_TYPES_ALLOW。
  • PRIVILEGE_LEVEL_MAP:master=100 / whitelist=70 / owner=60 / admin=50 / inviter=40 / user=0 / blacklist=-30。
  • HELP_MAP、ALIAS_MAP(命令别名表)、FACE_MAP(QQ 表情编号 → 名称)。
  • PROVIDER_MAP:10 家厂商的默认 baseURL(仅用于配置生成器与反查参考;v5 的 baseURL 是必填项、不再兜默认值)。
  • DEFAULT_CHAT_MODEL_BODY / DEFAULT_MULTIMODAL_MODEL_BODY / DEFAULT_EMBEDDING_MODEL_BODY:默认请求体兜底(用途组 groups[].body 未覆盖时生效)。
  • 按类别拆分为 meta.ts(版本/作者/插件名/缓存 TTL/存储版本)、cq.ts、permission.ts、help.ts、alias.ts、face.ts、providers.ts(厂商默认地址)、model_defaults.ts、memory.ts(记忆检索内置常量),由 index.ts 统一 re-export,历史导入路径 ../config/static_config 保持不变。

configs/(19 个分组)

文件 分组 主要配置项
base.ts 基础 日志级别/简短打印/记录消息内容、请求超时、请求并发/队列限制、海豹核心路径、全局待机
error.ts 错误处理 启用报错自动处理、上下文超长自动归档重试、余额不足自动切换模型、自动切换触发错误(默认 balance)、自动切换策略(跨厂商优先/配置顺序)、切换后发送通知
model.ts 模型 v5:v5 JSON 模型配置(src/config/v5/)——解析 providers(api/baseURL/apiKey/provider 可省/models/enabled/request)、purposes(用途 → 模型 id 有序数组)、groups(用途组 body),解析后 Model.bootstrap() 建连接状态机并异步拉取模型列表,同步全局分用途覆盖(modelPurposeOverrides 存储键)。校验失败整份拒绝(不做部分生效),错误带 $.providers[0].models[1].name 式路径;没有 v4 兼容/迁移(旧两项已注销,见 docs/13)
role.ts 角色设定 角色设定(v1:roles[] 的 name/prompt;名称 >20 截断,$gSYSPROMPT/.ai role 切换,重载 JS 生效)
context.ts 上下文 预设上下文(role 按 user/assistant 轮流)、对话保存轮数、上下文最大token(默认 1000000;0 或负数 = 跟随报错探测到的模型真实窗口 × 0.8,未探测到回退 100 万,显式正数优先)、插入 system message 间隔轮数、消息压缩阈值(压缩前原文保留,read_raw kind=user 可查)、冷态窗口字数、冷态单条截断字数、归档保留原文、窗口上限字数、结算水位 / 结算最小门槛 / 结算空闲门槛 / 单次结算调用上限(分片数)、原文分片保留天数 / 上限、待机入库配额;归档与窗口移出分片由 src/context/archive_store.ts 管理
received.ts 消息接收 接收图片/指令/骰子消息、忽略私聊、忽略豹语条件、忽略正则
event.ts 事件接收 接收依赖通知事件、通知事件白名单(notify 子类型单独匹配,含入群/好友申请)
trigger.ts 消息触发 默认计数器/计时器/概率/活跃时间、评分触发配置、触发正则、触发条件、令牌桶
image.ts 图片 图片全局识别豹语条件、识别图片时将 URL 转 base64(永不/自动/总是)、图片识别展示截断字数(默认5000,0=不截断不保留)
tool.ts 工具 函数调用开关、提示词工程、拉黑前需骰主确认、无工具调用时续跑提示、工具方向提示、连续调用次数(默认0=不限制)、工具响应截断字数(默认10000)、禁止/默认关闭函数与 OB11 action、可调用指令白名单、是否允许调用所有指令、指令前缀、音乐服务配置、音色
mcp.ts MCP 是否启用MCP、MCP服务器(标准 mcpServers 对象)、MCP会话空闲回收分钟、MCP每服务器最大会话数
skills.ts 技能 技能列表(v1:skills[] 的 name/description/platform/content;缺失的默认技能自动补入)
subagent.ts 子代理 是否启用子代理(默认开)、最大委派深度(默认 3,0=禁止委派)、子代理禁止调用工具(留空=继承主会话全部已开启工具)
memory.ts 记忆 长期/观察记忆开关、记忆LLM抽取/重排/观察合成/推理、自动维护固定心智模型、新近度权重/半衰期、长期记忆条数上限(默认100,0=不限制)、巩固后自动刷新心智模型、刷新最小间隔、心智模型刷新模式(full/delta)、刷新排除其它心智模型、定时刷新间隔
knowledge_base.ts 知识库 知识库开关、知识库配置(v1:libraries[] 的 name/description/platform/content,正文为 Markdown)
reply.ts 回复 引用(含戳戳跳过)、最大字数、去首尾空白、换行转义、分段发送延时、禁止复读与最低相似度
resource.ts 资源 本地图片路径、本地语音路径(语音名=路径)、本地文件路径、本地视频路径;供 list_resources / get_resource_path 查询
backend.ts 后端 后端配置(v1 JSON:backends.<后端名>.{url,token};所有后端都支持 token,请求带 Authorization: Bearer <token>,401 时提示重新复制后端配置;空=全部出厂默认;旧 8 个独立键不迁移、不兼容);网页读取与 md/html 渲染(MCP)在「MCP → MCP服务器」
forum.ts 论坛 论坛地址、论坛API Token、论坛签名密钥
web.ts 联网 联网配置(v1 JSON):providers(dialect/baseURL/apiKey/enabled/timeout/headers;generic 另带 search/fetch 模板与取数路径)、routes;留空=内置 SearXNG;10 家 provider + generic;校验失败只记日志、本次不生效。返回条数/详细程度/超时/正文截断/失败回退为同页 5 个独立配置项(不写进 JSON)

注意:本地图片/语音/文件/视频路径由资源分组统一注册;list_resources 查询资源 ID,get_resource_path 查询实际路径/URI;当前会话发送图片优先使用 [img:图片ID]。

prompt 模板(src/prompt/templates.ts)

模板在代码中以 TEMPLATES 常量内置(不再注册为配置项,避免误改导致渲染损坏),读取时 compileTemplate 编译,仅在编译失败时回退空函数;渲染期异常不兜底,由调用方处理。模板清单:

  • system prompt模板
  • 长期记忆prompt模板
  • 观察记忆prompt模板
  • 工具函数prompt模板
  • 图片识别prompt模板
  • 记忆观察prompt模板

模型层(src/model/)

  • v5/(schema.ts / adapt.ts / vendor.ts / default.ts):v5 配置的纯逻辑层(不依赖 seal,便于单测与跨仓库联测)。schema.ts 定义类型 + parseV5Config/validateV5Config(一次报出全部问题,带 JSON 路径);adapt.ts 的 adaptV5 把 v5 翻成下游沿用的 ConnConfigLike[] + ModelRuleTemplate[](per-model request 段 → 一条 models 定向的规则),运行时改动面因此被限制在少数几个字段;vendor.ts 由 baseURL 的 host 反查厂商标识(resolveVendor / vendorByUrlSuffix);default.ts 是纯数据的出厂最小示例(单独成文件以便集成联测直接打包引用)。
  • api_dialect.ts:协议方言判定,把"是不是 anthropic"这类按厂商名猜协议的硬编码集中到一处(resolveApiDialect/isAnthropicMessages/isOpenAiResponses)。v5 按 api 字段分派;api 缺省时退回按 provider 名推断,保证 v4 迁移期连接与既有单测不因缺新字段而行为突变。openai-responses 本期未实现,运行时明确报错而不是静默走错协议。
  • baidu_token.ts:百度 access_token 的获取与缓存——百度接口只认 token 而非 API Key,而连接只有 apiKey 一个字段,故约定填 "APIKey:SecretKey" 自动换取(也接受 | 分隔;密钥含冒号用 \: 转义,2 个以上未转义冒号视为写法非法、整体当令牌)。只存内存不落盘、过期前 5 分钟重新换取、同连接并发请求共享同一次换取(in-flight 去重)、换取失败缓存 60 秒避免反复撞鉴权接口。
  • model.ts:Model 静态注册表(states 连接状态 + entries 模型实例)与全局分用途覆盖 purposeModelOverrides(use → 模型 id;由 .ai model 写入 modelPurposeOverrides 存储键),另有配置默认层 configPurposes(v5 purposes 的有序数组,优先级低于运行时覆盖)。模型实例来自 v5 连接的 models 写死项或启动自动拉取,只存内存、不持久化,按能力标签(catalog.ts)分类进各用途候选;ref 优先用配置里的模型 id(稳定标识,不受连接顺序/重名影响,取代 v4 的 [连接序号]:模型名)。getChatModel(use)/getMultimodalModel(use)/getEmbeddingModel(use) 按「运行时覆盖 > 配置绑定(有序,失效自动往后切) > 该用途首个可用」三层解析,resolveSourceForUse(use) 返回当前生效层(override/config/auto/none)供 .ai model 展示;findModelByRef 按标识反查;listModelsForUse(use) 返回候选列表(.ai model 与余额不足自动切模型共用);ensureLoaded()/pull() 供首条消息等待与 .ai model pull 立即重拉(强制网络);reset() 供测试/热重载。
  • catalog.ts:模型能力分类(text/vision/embed/gen/tts)——判定顺序 models[].kind 显式声明 > 命名终值(reranker/生图/语音/嵌入白名单,如 text-embedding-/bge-/dall-e/cosyvoice/fish-speech) > 接口自报能力位(仅正向证据:vision/embed) > 兜底 text。⚠️ v5 删除了视觉的命名白名单(原 GENERIC_VISION_PATTERNS:pixtral/glm-4v/qwen-vl/*vision/gpt-4o/gpt-5/kimi-k2 等)——那批正则最容易过期(新模型名永远追不上),且猜错会让用户误以为模型已具备视觉能力;视觉现在必须显式声明(接口自报能力位仍作正向证据)。生图白名单的 flux/sd3/seedream/midjourney/imagen/veo 改用词边界匹配并补 qwen-image(聚合平台的厂商前缀名如 black-forest-labs/FLUX.1-schnell 由此可识别)。classifyModel 的 extra 承载 manual 与接口能力位;语音仍必须先于能力位判定——否则含 gpt-4o 的 gpt-4o-mini-tts 会被误判(顺序约束保留以防回归)。
  • request_rules.ts:per-model 请求规则的存储与合并(v5 里由 adapt.ts 从 models[].request 生成)。字段:use(用途组)、models(按模型定向,优先级高于 use)、body、params(AI 可覆写参数元信息)、prompt(extra 自由提示词)、request、response、bodyMode。命中规则分两趟收集:先 use 组(未声明 models 的行)、后 models 定向行(后者覆盖前者)。bodyDefaultsFor(use[, ref]) 返回合并后的请求体模板;promptExtraFor(顺序追加) / responseRuleFor / requestOverridesFor 分别返回提示词、取数规则与调用期覆盖;ruleBodyFor / ruleParamsFor 只取规则部分(供与内置预设合成),ruleBodyModeFor 取命中规则里最后设置的 bodyMode;defaultMediaParamsFor 给媒体用途的代码默认参数(音色 alloy、语速 1.0、尺寸 1024x1024)。媒体用途不叠代码兜底 body:请求结构由「内置预设」提供(见 media_presets.ts),bodyMode 未声明时媒体用途默认 replace(整体替换预设结构),声明 merge 则逐键合并——避免预设里 OpenAI 形状的键混进阿里/Google 等原生结构;mergeInto 只在两侧都是普通对象时深合并(否则会把字符串占位符 "{text}" 展开成 {0:'{',1:'t'…})。
  • media_presets.ts(4.24.0 起):供应商内置请求预设,逐条迁移自旧依赖插件的 tts/MODELS.md 与 tti/MODELS.md。MEDIA_PRESETS 按 tts / image-generation + 小写 vendor 键给出结构(body/request/response/params),同一 vendor 可用 match(正则)按模型名细分——百炼原生 wanx*(异步轮询)与 qwen-image*(同步)、Stability 的 core/ultra、OpenAI 的 gpt-image 与 dall-e 由此区分;未收录的 vendor 落 '' 的通用(OpenAI 兼容)预设。resolveMediaPreset(vendor, use, modelName) 做选择。地址约定:「模型配置」的 baseURL 是根地址,路径由预设的 request.urlSuffix 拼接;joinMediaUrl 会识别"baseURL 已以该路径结尾"(兼容直接粘旧版完整接口地址),urlSuffix = "" 表示 baseURL 原样即接口地址。百度预设另声明 auth_token = "baidu-token" 触发换 token(见 baidu_token.ts)。
  • media_template.ts(4.24.0 起):媒体模板引擎(纯模块,便于单测)。composeMediaTemplate(use, vendor, modelName, modelRef) 做预设 ← per-model 规则的合成(body 按 bodyMode 决定整体替换或逐键合并、request/response/params 逐键覆盖、headers 逐键合并、poll_* 也接受写在 request 的旧写法),resolveMediaUrl 按「规则 url > 规则 urlSuffix > 预设 url > 预设 urlSuffix > 用途默认」定址,templateParamKeys 采集请求体与地址里可覆写的自定义参数;collectPlaceholders(template) 采集模板里出现的自定义占位符(排除内置);renderMediaBody(template, ctx) 渲染请求体——整值占位符保留原始类型、混合串按字符串拼接、既无传参也无 default 时整个键删除、嵌套表键全删时剪枝;normalizeMediaParams(raw, allowed, meta, opts) 做参数归一与校验(语义别名 speed→rate/emotion→instruction、保留字拒绝、类型强转、min/max 与 enum 校验,coerceStringTypes 供命令 kwargs 把 "1.5" 转成数字);buildMediaParamHint / useParamSummary / describeMediaParam 由合成后的模板生成给 AI 的逐模型参数清单(因此不在配置里写任何请求覆盖也会列出预设里的 voice/rate/size)。buildMediaSubmitReply 生成同步/异步两种提交回执文案。
  • media.ts(4.24.0 起):媒体请求编排与响应解析。requestMedia(spec) 复用 requestModel 的超时/重试/ApiError 分类,按 response.data_type 决定取数方式(有 data_path 走 JSON;base64/hex 且无 data_path 表示整个响应体即数据,如 TTS 音频流,并按 content-type 自适应);normalizeMediaData 兼容 url/base64/hex/data-url,extractMediaData 支持 data_path 与各厂商候选路径兜底,extractMediaError 提取业务错误,并在声明了 task_id_path + poll_url 时轮询异步任务。
  • list.ts:连接级「获取可用模型列表」适配——OpenAI 兼容 GET {base}/models(Bearer);anthropic-messages 协议走 x-api-key + /v1/models 并 limit=1000 循环翻页;支持连接 request(list_url/auth_header_name/headers/timeout)覆盖。baseURL 填成完整接口端点时给出可操作提示:拼出的 /models 必然不存在(典型是把百炼原生的 .../multimodal-generation/generation 当 baseURL 填了),此时按 vendorByUrlSuffix 判定(只有唯一 vendor 拥有的路径才命中,自建网关不会误报)并提示"改填基础地址或指定 list_url",而不是发一个注定失败的请求再报含糊 404。对象项额外提取供应商自报能力位(vision/embed:type/model_type/kind/task、architecture.input_modalities、capabilities、supports_vision/vision;未知取值含 anthropic 的 type: "model" 一律忽略),normalizeModelListResult 归一化为「模型名(保序去重)+能力位映射」交上层分类;拉取结果类型为 ModelListResult(string[] 是其等价子集,兼容既有单测桩);不发额外请求(不做 /api/show、逐模型详情等探测);拉取函数可注入便于单测。
  • balance.ts:连接级账户余额查询(.ai balance 用)——deepseek/moonshot/siliconflow 内置余额端点;其余平台支持在连接 request 里配置 balance_url/balance_json_path(+balance_divisor/balance_currency)后查询自定义网关(one-api/new-api 等)。
  • api_error.ts:ApiError 与语义分类——把 OpenAI 兼容/Anthropic/Google/智谱/阿里/SiliconFlow/网关等非 2xx 报错归一化为 balance(余额不足)/context_length(上下文超长)/rate_limit(限速)/overload(过载)/auth/permission/content/parameter/server/unknown 类别,并携带 HTTP 状态码、服务商与响应体原文。
  • provider.ts:requestModel() 统一请求入口:解析协议方言(resolveApiDialect)、超时(TIMEOUT 或规则 request.timeout)、按类别退避重试(429/过载/服务端/网络类,尊重 Retry-After,其余不重试;标了 noRetry 的运行时不具备能力如 goja 无 arrayBuffer 直接失败,不白等退避)、成功时上报 token 用量(媒体用途 tts/image-generation 跳过用量与 token 校准——媒体响应里的 usage 与对话 token 语义无关);错误统一抛带 status/provider/响应体/类别的 ApiError(对外 message 文案不变);overridesFromRule 把规则的 request 段转成 RequestOverrides(含 authToken/authTokenScope),媒体路径由 MediaRequestSpec.overrides 直接传入(已合成,不再二次查规则)。adapter.ts 按协议方言转换请求/响应格式:对话路径 anthropic-messages 走 Messages 格式,媒体请求体一律原样透传(结构由内置预设或骰主的 body 决定,适配层不再做阿里/Google 的扁平 body 兜底包装)。
  • types.ts:用途类型 ChatModelUse('chat'|'compression'|'summarization'|'judge')、MultimodalModelUse('image-understanding'|上述对话用途)、EmbeddingModelUse('text-embedding')、MediaModelUse('tts'|'image-generation');isMediaUse(use) 供跳过用量等判定复用。

请求服务(src/agent/stream.ts)

  • sendChatRequest():非流式对话,函数调用模式(STATUS && !PROMPT_ENGINEERING)下注入 tools/tool_choice,统计用量;支持 throwClassified 上抛分类错误(默认关闭,不影响压缩/评分等调用方)。
  • startStream()/pollStream()/endStream():把模型配置(URL/Key/body)发给后端流式输出服务中转,按 id 轮询增量结果。
  • token 估算与校准:src/token_calibration.ts 按模型记录实际 prompt token 与估算的比值(校准系数),utils/message.ts 裁剪时用系数预测结果;流式请求结束按真实 usage 回写校准,避免长上下文估算偏差。

会话(src/session/)

  • session.ts:
  • Setting:priv、standby、regexTrigger、counter、timer、prob、judge、activeTimeInfo(start/end/segs)。
  • Session:agentName、sessionId、sessionType、state、context、memory、setting、stream、bucket、tool(state/callCount/listen)、lastCtx(不持久化);运行时字段 running(是否有 run/runStream 在跑)、starting(是否正在启动 run/runStream,置位期间同会话新消息一律挂起)、stopVersion(.ai stop 自增的中止版本号)、stopEvent(停止信号,fireStopEvent 唤醒等待者)、pendingQueue(会话忙时挂起的待入库消息队列)、activeRuns(本会话当前在跑的请求数)均不持久化。
  • toolState getter:按 BLOCKED 与 DEFAULT_CLOSED 同步每会话工具开关,并清理已删除工具的残留状态;若会话带 toolRestriction(子代理继承的工具面 allow/deny 求交,deny 优先)则在其上二次收窄。
  • 技能/知识库会话级开关 skillState / kbState:只记录被 .ai skill off / .ai kb off 关闭的项,缺省=开启,随 session.save() 落盘,并参与 system prompt 静态段缓存 key。
  • 子代理运行时字段(均不持久化):headless(reply() 不发送、文本进 headlessTranscript)、toolRestriction、parentSessionId、subagentDepth、noticeQueue(子代理结算通知队列)、headlessTranscript;flushNotices() 在每轮模型请求前把队列写入上下文([system:子代理 …],与 flushPending 同点调用)。
  • chat():配额检查 → 重置 → 流式判断 → Agent.run/runStream → save()。
  • reply():逐段发送并写入上下文。
  • 活跃时间:把时间段切分为 segs 段,checkActiveTimer 用 TimerManager 安排下一次触发。
  • session_service.ts:getSession(sid) 从存储复活会话(经 revive),SessionService 持有 sessionMap 与全局 memory。
  • group.ts / user.ts:群档案(角色/群主/管理员/成员/忽略名单)与用户档案(名称)。
  • types.ts:SessionType('group'|'user')、State、GroupInfo、UserInfo。

上下文(src/context/)

  • types.ts:消息类型定义:UserMessageItem、SystemUserMessageItem、AssistantMessageItem、ToolCallsMessage、ToolCallbackMessage、RequestMessage。
  • message.ts:Message 静态方法判断消息类型;handleMessages 把内部消息转为 API 请求格式,元数据(时间/用户/消息ID/系统来源)存在消息项字段中,多模态模式下把 [img:...] 等图片标签转成 image_url 内容块直接传给模型。系统名义消息(群事件/触发原因/定时器等)与工具返回分别以 [system:名称]…[/system]、[tool_result]…[/tool_result] 成对边界渲染,配合 system prompt 声明防注入;stripInternalTags 统一剥离内部标签及其闭合形式([/system] 等),防止标签逃逸。
  • pipeline.ts 的接收适配遵循核心优先、依赖补充原则:核心 msg.message/Milky msg.segments 直接进入统一转换器;ob11 依赖只补充核心不接收的 file/video/record/json/music/node 等段,并通过唯一消息 ID 去重。转换器输出统一的 [from:...]、[time:...]、[msg_id:...]、[img:...]、[at:...]、[reply:...]、[face:...]、[poke:...] 标签。
  • handleMessages 在组装 system、预设上下文、历史和工具定义后进行整包 token 估算;system 与预设上下文不裁剪,历史从最早消息开始裁剪。长对话中按 INSERT_COUNT 插入短提醒,不会重复复制完整 system prompt。
  • context.ts:Context 类:
  • 添加用户消息(自动改名、超长消息交给 compress_agent 压缩)、助手消息(写入后触发持久化 token 超限归档检查)、系统用户消息、工具调用/回调消息。
  • 持久化 token 超限归档:archiveByTokenIfNeeded() 在新增消息后按需执行——超过 MAX_CONTEXT_TOKENS 时保留最近 MAX_ROUNDS 个真实 user 轮(出现一次真实 user 视作一轮),更早消息按 ARCHIVE_CHUNK_TOKENS(50000 token)切块,逐块经 MemoryManager.summarizeChunkWithRetry(最多重试 3 次)沉淀为观察/长期记忆;归档的群聊原文写入独立分片 key session_<id>:arc:<seq>(只写一次、永不重写,见 src/context/archive_store.ts),再从上下文移除;轮数不够时(待机群常见:只有 1 轮)按 contentItem 粒度走 archiveOldestItems() 归档,同样先 summarize 再写分片;归档失败不再降级硬删(数据保持原样并返回 false,调用方据此如实记录)。函数支持显式 target 预算(模型报错「上下文超长」时按 min(本地预算, 报文窗口×0.8) 复用同一链路归档后自动重发)。handleMessages 另有请求级 token 预算裁剪(整条优先丢弃、仅剩单条保护消息时兜底截断)。
  • 冷热状态(算出来的,不持久化):Session.stateOf() 按"是否待机 + 是否有在途任务 + 距上次相关活动时长"返回 HOT/COOLING/COLD;「相关活动」=任何一次 session.chat()(普通消息入库不算)。冷态下 addUserMessage 零 LLM:只追加 + 纯代码中段截断(冷态单条截断字数),条目标记 compressed 表示永不参与 LLM 压缩;热态压缩遵守三条规则——同一批只尝试一次(含失败)、只压未尝试过的尾部条目、结果必须比原文更短才采纳。buildContextMessages 按「冷态窗口字数」做渲染窗口折叠(窗口 60% + 纯代码折叠摘要 40%,零 LLM),档位由 pickTier() 依最新消息文本判定(L0 默认/L1 含指代/L2 明确要求回顾不折叠)。
  • 会话保存节流(Session.save(coalesce)):高频入库路径(待机消息/事件)传 coalesce=true,5 秒或 20 条最多落盘一次并安排兜底写入;回复、命令、归档等关键路径保持立即写。海豹 storage 是叠加式存储,写越少越好。
  • 事件提示词条目支持 eventType/raw 字段(addSystemUserMessage 第三参传入):eventType 供事件过滤,raw 为事件原始数据、不参与渲染、仅由 read_raw kind=event 读取,随所在消息清理/归档失效,不设条数与长度上限。
  • 事件去重"后到覆盖先到":addSystemUserMessageTracked() 返回条目定位引用(SystemEntryRef),去重窗口内重复到达时 replaceSystemUserMessage() 按引用就地替换旧副本(文本/raw/systemName 取最后到达,不新增条目、位置不变);原位置已被合并压缩/归档改写时返回 false,由调用方追加新副本兜底。
  • 工具回调消息支持 rawText(addToolCallbackMessage 在超长截断时保留截断前完整原文);用户消息条目在压缩替换时保留 rawText(单条)/rawItems(连续多条合并压缩块);均不参与渲染与 token 估算,随所在消息清理/归档失效;由 grep_raw/read_raw 按 kind 只读检索。
  • getUserById / getGroupById:仅按用户ID或群ID获取目标,不执行名称搜索。
  • findImage:支持 user_avatar、group_avatar、图片 ID、本地图片路径、记忆内图片。
  • setName/updateName:按 autoNameMod(0/1/2)自动把名字改为昵称或群名片。

联网(src/web/)

  • types.ts:联网配置与 provider / routes / defaults 的类型定义;schema.ts:v1 JSON 解析与结构校验(错误带 $.providers[0].baseURL 式路径;校验失败整体拒绝、只记日志、本次不生效,不回退默认);defaults 是运行时字段,由 configs/web.ts 的 5 个独立配置项填充,JSON 里的旧 defaults 字段忽略。
  • presets.ts:10 家 provider 方言(DeepSeek / SearXNG / Kimi / Brave / Tavily / Serper / 博查 / 智谱 / Exa / Firecrawl)+ generic 的请求构造与响应解析、搜索/抓取路由、内置默认 SearXNG(https://searxng.fishwhite.top);client.ts:withTimeout 发送与错误分类(400/422 参数错误不换家,其余可回退);service.ts:runSearch / runFetch 回退编排、正文截断、结果渲染(renderSearchResult / renderFetchResult 支持 start_index 续读);template.ts:结果给模型的固定引导文案与模板渲染。
  • 对外工具在 src/tool/tools/web/(分类「网页」):web_search(多 provider 搜索 + 编号摘要)、web_fetch(正文 Markdown,max_chars / start_index 续读)、forum_*(论坛工具,走「后端」页论坛地址/Token)。

记忆(src/memory/)

  • session_memory.ts:SessionMemoryService 会话记忆门面:只包装 Hindsight-like 新引擎(不再使用旧 MemoryService/MemoryItem),提供 retainMemory/recallMemory/deleteMemory/clearMemory/buildObservationPrompt 等,保留 validKeysMap 与兼容方法以平滑读旧存档。
  • revision.ts:记忆/观察缓存失效:bumpMemoryRevision() / bumpSummaryRevision() 自增全局版本号并主动失效对应 prompt 缓存(一次总结可能写入多个会话,统一全量失效)。
  • session_target.ts:resolveTargetSession() 按「当前会话所属 agent」的 sessionService 定位私聊/群聊目标会话,避免写入与检索落到不同 agent 的会话空间。
  • knowledge_chunk.ts:splitMarkdownIntoChunks() 按 #/##/### 标题与段落把 Markdown 文档切块(单块约 800 字符、块间保留重叠),生成稳定 ID(kb_xxxxxxxx)。
  • knowledge_base.ts:KnowledgeBaseService 单例,配置驱动(JSON 里的 Markdown 正文),解析时不出向量(懒向量);search() 先关键词过滤,嵌入可用时仅对命中候选(≤20 块)做惰性向量重排,失败自动降级关键词;buildKnowledgePrompt() 只展示条目索引:ID + 标题,1500 字符预算内尽可能多列(≤100 条),不注入正文,详情由模型用 knowledge_read 按 ID 读取;formatIndex(limit) 支持索引条数上限。
  • knowledge.ts:兼容导出层,knowledgeService = knowledgeBase(旧 KnowledgeService 已移除)。
  • manager.ts:MemoryManager 统一入口:buildLongTermPrompt(心智模型 + 长期记忆混合注入,群聊按「群聊 / 最近发言者」分组)、buildObservationPrompt / buildKnowledgePrompt、retainMemory / recallMemory / consolidateMemory / reflectMemory、summarizeChunk / summarizeChunkWithRetry(上下文 token 超限归档:把将删除的消息块交给 summarize_agent 抽取 facts → 写入长期记忆 → consolidate 为观察记忆,最多重试 3 次)、migrateLegacyPersona(旧 persona 懒迁移为心智模型并清空)。
  • v2/(4.17.0 起):Hindsight-like 纯 TS 记忆引擎。engine.ts 实现 Retain(抽取原子事实/观察记忆,可选 LLM 结构化抽取 occurred_at/entities)、Recall(RRF 混合检索 + Hindsight 式新近度加权(按 lastAccessedAt 指数衰减,权重/半衰期可配)+ 可选 LLM 重排)、Consolidation(LLM 合成观察记忆、合并重复、清理 90 天过期观察)、MentalModel(心智模型:question/answer/scopeTags/status/lastRefreshedAt/history/trigger,支持 refresh/delete、persona 懒迁移)、固定模板(templates.ts 按场景写死「设定/偏好/规则」问题,ensureDefaultMentalModels 按 seededMentalModelVersion 版本化播种:升级只增量补新、删除不复活;固定模板优先注入、语义召回补余)、遗忘机制(长期记忆条数上限,默认 100,0=不限制;retain/consolidate 后超限时按「已被观察覆盖 → 普通按衰减分 → observation 沉淀 → 保护项(importance≥0.9 或 pinned)」优先级物理删除多余记忆,上限为硬约束);repository.ts 统一存取长期/观察记忆与心智模型,bank_resolver.ts 按会话解析记忆库(私聊 user_<id> / 群聊 group_<gid>),migrate.ts 负责旧格式(memoryMap/summaries/persona)自动迁移,llm.ts 提供可选的 LLM 抽取/重排/合成/推理回调,prompt.ts 负责注入裁剪(scopeTags 任一命中过滤、心智模型只注入 ready、观察按 lastVerifiedAt 剔除 90 天过期)与分组渲染(buildGroupedMemoryPrompt:每组段头标注会话身份与证据范围——群聊/个人 + 群号/ID(含名称),reflect 提示词开头注入会话范围行)。manager.ts buildLongTermPrompt 在群聊时除群记忆库外,还会并入最近发言者(最多 3 人)的个人记忆库并分组渲染:群回忆 ≤20、群心智模型 ≤3(固定优先),每个发言者个人回忆 ≤20 条且单用户 2048 字符预算、个人心智模型 ≤2(固定模板 ≤1),观察记忆由 buildObservationPrompt 独立注入(≤20 条、90 天过期剔除),私有记忆仅创建者本人可见;查询向量每轮只算一次,复用于群聊与各发言者个人库。配套记忆工具为 memory_* 系列(增删改查/巩固/推理/心智模型,含 memory_mm_list/view 查看心智模型、memory_mm_delete 可按 model_id 或 question 删除),.ai memo 子命令重构为 status/add/list/update/delete/clear/obs/consolidate/reflect/mm,范围参数改用 --u/--g。

智能体(src/agent/)

  • agent.ts:
  • Agent 字段:name、description、instruction(可为函数)、use、sessionService、tools、subAgents。
  • Agent.get(name) / Agent.save(agent):从 agent_<name> 存储复活/保存。
  • chat(prompt):直接发起一次单轮对话(压缩/摘要智能体使用)。
  • run():完整编排循环——组装消息 → 请求 → 工具执行(函数调用或提示词工程)→ 复读检测重试 → 最终回复。
  • runStream():流式编排,轮询后端流,检测 ```function 代码块截断,执行工具后递归续流。
  • 模型报错自动处理(ErrorConfig 开关):主链路捕获分类 ApiError 后——上下文超长按 min(本地预算, 报文窗口×0.8) 观察归档删除并自动重发一次;余额不足等触发类别自动把 chat 用途切到备用模型(按跨厂商优先/配置顺序选,写入全局覆盖并持久化,可选 ctx.notice 通知;单 run 各至多一次);其余未覆盖错误仅记日志不回复。
  • .ai stop 中止机制:run/runStream 启动时捕获 session.stopVersion 并通过代际检查;stop 时 fireStopEvent 置位会话级 stopEvent(StopEvent)并同步唤醒所有 withTimeout/requestModel 等待者(以 StopError reject),同时归零 activeRuns/running、清空挂起队列与待触发计时器;StopError 不算工具失败回填,消息组装/请求返回/工具执行后/最终回复前均设检查点,检测到版本变化或 StopEvent 即中止;goja 无 AbortController,底层 fetch 无法硬中断,但返回结果会被丢弃。并发队列中该会话的等待项由 requestLimiter.cancelBySession 丢弃。
  • 挂起队列机制:会话运行(或正在启动)时收到的新消息经 session.deferReceipt 进入 pendingQueue 挂起,run/runStream 每轮模型请求前 flushPending 统一入库(此时上一轮工具回调已写入上下文,插入位置合法);记录类只入库不续跑,触发类在链结束后由 resumePending 用第一条触发消息串行再起一轮;.ai stop 清空挂起队列并自增 stopVersion,停止后不复活。
  • run_context.ts:AgentRunContext 记录单次 run 的观测信息(runId/轮次/工具调用事件/耗时)。
  • api.ts:对外 API,启动时挂载 globalThis.aiplugin4(智能体调用 chat/run/getAgent 与工具注册 registerTool),供其他海豹插件使用。
  • agents/:包含 compress_agent(压缩)、summarize_agent(摘要/上下文归档总结);samples.ts 的 sample_agent 仅作参考,不在 initAgents 中初始化。
  • subagent/(4.22 起,DSH 对齐子代理框架):把独立任务委派给 headless 子代理——child.ts 子会话编排(spawn 无 seed / fork 继承父上下文已完成轮次;原生模式只向子代理注入 list_tools/search_tools/call_tool;单次激活默认 8 轮、总预算 120s),child_system.ts 子代理 system 组装(persona/默认指令 + 工具引导,不含父记忆/知识库/委派指引),providers.ts spawn/fork executor,service.ts 服务层(启动/前台与可续跑/发送消息/打断/结算通知/清理),jobs.ts 后台 job(run_in_background,id subagent-job-N,结果由 job_output 收取,完成不投 notice),store.ts KV 持久化(subagent_index:<父>/subagent_seq:<父>/subagent:roots/session_sub:<父>:<child>,每父会话保留 100 条),wake.ts 父会话空闲唤醒决策,rules.ts fork seed 计算与错误文案,limits.ts 委派深度限制与 system prompt 委派指引文本,wiring.ts 运行时接线(ext KV、notice 入父 noticeQueue(agent run 每轮 flushNotices 呈现)、重载后 running 对账为「已中断·可续」)。配套工具在 src/tool/tools/subagent/init.ts(subagent/subagent_fork/send_message/interrupt_agent/list_agents/job_list/job_output/job_kill),命令 .ai subagent,配置分组「子代理」。

媒体生成(src/media/ + src/model/media*.ts,4.24.0 起内置)

语音合成与文生图不再依赖外部插件,在「模型」页模型配置里给模型声明 kind: "tts" / "gen" 即进入 tts / image-generation 用途候选。请求结构默认由内置供应商预设提供(src/config/static_config/media_presets.ts,迁移自旧依赖插件的 MODELS.md):骰主只填连接就能生成,该模型的 request 只在接口与预设不一致时才写覆盖。

  • src/media/task_queue.ts:MediaTask(id/sid/isPrivate/epId/kind/origin/status/createdAt/updatedAt/request/result/error/notified/requester/save/name,含 validKeys 供 revive 恢复)与 MediaTaskManager 静态管理器。
  • 提交与执行:submit() 建任务入队、落盘并立即返回(工具/命令随即把任务号回给 AI 或用户);pump() 在「媒体任务并发上限」(默认 3)内启动 pending 任务,run() 调 entry.callTTS / entry.callTTI 执行。
  • 结算分流(origin):command(人类敲 .ai tts / .ai tti)由插件直接 replyToSender 投递;tool(AI 调工具)先把结果写入会话 noticeQueue,再尽力触发一轮对话投递(session.chat(ctx, msg, '生成任务完成', undefined, { skipBucket: true }))。
    • ⚠️ 硬约束(不得违反):工具来源的媒体结果"要么已呈现给 AI、要么仍在 noticeQueue 待呈现",绝不允许两头落空。 这条约束的存在原因是一次真实线上故障:实现曾写成果完成后无条件 session.chat(),而 Agent.run()/runStream() 开头有一道同会话闸门
      if (session.running || session.starting) return;   // 静默 return:不抛错、不排队、不打日志
      
      媒体唤醒恰好是唯一绕开 pipeline 分流、直接调 chat() 的调用方(普通消息在 pipeline 里已由 deferReceipt 挂起,不会撞到它)。于是生成极快时: 完成通知入队 → 直接 chat() → 撞闸门被静默丢弃(Promise 正常 resolve,.catch 不触发,日志还写着"触发成功") → 若本轮已过最后一次模型请求,flushNotices 不会再消费它 → 永久等待。 更糟的是入队时就置了 notified = true(谎报成功),把"重载补发(done && !notified)"这条兜底也一并堵死。 改动媒体送达链路时必须保持下列三段式,缺一不可:
    • 入队:单独一步、置于所有可能抛错的调用之前;通知带 taskId 作为"是否已被消费"的身份标识; 入队时不得置 notified —— notified 的唯一语义是"已呈现给 AI",只能由 flushNotices 真正消费时经 MediaTaskManager.markNotifiedByNotice() 回置。
    • 判定:走 decideWake 同一份口径;会话忙(running || starting)则 defer(通知留在队列,不丢), 不得假定"忙 → 本轮 flushNotices 一定会注入"(本轮可能已过最后一次模型请求)。 注意:媒体结果不受待机拦截(待机约束的是"主动搭话",这是用户请求的送达),与子代理的唤醒口径不同。
    • 轮末补触发:Agent.run()/runStream() 的 finally 中,resumePending() 之后调用 MediaTaskManager.wakePendingNotices(session) —— 由"会话真的结束了"这一确定事件驱动,不靠猜时机、不会永久等待。 判定条件:activeRuns === 0 && !running && !starting、未 stopEvent.fired、且队列里仍有带 taskId 的媒体通知; 起一轮后该轮结束时再检查,届时通知已被消费 → 自动停止,不会死循环。 (历史教训:曾有实现改用"忙则定时重试 N 次"(wakeRetryMax/wakeRetryDelayMs),后被整体移除;计时器方案要猜"还要忙多久", 猜短白跑、猜长延迟送达,次数用尽仍可能落空 —— 故不采用。)
    • 入队与唤醒分离:入队单独一步且置于所有可能抛错的调用之前,唤醒侧失败(如端点离线导致 getSessionCtxAndMsg 抛错)只影响"是否立刻起一轮",通知不会丢;每一步的跳过/失败都留含任务号与原因的日志(原实现把两步包在同一个 try 里,异常被一行 WARN 吞掉,表现为"任务完成什么都没发生")。
    • 免桶送达:调用 session.chat(...) 时带 { skipBucket: true } —— 跳过令牌桶检查且不扣减。注意与旧有 reason === '函数回调触发' 的区别:那条豁免只跳过检查、仍扣减令牌,行为未改动。
  • 提交后同步等待窗口:工具调用(text_to_image / generate_audio)在 submit() 后用 MediaTaskManager.waitForSettle(task, ms) 轮询等待,窗口由「工具 → 媒体生成同步等待秒数」控制(默认 5 秒,0=关闭)。 窗口内已结算(done/failed)→ 工具当场返回可执行结果(图片直接给 [img:ID],语音给 record 段指引), 这批结果根本不进唤醒链路;未结算则返回任务号走异步。 价值:生成过快时(本地模型/缓存命中/极短文本)省掉一轮唤醒与一次模型调用,也天然避开临界点。
  • 结果落地:音频 seal.base64ToImage 存本地文件、图片走 Image(imageId);save 意图在完成时用 getSessionCtxAndMsg(epId,sid,isPrivate) 重建 ctx 后写记忆。队列与通知只存句柄/短摘要,绝不落 base64;参考图 base64 只存运行期内存(runtime Map)。
  • 恢复与清理:每会话最多保留 50 条已结束任务(未结束永不淘汰);启动读盘时 running → failed('插件重载中断'),done && !notified 且来源为工具则补通知;cancel(sid,id) 只对 pending/running 生效。
  • src/tool/tools/media/:媒体工具。shared.ts 提供 buildMediaToolInfo(把 description 与 parameters 做成惰性 getter,读取时按当前 per-model 规则生成逐模型参数清单与 schema,规避"注册时机早于模型列表就绪"的脆弱性)与 resolveMediaEntry / buildMediaSubmitReply;tool_media_tasks.ts 注册 media_tasks(list/cancel,归「多媒体」分类)。
  • 工具与命令:generate_audio 与 text_to_image 改为提交任务并立即返回(generate_audio 传 path 时仍同步解析本地/MCP 音频资源,不进队列);新增 .ai tts / .ai tti / .ai media(src/cmd/sub_cmd/{tts,tti,media,media_shared}.ts),三者的 priv 约定见 05-命令与配置。
  • 注意:.ai tts / .ai tti 的 priv 不能带 args——PrivilegeManager.checkPriv 会把 cmdArgs.args 逐段拼进权限链路,带 args 时正文会被当成子指令而误报"命令不存在";正文解析从 cmdArgs.args 第 2 位起按 RE_KEYWORD 剔除 --xxx 后拼接(不用 getRestArgsFrom,它在真实 SealDice 下可能把 --rate=1.5 并进正文)。

命令系统(src/cmd/)

  • root_cmd.ts:注册根命令 .ai(注册到 cmdMap['ai'] 与 cmdMap['AI']),别名经 aliasToCmd 归一;子命令统一分发,兜住异步异常并回复错误;支持 --page/--p 翻页。
  • privilege.ts:命令权限系统,CmdPrivInfo { priv: [会话权限, 用户权限, 强触权限] },预置 U(user)/M(master)/I(inviter)/S(会话需 inviter,否则骰主);PrivilegeManager 管理 cmdPriv 存储与校验,支持 * 通配。
  • sub_cmd/:30 个文件,其中 28 个注册到根命令(balance/kb/mcp/model/pub/skill/subagent/tts/tti/media 等),sample.ts 未注册(仅开发参考)、media_shared.ts 为 tts/tti/media 共用的解析与模型定位辅助(不注册子命令),详见 05-命令与配置。

定时器(src/timer.ts)

  • TimerInfo:sid/isPrivate/epId/set/target/interval/count/type/content;type 分 target(目标时间)、interval(间隔)、activeTime(活跃时间)。
  • TimerManager:队列持久化到 timerQueue;executeTask() 每 5 秒扫一轮,每个定时器执行间隔 2 秒;超过目标时间 1 小时未执行则忽略;触发时注入系统用户消息并调用 session.chat。

用量统计(src/usage.ts)

  • UsageManager:按模型按天(Y-M-D)累计 prompt/completion token,持久化到 usageMap;过期数据归并(30 天前 → Y-M-0,11 个月前 → 0-0-0)。
  • get_chart_url():把用量数据 POST 给用量图表后端 /chart,返回图片 URL。

日志(src/logger.ts)

  • 输出格式:【AI骰娘4】[级别短标签] [模块标签] 内容。级别短标签为 ERROR/WARN/INFO/DEBUG,与海豹面板日志级别/颜色对应,便于按级别过滤。
  • 模块标签:通过 logger.withTag('模块名') 得到带标签的日志对象,已覆盖 agent/model/pipeline/timer/send/ob11/context/image 等核心链路;全局 logger 仍可用。
  • 级别:从不/错误/警告/信息/调试,由 日志级别 配置控制。
  • 单条截断 2000 字符;日志简短打印 时首尾各保留 500 字符;logLong() 超长文本按片打印并标注 [第x段/总段数]。
  • 密钥脱敏:api_key/token/authorization/secret/password/app_secret/appkey 字段、URL query 中的同名参数、Bearer xxx、sk- 前缀密钥、access/refresh token。
  • exception() 错误进 error、堆栈进 debug;printRequestMessages() 默认只打 [请求上下文] run=... 共N条 摘要,逐条 role/长度/toolCalls 与正文进 debug(正文受 日志记录消息内容 开关控制,截断 300 字符,默认不打印 system)。

通用工具(src/utils/)

文件 职责
string.ts 消息段解析/转换、图片抽取、回复拆分、复读检测、特殊 token 解析、编辑距离/相似度、fmtDate、fixJsonString、活跃时间解析
message.ts system prompt 组装、预设上下文、上下文拼接与请求级 token 预算裁剪(整条优先、单条兜底截断,含模型校准系数)、最终请求消息、getRoleSetting
ob11.ts OneBot 11 API 封装:收发消息、用户/群信息、禁言、精华消息、打卡、取/撤消息、合并转发、AI 语音
seal.ts 构造 ctx/msg、会话 ID 工具
update.ts 版本比较与启动更新日志
utils.ts 消息 ID 进制转换、generateId、replyToSender、withTimeout、revive、别名、余弦相似度、resolveLocalPath
web.ts fetchData 统一 POST 请求(错误信息提取、鉴权头/内容类型覆盖、按 content_type 编码请求体:JSON / multipart 自行拼串并回填 boundary / x-www-form-urlencoded);dataType 支持 json(默认)/binary(响应体即二进制,转 base64;按 content-type 自适应,JSON 响应仍走解析)/text 三种取数方式,供媒体生成读取音频流;binary 在配置了 backends.media-fetch 时直接由后端代发(只产生一次上游请求;后端 4xx 状态码/上游 bodyText 还原成 ApiError 供分类)

图片资源(src/resource/image.ts)

  • Image:图片对象(url/local/base64 三种来源),提供 CQCode、urlToBase64、imageToText;全局 imageMap 缓存与 save。
  • 头像:getUserAvatar / getGroupAvatar(经 ob11 获取头像 URL)。

其他

  • src/trigger.ts:触发模块占位(预留)。
  • src/pub/(4.22 起):公开会话目录——registry.ts 目录条目持久化(存储键 pubSessions)与 list/add/按平台·Bot·会话过滤删除,snapshot.ts 把目标会话 context.messages 渲染为只读快照文本;配套命令 .ai pub list/add/rm(src/cmd/sub_cmd/pub.ts)与工具 pub_read/pub_send(src/tool/tools/pub/)。
  • src/changelog.ts:版本更新日志(changelog),启动时由 checkUpdate 输出。
  • src/note.txt:作者开发备忘(TODO 与重构思路),不属于运行代码。