diff --git a/AGENT.md b/AGENT.md index a9745ff..9ddaee3 100644 --- a/AGENT.md +++ b/AGENT.md @@ -43,7 +43,7 @@ npm run pack:release # 发布打包:本体 JS + 本体豹包 + 完整 4. **新增消息级能力优先做成"工具"**(`src/tool/tools/` 下新建文件导出 `registerXxx()`,在 `src/tool/tools/init.ts` 注册);敏感操作(发消息/禁言/改名等)置 `sensitive = true`;工具名不要与已有工具重复。 5. **`registerTemplateConfig` 默认值不能是空数组**,至少保留一个元素(占位用 `['']`)。 6. **配置项描述要引导用户**:写清格式、必填/可选参数、示例;配置页签按重要性排列(基础/模型/对话/消息接收/消息触发/回复/工具/记忆/图片/后端/prompt模板/资源)。 -7. **模板文案放配置模板**(`src/config/configs/prompt.ts`),不要硬编码在业务逻辑。 +7. **模板文案放内置模板**(`src/prompt/templates.ts`),不要硬编码在业务逻辑。 8. **日志统一走 `Logger`**(脱敏/截断),不要直接打明文密钥;网络请求统一 `withTimeout` + `fetchData`/`requestModel`,避免卡死。 9. **修改持久化类字段后检查 `validKeysMap`** 是否需要同步更新(用 `revive()` 恢复)。 10. **新增对外 API**(`globalThis.aiplugin4`)时同步更新 `docs/07-开发指南.md` 的「为其他插件提供 API」表格与示例。 @@ -58,7 +58,7 @@ npm run pack:release # 发布打包:本体 JS + 本体豹包 + 完整 ## 发布流程 -1. 版本推进:`src/config/static_config.ts` 的 `VERSION`、`header.txt` 的 `@version`、`sealpack/info.toml` 的 `version`(由 `scripts/prepare-sealpack.js` 自动同步)、`src/update.ts` 新增对应版本条目,走 PR 合并到 main。 +1. 版本推进:`src/config/static_config/meta.ts` 的 `VERSION`、`header.txt` 的 `@version`、`sealpack/info.toml` 的 `version`(由 `scripts/prepare-sealpack.js` 自动同步)、`src/update.ts` 新增对应版本条目,走 PR 合并到 main。 2. 推送 `v<版本>` 标签 → GitHub Actions `release.yml` 自动:verify(校验标签与 VERSION/update.ts 一致)→ `node scripts/build-release.js` 打包(本体 JS + 本体豹包 + 完整豹包)→ 用 `SEALPACK_TOKEN` 发布两个豹包到 SealRepo → 从 `update.ts` 提取版本日志创建 GitHub Release。 3. 完整包依赖插件在 `scripts/deps.cjs` 的 `dependencies` 配置(`url` 为 raw 地址);包图标 `sealpack/assets/icon.png`;SealRepo Token 放仓库 secrets(`SEALPACK_TOKEN`)。 diff --git a/README.md b/README.md index 12936ea..1f151ca 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,6 @@ max_tokens = 2048 |:---:|:---| | 图片全局识别豹语条件 | 填 `'1'` 开启所有图片自动识别转文字;或填豹语表达式限制群/用户范围 | | 识别图片时将url转换为base64 | 永不 / 自动 / 总是,解决大模型无法正常获取 QQ 图床图片的问题 | -| 图片识别默认prompt | 识图时的默认提示词 | ### 工具 @@ -325,6 +324,7 @@ max_tokens = 2048 | 命令 | 使用示例 | 说明 | |:---:|:---:|:---| | `.ai status` | - | 查看当前会话设置 | +| `.ai help <子指令>` / `.ai <子指令> help` | `.ai help tool` | 查看子命令帮助;`.ai <子指令> help` 带更多参数时交由子命令自身处理(如 `.ai tool help <函数名>`) | | `.ai ctxn status` | - | 查看上下文中的名字与自动修改状态 | | `.ai ctxn set [nick/card]` | - | 将上下文中的名字设置为昵称/群名片 | | `.ai ctxn mod <0\|1\|2>` | - | 自动修改上下文中的名字:0 不修改,1 昵称,2 群名片 | diff --git "a/docs/01-\351\241\271\347\233\256\346\246\202\350\247\210.md" "b/docs/01-\351\241\271\347\233\256\346\246\202\350\247\210.md" index df231e5..849fc3f 100644 --- "a/docs/01-\351\241\271\347\233\256\346\246\202\350\247\210.md" +++ "b/docs/01-\351\241\271\347\233\256\346\246\202\350\247\210.md" @@ -29,7 +29,7 @@ AI 骰娘4(`aiplugin4`)是运行在 SealDice 上的 JavaScript 插件,为骰娘 ## 版本信息 -- `src/config/static_config.ts` 中 `VERSION = "4.15.0"`,作者 `baiyu&错误`,插件名 `aiplugin4`。 +- `src/config/static_config/meta.ts` 中 `VERSION = "4.15.0"`,作者 `baiyu&错误`,插件名 `aiplugin4`。 - 开发遵循「分支 + squash PR」流程(见 [07-开发指南](07-开发指南.md));近期工作围绕:模型配置 TOML 化、Provider 统一请求(超时/重试/用量上报)、智能体编排重构、MCP/技能、日志治理、消息管线、配套后端独立仓库化等。 - README 顶部徽章与 `VERSION` 保持一致(当前 4.15.0)(详见 [09-注意事项与常见问题](09-注意事项与常见问题.md))。 diff --git "a/docs/02-\346\236\266\346\236\204\350\256\276\350\256\241.md" "b/docs/02-\346\236\266\346\236\204\350\256\276\350\256\241.md" index 7d9e025..8412eff 100644 --- "a/docs/02-\346\236\266\346\236\204\350\256\276\350\256\241.md" +++ "b/docs/02-\346\236\266\346\236\204\350\256\276\350\256\241.md" @@ -25,7 +25,7 @@ ┌──────────────────────────────────────────────────────────────┐ │ 智能体 src/agent/agent.ts │ │ run() / runStream():构建消息 → 请求模型 → 工具调用循环 → 回复 │ -│ 轮次上限(MAX_TOOL_TURNS=10)、复读检测、最多尝试 3 次 │ +│ 工具轮数由配置 MAX_CALL_COUNT 控制(0 不限制)、复读检测、最多尝试 3 次 │ └───────────────┬──────────────────────────────────────────────┘ ▼ ┌──────────────────────────────────────────────────────────────┐ @@ -103,7 +103,7 @@ 3. `Agent.run()`: - `handleMessages()` 组装请求消息:system prompt(分节)+ 示例对话 + 会话上下文(按 `INSERT_COUNT` 周期插入 system、按 `MAX_CONTEXT_TOKENS` 裁剪、过滤无回调的 tool_calls)。 - `streamService.sendChatRequest()` 请求模型。 - - 若开启函数调用且返回 `tool_calls`(或提示词工程模式匹配 ```function 代码块),先发送已生成文本,把工具调用写入上下文,执行工具(`ToolRunner`),把回调写回上下文,然后进入下一轮,直至模型给出最终回复或达到 `MAX_TOOL_TURNS`。 + - 若开启函数调用且返回 `tool_calls`(或提示词工程模式匹配 ```function 代码块),先发送已生成文本,把工具调用写入上下文,执行工具(`ToolRunner`),把回调写回上下文,然后进入下一轮,直至模型给出最终回复(工具轮数由「允许连续调用函数次数」配置控制,0 为不限制)。 - `checkRepeat()` 检测复读,检测到则重新请求,一轮对话最多尝试 3 次(含首次)。 4. `Session.reply()`:对每段回复调用 `replyToSender` 发送(ob11 依赖可用时拿到 message_id),把助手消息写入上下文。 5. 上下文写入助手消息时触发记忆权重更新与短期记忆总结检查(`SUMMARY_INTERVAL`),最后 `session.save()` 持久化。 diff --git "a/docs/03-\346\240\270\345\277\203\346\250\241\345\235\227\350\257\246\350\247\243.md" "b/docs/03-\346\240\270\345\277\203\346\250\241\345\235\227\350\257\246\350\247\243.md" index 6a18714..e0a4b59 100644 --- "a/docs/03-\346\240\270\345\277\203\346\250\241\345\235\227\350\257\246\350\247\243.md" +++ "b/docs/03-\346\240\270\345\277\203\346\250\241\345\235\227\350\257\246\350\247\243.md" @@ -29,7 +29,7 @@ - 读取带缓存:`CONFIG_CACHE_TTL = 60000` 毫秒,`Object.defineProperty` 把 `Config.<分组>` 变为缓存 getter;`ModelConfig.get()` 在注册时预热并同步模型静态列表。 - `getRegexConfig()`:把模板配置的多行正则拼接为单个 `RegExp`,失败时返回永不匹配的 `/(?!)/`。 -### static_config.ts(静态常量) +### static_config/(静态常量) - `VERSION`(当前 4.15.0)、`AUTHOR`、`NAME`、`STORAGE_VERSION`、`CONFIG_CACHE_TTL`、`CQ_TYPES_ALLOW`。 - `PRIVILEGE_LEVEL_MAP`:master=100 / whitelist=70 / owner=60 / admin=50 / inviter=40 / user=0 / blacklist=-30。 @@ -37,8 +37,9 @@ - `PROVIDER_MAP`:10 家厂商的 OpenAI 兼容 base_url。 - `CHAT_MODEL_MAP` / `IMAGE_MODEL_MAP` / `EMBEDDING_MODEL_MAP` 及反向映射:模型名 → 提供商自动识别。 - `DEFAULT_CHAT_MODEL_BODY` / `DEFAULT_IMAGE_MODEL_BODY` / `DEFAULT_EMBEDDING_MODEL_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/(12 个分组) +### configs/(11 个分组) | 文件 | 分组 | 主要配置项 | | --- | --- | --- | @@ -52,14 +53,13 @@ | `memory.ts` | 记忆 | 长期/总结/知识库开关、总结上限/间隔/参与轮数、短期记忆上限、知识库注入阈值、知识库(Markdown 模板) | | `reply.ts` | 回复 | 引用(含戳戳跳过)、最大字数、去空白、禁止复读与相似度、过滤正则与模板 | | `message.ts` | 对话 | 角色扮演设定(每条第一行为名称,以 `.` 开头隐藏)、示例对话、轮数、token 上限、system 插入间隔、压缩阈值 | -| `prompt.ts` | prompt 模板 | 6 个内置 Handlebars 模板(见下文,不再注册为配置项) | | `resource.ts` | 资源 | 本地图片路径、本地语音路径(`语音名=路径`,供 system prompt 列出资源) | 注意:本地图片/语音路径由资源分组统一注册;图片发送与 system prompt 资源列表读取同一份配置(本地语音路径支持 `语音名=路径`,省略语音名时用文件名)。 -### prompt 模板(configs/prompt.ts) +### prompt 模板(src/prompt/templates.ts) -模板在代码中以 `TEMPLATES` 常量内置(不再注册为配置项,避免误改导致渲染损坏),读取时 `compileTemplate` 编译,渲染抛错时回退空函数。模板清单: +模板在代码中以 `TEMPLATES` 常量内置(不再注册为配置项,避免误改导致渲染损坏),读取时 `compileTemplate` 编译,仅在编译失败时回退空函数;渲染期异常不兜底,由调用方处理。模板清单: - `system prompt模板` - `长期记忆prompt模板` diff --git "a/docs/04-\345\267\245\345\205\267\347\263\273\347\273\237.md" "b/docs/04-\345\267\245\345\205\267\347\263\273\347\273\237.md" index 6b3eb4b..97b8cca 100644 --- "a/docs/04-\345\267\245\345\205\267\347\263\273\347\273\237.md" +++ "b/docs/04-\345\267\245\345\205\267\347\263\273\347\273\237.md" @@ -38,7 +38,6 @@ 防失控限制: - `允许连续调用函数次数`(`MAX_CALL_COUNT`,默认 10):单次触发内会话累计工具调用次数上限,设为 0 不限制。 -- `MAX_TOOL_TURNS = 10`(`src/agent/agent.ts`):单次对话中允许的连续工具调用轮次上限。 ## 工具回调压缩 diff --git "a/docs/05-\345\221\275\344\273\244\344\270\216\351\205\215\347\275\256.md" "b/docs/05-\345\221\275\344\273\244\344\270\216\351\205\215\347\275\256.md" index 903cf52..0badbd9 100644 --- "a/docs/05-\345\221\275\344\273\244\344\270\216\351\205\215\347\275\256.md" +++ "b/docs/05-\345\221\275\344\273\244\344\270\216\351\205\215\347\275\256.md" @@ -4,7 +4,7 @@ ## `.ai` 命令体系 -根命令 `.ai`(`AI` 大写同样注册),子命令别名经 `ALIAS_MAP` 归一(常用:`priv→privilege`、`ses→session`、`st→set`、`ck→check`、`clr→clear`、`sb→standby`、`fgt→forget`、`memo→memory`、`p→private`、`g→group`、`tk→token`、`y→year`、`m→month`、`lcl→local`、`ran→random`、`lst→list`、`rm→remove`)。支持 `--page=<数字>`(别名 `--p`)翻页;`.ai help <一级子指令>` 可查看对应子命令帮助。 +根命令 `.ai`(`AI` 大写同样注册),子命令别名经 `ALIAS_MAP` 归一(常用:`priv→privilege`、`ses→session`、`st→set`、`ck→check`、`clr→clear`、`sb→standby`、`fgt→forget`、`memo→memory`、`p→private`、`g→group`、`tk→token`、`y→year`、`m→month`、`lcl→local`、`ran→random`、`lst→list`、`rm→remove`)。支持 `--page=<数字>`(别名 `--p`)翻页;`.ai help <一级子指令>` 或 `.ai <一级子指令> help` 可查看对应子命令帮助(后者带更多参数时交由子命令自身处理,如 `.ai tool help <函数名>`)。 ### 子命令一览(源码为准) diff --git "a/docs/07-\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/docs/07-\345\274\200\345\217\221\346\214\207\345\215\227.md" index 81b124a..4fbd9c8 100644 --- "a/docs/07-\345\274\200\345\217\221\346\214\207\345\215\227.md" +++ "b/docs/07-\345\274\200\345\217\221\346\214\207\345\215\227.md" @@ -90,6 +90,8 @@ registerSay(); `SubCmdContext` 提供:ctx、msg、cmdArgs、epId、uid、gid、sid、session、page、ret。 +> `.ai <子指令> help`(无更多参数)是内置保留用法:根命令直接展示该子指令的 `cmd.help`,不校验权限;若子指令需要处理 `help` 后的更多参数(如 `.ai tool help <函数名>`),在 `solve` 内自行处理。新增子命令务必填写 `cmd.help`。 + ## 添加一个智能体 1. 在 `src/agent/agents/` 新建文件,导出 `init()`,内部用 `Agent.get(name)` 获取实例并配置 name/description/instruction/use。 @@ -173,7 +175,7 @@ api.registerTool({ ## 修改 prompt 模板 -- 模板在 `src/config/configs/prompt.ts` 中定义默认值(注意改动需要同时更新默认注册内容,WebUI 中"刷子"可还原默认)。 +- 模板在 `src/prompt/templates.ts` 中以内置常量定义(不再注册为配置项,WebUI 无默认值可还原;改动模板后需重新构建)。 - system prompt 的分节组装逻辑在 `src/prompt/builder.ts`,新增节时需要同步修改模板骨架与构建函数。 - 记忆/知识库/工具段的开关在 `src/memory/manager.ts` 与 `src/config/configs/memory.ts`。 @@ -197,7 +199,7 @@ api.registerTool({ 配套后端服务已迁移到独立仓库 [aiplugin4-backends](https://github.com/error2913/aiplugin4-backends),由 `launcher.py` 一键管理(list/setup/start/stop/status/port/package/webui),Windows/Linux 通用;默认不启动任何后端,首次启动某后端时才自动安装其环境(独立 venv),由该仓库自身发版流程打包发布。详见 [08-相关后端项目](08-相关后端项目.md)。 -发版前记得同步:`src/config/static_config.ts` 的 `VERSION`、`header.txt` 的 `@version`、`src/update.ts` 的 `updateInfo`(且需包含新版本条目)。 +发版前记得同步:`src/config/static_config/meta.ts` 的 `VERSION`、`header.txt` 的 `@version`、`src/update.ts` 的 `updateInfo`(且需包含新版本条目)。 ## 开发小贴士 diff --git "a/docs/09-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/docs/09-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\345\270\270\350\247\201\351\227\256\351\242\230.md" index 857f2b5..1e76f47 100644 --- "a/docs/09-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ "b/docs/09-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -14,7 +14,7 @@ ## 代码中的已知不一致 -- `src/config/static_config.ts` 的 `VERSION` 与 `src/update.ts` 的 `updateInfo` 最新条目需保持一致(当前 4.15.0);`checkUpdate` 按 `VERSION` 比较输出对应更新日志。 +- `src/config/static_config/meta.ts` 的 `VERSION` 与 `src/update.ts` 的 `updateInfo` 最新条目需保持一致(当前 4.15.0);`checkUpdate` 按 `VERSION` 比较输出对应更新日志。 - `package.json` 的 `main`/`typings` 指向 `build/index.js`/`build/index.d.ts`,但实际构建产物是 `dist/aiplugin4.js`(esbuild),`build/` 目录是陈旧的 tsc 输出,与构建流程无关。 - `engines.node >= 10` 是模板遗留,实际构建依赖 esbuild,建议使用较新 Node。 - `sub_cmd/sample.ts` 与 `agent/agents/samples.ts` 是开发示例,未注册到运行时。 diff --git a/scripts/build-release.js b/scripts/build-release.js index 03d4ed5..dd6f64e 100644 --- a/scripts/build-release.js +++ b/scripts/build-release.js @@ -20,10 +20,10 @@ const FULL_PACKAGE_NAME = 'AI骰娘4-扩展'; const FULL_PACKAGE_DESC = 'AI骰娘4 扩展包(完整包):包含本体与依赖插件,安装即用'; function getVersion() { - const src = fs.readFileSync(path.join(root, 'src', 'config', 'static_config.ts'), 'utf8'); + const src = fs.readFileSync(path.join(root, 'src', 'config', 'static_config', 'meta.ts'), 'utf8'); const m = src.match(/VERSION\s*=\s*["']([^"']+)["']/); if (!m) { - console.error('未能在 src/config/static_config.ts 中找到 VERSION'); + console.error('未能在 src/config/static_config/meta.ts 中找到 VERSION'); process.exit(1); } return m[1]; diff --git a/scripts/prepare-sealpack.js b/scripts/prepare-sealpack.js index ea1a82c..ffa3ce1 100644 --- a/scripts/prepare-sealpack.js +++ b/scripts/prepare-sealpack.js @@ -1,5 +1,5 @@ // 构建 sealpack 打包源目录(sealpack/): -// 1. 从 src/config/static_config.ts 读取插件版本 VERSION; +// 1. 从 src/config/static_config/meta.ts 读取插件版本 VERSION; // 2. 将构建产物 dist/aiplugin4.js 复制为 sealpack/scripts/main.js; // 3. 同步 sealpack/info.toml 中的 version,保证与插件版本一致; // 4. 在 stdout 输出版本号,供 CI 拼装产物文件名。 @@ -7,7 +7,7 @@ const fs = require('fs'); const path = require('path'); const root = path.resolve(__dirname, '..'); -const staticConfig = path.join(root, 'src', 'config', 'static_config.ts'); +const staticConfig = path.join(root, 'src', 'config', 'static_config', 'meta.ts'); const bundle = path.join(root, 'dist', 'aiplugin4.js'); const sealpackDir = path.join(root, 'sealpack'); const mainJs = path.join(sealpackDir, 'scripts', 'main.js'); @@ -16,7 +16,7 @@ const infoToml = path.join(sealpackDir, 'info.toml'); const src = fs.readFileSync(staticConfig, 'utf8'); const m = src.match(/VERSION\s*=\s*["']([^"']+)["']/); if (!m) { - console.error('未能在 src/config/static_config.ts 中找到 VERSION'); + console.error('未能在 src/config/static_config/meta.ts 中找到 VERSION'); process.exit(1); } const version = m[1]; diff --git a/sealpack/info.toml b/sealpack/info.toml index 30a28cf..2e0406e 100644 --- a/sealpack/info.toml +++ b/sealpack/info.toml @@ -2,7 +2,7 @@ format_version = "1.0.0" # 包基础信息。id 必须使用 namespace/package 格式;version 必须是 semver, -# 与插件版本(src/config/static_config.ts 的 VERSION)保持一致, +# 与插件版本(src/config/static_config/meta.ts 的 VERSION)保持一致, # 构建时会由 scripts/prepare-sealpack.js 自动同步。 [package] id = "error2913/aiplugin4" diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 9eedbfa..c3e77d5 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -21,9 +21,6 @@ import { revive, TypeDescriptor } from "../utils/utils"; import { AgentRunContext } from "./run_context"; import { streamService } from "./stream"; -// 单次对话中允许的连续工具调用轮次上限(防止模型无限调用工具) -export const MAX_TOOL_TURNS = 10; - export default class Agent { static validKeysMap: { [key in keyof Agent]?: TypeDescriptor } = { sessionService: SessionService, @@ -94,7 +91,7 @@ export default class Agent { /** * 标准智能体编排:构建消息 → 请求模型 → 执行工具调用(函数调用/提示词工程)→ 回填上下文 → - * 循环直到模型给出最终回复(带轮次上限),最后统一拆分并发送回复。 + * 循环直到模型给出最终回复(工具轮数由「允许连续调用函数次数」配置控制,0 为不限制),最后统一拆分并发送回复。 */ async run(session: Session, ctx: seal.MsgContext, msg: seal.Message, tool_choice?: string): Promise { if (!(await requestLimiter.acquire())) return; @@ -112,7 +109,6 @@ export default class Agent { let result: { contextArray: string[], replyArray: string[], images: Image[] } = { contextArray: [], replyArray: [], images: [] }; const MaxRetry = 3; - let toolTurn = 0; for (let retry = 1; retry <= MaxRetry; retry++) { trace.beginTurn(); @@ -129,10 +125,6 @@ export default class Agent { if (PROMPT_ENGINEERING) { const match = promptCallMatch; if (match) { - if (toolTurn >= MAX_TOOL_TURNS) { - logger.warning(`工具调用轮次超限(${MAX_TOOL_TURNS}),停止继续调用`); - break; - } logger.info('prompt tool call triggered'); const { contextArray, replyArray, images } = result; await session.reply(ctx, msg, contextArray, replyArray, images, { withSegmentDelay: true }); @@ -153,16 +145,11 @@ export default class Agent { Logger.exception('handlePromptToolCalls error', e); trace.recordToolCall('prompt-call', Date.now() - callTime, false, e instanceof Error ? e.message : String(e)); } - toolTurn++; retry = 0; continue; } } else { if (tool_calls.length > 0) { - if (toolTurn >= MAX_TOOL_TURNS) { - logger.warning(`工具调用轮次超限(${MAX_TOOL_TURNS}),停止继续调用`); - break; - } logger.info('tool call triggered'); const { contextArray, replyArray, images } = result; await session.reply(ctx, msg, contextArray, replyArray, images, { withSegmentDelay: true }); @@ -183,7 +170,6 @@ export default class Agent { Logger.exception('handleToolCalls error', e); trace.recordToolCall('function-call', Date.now() - callTime, false, e instanceof Error ? e.message : String(e)); } - toolTurn++; retry = 0; continue; } @@ -208,7 +194,7 @@ export default class Agent { logger.info(`[run] ${trace.summary()}`); } - /** 流式编排:与 run() 同层的流式循环(start → poll → 工具调用 → 递归续流),带轮次上限 */ + /** 流式编排:与 run() 同层的流式循环(start → poll → 工具调用 → 递归续流),工具轮数由配置控制 */ async runStream(session: Session, ctx: seal.MsgContext, msg: seal.Message): Promise { if (!(await requestLimiter.acquire())) return; try { @@ -221,7 +207,6 @@ export default class Agent { private async runStreamInner(session: Session, ctx: seal.MsgContext, msg: seal.Message): Promise { const { STATUS, PROMPT_ENGINEERING } = Config.tool; const trace = new AgentRunContext(); - let turns = 0; await session.stopCurrentChatStream(); @@ -282,11 +267,6 @@ export default class Agent { try { trace.recordToolCall('stream-tool-call', 0, true); - turns++; - if (turns >= MAX_TOOL_TURNS) { - logger.warning(`工具调用轮次超限(${MAX_TOOL_TURNS}),停止继续调用`); - return; - } const callResults = await ToolRunner.executePromptCalls(ctx, msg, session, match[1]); for (const r of callResults) { if (r.callBack !== false) await session.context.addToolCallbackMessage(r.content, r.tool_call_id, r.toolName, r.searchTarget); diff --git a/src/cmd/root_cmd.ts b/src/cmd/root_cmd.ts index 302c0a0..df3baa0 100644 --- a/src/cmd/root_cmd.ts +++ b/src/cmd/root_cmd.ts @@ -98,6 +98,15 @@ export function registerCmd() { const subCmd = aliasToCmd(cmdArgs.getArgN(1)); if (Object.prototype.hasOwnProperty.call(SubCmd.map, aliasToCmd(subCmd))) { + // .ai <子指令> help:查看对应子命令帮助(不校验权限,与 .ai help <子指令> 一致); + // 带更多参数时(如 .ai tool help <函数名>)交由子命令自身处理 + const subHelpArg = aliasToCmd(cmdArgs.getArgN(2)); + if (subHelpArg === 'help' && !cmdArgs.getArgN(3)) { + const helpCmd = SubCmd.map[subCmd]; + seal.replyToSender(ctx, msg, `【.ai ${helpCmd.name}】${helpCmd.desc}${helpCmd.help ? `\n${helpCmd.help}` : ''}`); + return ret; + } + const uid = ctx.player!.userId; const gid = ctx.group!.groupId; const epId = ctx.endPoint.userId; diff --git a/src/cmd/sub_cmd/block.ts b/src/cmd/sub_cmd/block.ts index 560f770..a931482 100644 --- a/src/cmd/sub_cmd/block.ts +++ b/src/cmd/sub_cmd/block.ts @@ -7,7 +7,12 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdBlock() { const cmd = new SubCmd('block'); cmd.desc = '黑名单相关操作'; - cmd.help = ''; + cmd.help = `帮助: +【.ai block add <统一ID/@xxx> <原因>】添加黑名单 +【.ai block rm <统一ID/@xxx>】移除黑名单 +【.ai block list】查看黑名单列表 + +被拉黑的对象无法触发AI对话`; cmd.priv = { priv: M, args: { add: { priv: M }, diff --git a/src/cmd/sub_cmd/forget.ts b/src/cmd/sub_cmd/forget.ts index b715775..8a02a38 100644 --- a/src/cmd/sub_cmd/forget.ts +++ b/src/cmd/sub_cmd/forget.ts @@ -6,7 +6,10 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdForget() { const cmd = new SubCmd('forget'); cmd.desc = '遗忘上下文'; - cmd.help = ''; + cmd.help = `帮助: +【.ai forget】清除全部上下文 +【.ai forget user】仅清除用户消息 +【.ai forget assistant】清除 AI 回复与工具调用记录`; cmd.priv = { priv: I, args: { assistant: { priv: U }, diff --git a/src/cmd/sub_cmd/ignore.ts b/src/cmd/sub_cmd/ignore.ts index addb776..000bd03 100644 --- a/src/cmd/sub_cmd/ignore.ts +++ b/src/cmd/sub_cmd/ignore.ts @@ -6,7 +6,12 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdIgnore() { const cmd = new SubCmd('ignore'); cmd.desc = '忽略名单相关操作'; - cmd.help = ''; + cmd.help = `帮助: + 【.ai ign add @xxx】添加忽略名单 + 【.ai ign rm @xxx】移除忽略名单 + 【.ai ign lst】列出忽略名单 + + 忽略名单中的对象仍能正常对话,但无法被选中QQ号`; cmd.priv = { priv: U, args: { add: { priv: U }, diff --git a/src/cmd/sub_cmd/image.ts b/src/cmd/sub_cmd/image.ts index d998e45..bd0d9ca 100644 --- a/src/cmd/sub_cmd/image.ts +++ b/src/cmd/sub_cmd/image.ts @@ -8,7 +8,10 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdImage() { const cmd = new SubCmd('image'); cmd.desc = '图片相关操作'; - cmd.help = ''; + cmd.help = `帮助: + 【.ai img list [lcl]】展示本地图片 + 【.ai img itt [图片] (附加提示词)】图片转文字 + 【.ai img find <图片ID>】查找图片`; cmd.priv = { priv: U, args: { list: { diff --git a/src/cmd/sub_cmd/kb.ts b/src/cmd/sub_cmd/kb.ts index 78a2bab..c178941 100644 --- a/src/cmd/sub_cmd/kb.ts +++ b/src/cmd/sub_cmd/kb.ts @@ -7,7 +7,10 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdKB() { const cmd = new SubCmd('kb'); cmd.desc = '知识库相关操作'; - cmd.help = ''; + cmd.help = `帮助: + 【.ai kb list】列出知识库条目索引 + 【.ai kb search <关键词>】搜索知识库 + 【.ai kb read 】按 ID 读取知识库条目`; cmd.priv = { priv: U, args: { list: { priv: U }, diff --git a/src/cmd/sub_cmd/memory.ts b/src/cmd/sub_cmd/memory.ts index a9c1e80..747fcd0 100644 --- a/src/cmd/sub_cmd/memory.ts +++ b/src/cmd/sub_cmd/memory.ts @@ -9,7 +9,16 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdMemory() { const cmd = new SubCmd('memory'); cmd.desc = '记忆相关操作'; - cmd.help = ''; + cmd.help = `帮助: + 【.ai memo status (@xxx)】查看记忆状态,@为查看个人记忆状态 + 【.ai memo [p/g] st <内容>】设置个人/群聊设定 + 【.ai memo [p/g] st clr】清除个人/群聊设定 + 【.ai memo [p/g] del --关键词1 --关键词2】删除个人/群聊记忆 + 【.ai memo [p/g/short] list】展示个人/群聊/短期记忆 + 【.ai memo [p/g/short] clr】清除个人/群聊/短期记忆 + 【.ai memo short [on/off]】开启/关闭短期记忆 + 【.ai memo sum】立即总结一次短期记忆 + 【.ai memo sum clr】清除总结记忆`; cmd.priv = { priv: U, args: { status: { priv: U }, diff --git a/src/cmd/sub_cmd/off.ts b/src/cmd/sub_cmd/off.ts index 3b096ce..18134cc 100644 --- a/src/cmd/sub_cmd/off.ts +++ b/src/cmd/sub_cmd/off.ts @@ -6,7 +6,9 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdOff() { const cmd = new SubCmd('off'); cmd.desc = '关闭AI(含非指令正则触发)'; - cmd.help = ''; + cmd.help = `帮助: +【.ai off】关闭 AI 及全部触发模式 +【.ai off --<参数>】按模式关闭(--r 正则 / --c 计数器 / --t 计时器 / --p 概率 / --a 活跃时间段)`; cmd.priv = { priv: I }; cmd.solve = (scc: SubCmdContext) => { const { ctx, msg, cmdArgs, sid, session, ret } = scc; diff --git a/src/cmd/sub_cmd/prompt.ts b/src/cmd/sub_cmd/prompt.ts index 910260e..e787dd5 100644 --- a/src/cmd/sub_cmd/prompt.ts +++ b/src/cmd/sub_cmd/prompt.ts @@ -7,7 +7,8 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdPrompt() { const cmd = new SubCmd('prompt'); cmd.desc = '查看system prompt'; - cmd.help = ''; + cmd.help = `帮助: +【.ai prompt】查看当前 system prompt,内容过长时仅记录到日志`; cmd.priv = { priv: M }; cmd.solve = async (scc: SubCmdContext) => { const { ctx, msg, session, ret } = scc; diff --git a/src/cmd/sub_cmd/role.ts b/src/cmd/sub_cmd/role.ts index 3b322d2..26abf22 100644 --- a/src/cmd/sub_cmd/role.ts +++ b/src/cmd/sub_cmd/role.ts @@ -7,7 +7,9 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdRole() { const cmd = new SubCmd('role'); cmd.desc = '切换角色设定'; - cmd.help = ''; + cmd.help = `帮助: +【.ai role】查看当前角色设定 +【.ai role <名称>】切换角色设定(以 . 开头的名称为隐藏角色,不显示在列表中)`; cmd.priv = { priv: I }; cmd.solve = (scc: SubCmdContext) => { const { ctx, msg, cmdArgs, ret } = scc; diff --git a/src/cmd/sub_cmd/shut.ts b/src/cmd/sub_cmd/shut.ts index 7177606..ac6c0fb 100644 --- a/src/cmd/sub_cmd/shut.ts +++ b/src/cmd/sub_cmd/shut.ts @@ -5,7 +5,8 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdShut() { const cmd = new SubCmd('shut'); cmd.desc = '打断当前对话'; - cmd.help = ''; + cmd.help = `帮助: +【.ai shut】打断当前正在进行的对话`; cmd.priv = { priv: U }; cmd.solve = async (scc: SubCmdContext) => { const { ctx, msg, session, ret } = scc; diff --git a/src/cmd/sub_cmd/standby.ts b/src/cmd/sub_cmd/standby.ts index 795ae80..3f72af0 100644 --- a/src/cmd/sub_cmd/standby.ts +++ b/src/cmd/sub_cmd/standby.ts @@ -6,7 +6,8 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdStandby() { const cmd = new SubCmd('standby'); cmd.desc = '开启待机模式:记录聊天内容、不主动发言(非指令关键词触发仍可发言)'; - cmd.help = ''; + cmd.help = `帮助: +【.ai standby】开启待机模式:记录聊天内容、不主动发言(非指令关键词触发仍可发言)`; cmd.priv = { priv: I }; cmd.solve = (scc: SubCmdContext) => { const { ctx, msg, sid, session, ret } = scc; diff --git a/src/cmd/sub_cmd/status.ts b/src/cmd/sub_cmd/status.ts index 97c0286..7cee279 100644 --- a/src/cmd/sub_cmd/status.ts +++ b/src/cmd/sub_cmd/status.ts @@ -5,7 +5,8 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdStatus() { const cmd = new SubCmd('status'); cmd.desc = '查看当前AI状态'; - cmd.help = ''; + cmd.help = `帮助: +【.ai status】查看当前会话 AI 状态(权限/上下文轮数/各触发模式)`; cmd.priv = { priv: U }; cmd.solve = (scc: SubCmdContext) => { const { ctx, msg, sid, session, ret } = scc; diff --git a/src/cmd/sub_cmd/token.ts b/src/cmd/sub_cmd/token.ts index 42f2126..33cd2e7 100644 --- a/src/cmd/sub_cmd/token.ts +++ b/src/cmd/sub_cmd/token.ts @@ -8,7 +8,15 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdToken() { const cmd = new SubCmd('token'); cmd.desc = 'token相关操作'; - cmd.help = ''; + cmd.help = `帮助: + 【.ai tk lst】查看所有模型 + 【.ai tk sum】查看所有模型的token使用记录总和 + 【.ai tk all】查看所有模型的token使用记录 + 【.ai tk [y/m] (chart)】查看所有模型今年/这个月的token使用记录 + 【.ai tk <模型名称>】查看模型的token使用记录 + 【.ai tk <模型名称> [y/m] (chart)】查看模型今年/这个月的token使用记录 + 【.ai tk clr】清除token使用记录 + 【.ai tk clr <模型名称>】清除token使用记录`; cmd.priv = { priv: S, args: { list: { priv: U }, @@ -398,4 +406,4 @@ export function registerCmdToken() { } } } -} \ No newline at end of file +} diff --git a/src/cmd/sub_cmd/tool.ts b/src/cmd/sub_cmd/tool.ts index 054bdff..4bb13bf 100644 --- a/src/cmd/sub_cmd/tool.ts +++ b/src/cmd/sub_cmd/tool.ts @@ -10,7 +10,11 @@ import { SubCmd, SubCmdContext } from "../root_cmd"; export function registerCmdTool() { const cmd = new SubCmd('tool'); cmd.desc = '工具相关操作'; - cmd.help = ''; + cmd.help = `帮助: + 【.ai tool】列出所有工具 + 【.ai tool [on/off] <函数名>】开启或关闭工具函数 + 【.ai tool help <函数名>】查看工具详情 + 【.ai tool call <函数名> --参数名=具体参数】试用工具函数`; cmd.priv = { priv: U, args: { on: { priv: I }, diff --git a/src/config/config.ts b/src/config/config.ts index 5a83350..0c26531 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -6,7 +6,6 @@ import ImageConfig from "./configs/image"; import MemoryConfig from "./configs/memory"; import MessageConfig from "./configs/message"; import ModelConfig from "./configs/model"; -import PromptConfig from "./configs/prompt"; import ReceivedConfig from "./configs/received"; import ReplyConfig from "./configs/reply"; import ResourceConfig from "./configs/resource"; @@ -33,7 +32,6 @@ const configMap = { memory: MemoryConfig, image: ImageConfig, backend: BackendConfig, - prompt: PromptConfig, resource: ResourceConfig, } as const; diff --git a/src/config/configs/image.ts b/src/config/configs/image.ts index babe35c..53c902d 100644 --- a/src/config/configs/image.ts +++ b/src/config/configs/image.ts @@ -13,15 +13,13 @@ export default class ImageConfig { static register() { seal.ext.registerStringConfig(ext, "图片全局识别豹语条件", '0', "0 不自动识别;1 所有图片自动识别转文字;也可填豹语表达式(如 $t群号_RAW=='2001')按群启用", "图片"); seal.ext.registerOptionConfig(ext, "识别图片时将url转换为base64", "永不", ["永不", "自动", "总是"], "解决大模型无法正常获取QQ图床图片的问题", "图片"); - seal.ext.registerStringConfig(ext, "图片识别默认prompt", "请帮我用简短的语言概括这张图片的特征,包括图片类型、场景、主题、主体等信息,如果有文字,请全部输出", "图片转文字使用的提示词,留空使用内置默认", "图片"); } static get() { return { IMAGE_CONDITION: seal.ext.getStringConfig(ext, "图片全局识别豹语条件"), URL_TO_BASE64: seal.ext.getOptionConfig(ext, "识别图片时将url转换为base64"), - LOCAL_IMAGE_PATH_MAP: ImageConfig.getPathMapConfig(ext, "本地图片路径"), - IMAGE_DEFAULT_PROMPT: seal.ext.getStringConfig(ext, "图片识别默认prompt") + LOCAL_IMAGE_PATH_MAP: ImageConfig.getPathMapConfig(ext, "本地图片路径") } } } diff --git a/src/config/configs/model.ts b/src/config/configs/model.ts index e2333bd..f535a32 100644 --- a/src/config/configs/model.ts +++ b/src/config/configs/model.ts @@ -38,10 +38,10 @@ use = ["image-understanding"] # 必填,用途:image-understanding / ch provider = "zhipu" # 可选,服务商,省略时自动识别 base_url = "https://open.bigmodel.cn/api/paas/v4" # 可选,API 地址,省略时取服务商默认 -[body] # 可选,请求参数覆盖;默认 max_tokens=4096、stop=null、stream=false +[body] # 可选,请求参数覆盖;默认 max_tokens=2048、stop=null、stream=false temperature = 1 # 可选 -max_tokens = 4096 # 可选` - ], '每行一个图片模型(TOML)。必填:name(模型名)、api_key(API 密钥)、use(用途)。可选:provider(服务商,省略时按模型名自动识别:zhipu/alibaba/openai/google/siliconflow)、base_url(API 地址,省略时按服务商取默认)、body(请求参数覆盖)。use 可选值:image-understanding(图片理解/图片转文字)、chat(多模态对话:把该模型同时当作对话模型,用 .ai model 选择它后,上下文里的图片会以图片内容直接传给模型,不再转成文本标签;对话模型名字出现在本列表时同样按多模态处理)。body 未配置时使用 max_tokens=4096、stop=null、stream=false。下方默认值即完整示例,可直接修改。', "模型"); +max_tokens = 2048 # 可选` + ], '每行一个图片模型(TOML)。必填:name(模型名)、api_key(API 密钥)、use(用途)。可选:provider(服务商,省略时按模型名自动识别:zhipu/alibaba/openai/google/siliconflow)、base_url(API 地址,省略时按服务商取默认)、body(请求参数覆盖)。use 可选值:image-understanding(图片理解/图片转文字)、chat(多模态对话:把该模型同时当作对话模型,用 .ai model 选择它后,上下文里的图片会以图片内容直接传给模型,不再转成文本标签;对话模型名字出现在本列表时同样按多模态处理)。body 未配置时使用 max_tokens=2048、stop=null、stream=false。下方默认值即完整示例,可直接修改。', "模型"); seal.ext.registerBoolConfig(ext, "是否开启嵌入模型", false, "总开关,默认关闭。\n配置好嵌入模型后再开启;关闭时向量记忆的嵌入生成与检索不生效,记忆按关键词/分数检索", "模型"); seal.ext.registerTemplateConfig(ext, "嵌入模型", [ `# 使用toml格式 diff --git a/src/config/static_config/alias.ts b/src/config/static_config/alias.ts new file mode 100644 index 0000000..d9c02e5 --- /dev/null +++ b/src/config/static_config/alias.ts @@ -0,0 +1,26 @@ +export const ALIAS_MAP: { [key: string]: string } = { + "AI": "ai", + "priv": "privilege", + "ses": "session", + "st": "set", + "ck": "check", + "clr": "clear", + "sb": "standby", + "fgt": "forget", + "f": "forget", + "ass": "assistant", + "img": "image", + "memo": "memory", + "p": "private", + "g": "group", + "del": "delete", + "ign": "ignore", + "rm": "remove", + "lst": "list", + "tk": "token", + "y": "year", + "m": "month", + "lcl": "local", + "ran": "random", + "nick": "nickname" +} diff --git a/src/config/static_config/cq.ts b/src/config/static_config/cq.ts new file mode 100644 index 0000000..e8cab02 --- /dev/null +++ b/src/config/static_config/cq.ts @@ -0,0 +1 @@ +export const CQ_TYPES_ALLOW = ["at", "image", "reply", "face", "poke"]; diff --git a/src/config/static_config.ts b/src/config/static_config/face.ts similarity index 55% rename from src/config/static_config.ts rename to src/config/static_config/face.ts index e42a673..96d9a53 100644 --- a/src/config/static_config.ts +++ b/src/config/static_config/face.ts @@ -1,78 +1,3 @@ -// 静态常量:版本/作者/模型映射表/权限等级/别名/表情表等 -export const VERSION = "4.15.0"; -export const AUTHOR = "baiyu&错误"; -export const NAME = "aiplugin4"; - -export const CONFIG_CACHE_TTL = 60000; -// 存储结构版本:存档结构变更时递增,用于后续数据迁移判断 -export const STORAGE_VERSION = 1; - -export const CQ_TYPES_ALLOW = ["at", "image", "reply", "face", "poke"]; - -export const PRIVILEGE_LEVEL_MAP = { - "master": 100, - "whitelist": 70, - "owner": 60, - "admin": 50, - "inviter": 40, - "user": 0, - "blacklist": -30 -} - -export const HELP_MAP = { - "ID": `: -【QQ:1234567890】 私聊窗口 -【QQ-Group:1234】 群聊窗口 -【now】当前窗口`, - "会话权限": `<会话权限>:任意数字,越大权限越高`, - "指令": `<指令>:指令名称和参数,多个指令用-连接,如ai-sb`, - "权限限制": `<权限限制>:数字0-数字1-数字2,如0-0-0,含义如下: -0: 会话所需权限, 1: 会话检查通过后用户所需权限, 2: 强行触发指令用户所需权限, 进行检查时若通过0和1则无需检查2 -【-30】黑名单用户 -【0】普通用户 -【40】邀请者 -【50】群管理员 -【60】群主 -【70】白名单用户 -【100】骰主`, - "参数": `<参数>: -【c】计数器模式,接收消息数达到后触发 -单位/条,默认10条 -【t】计时器模式,最后一条消息后达到时限触发 -单位/秒,默认60秒 -【p】概率模式,每条消息按概率触发 -单位/%,默认10% -【a】活跃时间段和活跃次数 -格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5")` -} - -export const ALIAS_MAP: { [key: string]: string } = { - "AI": "ai", - "priv": "privilege", - "ses": "session", - "st": "set", - "ck": "check", - "clr": "clear", - "sb": "standby", - "fgt": "forget", - "f": "forget", - "ass": "assistant", - "img": "image", - "memo": "memory", - "p": "private", - "g": "group", - "del": "delete", - "ign": "ignore", - "rm": "remove", - "lst": "list", - "tk": "token", - "y": "year", - "m": "month", - "lcl": "local", - "ran": "random", - "nick": "nickname" -} - export const FACE_MAP: { [key: string]: string } = { "0": "惊讶", "1": "撇嘴", @@ -371,69 +296,3 @@ export const FACE_MAP: { [key: string]: string } = { "431": "蛇尾", "432": "灵蛇献瑞" } - -export const PROVIDER_MAP: { [provider: string]: string } = { - "deepseek": "https://api.deepseek.com/v1", - "zhipu": "https://open.bigmodel.cn/api/paas/v4", - "alibaba": "https://dashscope.aliyuncs.com/compatible-mode/v1", - "openai": "https://api.openai.com/v1", - "google": "https://generativelanguage.googleapis.com/v1beta/openai", - "anthropic": "https://api.anthropic.com/v1", - "moonshot": "https://api.moonshot.cn/v1", - "xai": "https://api.x.ai/v1", - "mistral": "https://api.mistral.ai/v1", - "siliconflow": "https://api.siliconflow.cn/v1" -} - -export const CHAT_MODEL_MAP = { - "deepseek": ["deepseek-chat", "deepseek-reasoner", "deepseek-v4-pro", "deepseek-v4-flash"], - "openai": ["gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini", "gpt-5", "gpt-5-mini", "o4-mini"], - "google": ["gemini-3-pro-preview-low", "gemini-3-flash", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"], - "zhipu": ["glm-4", "glm-4-flash", "glm-4.5", "glm-4.7-flash"], - "alibaba": ["qwen-max", "qwen-plus", "qwen-turbo", "qwen3-max", "qwen3.5-plus", "qwen3.5-flash"], - "anthropic": ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5"], - "moonshot": ["kimi-k2-0905-preview", "moonshot-v1-32k"], - "xai": ["grok-3", "grok-4"], - "mistral": ["mistral-large-latest", "mistral-small-latest"], - "siliconflow": ["deepseek-ai/DeepSeek-V3.2", "Qwen/Qwen3-235B-A22B-Instruct"] -}; -export const CHAT_MODEL_TO_PROVIDER = Object.entries(CHAT_MODEL_MAP).reduce((acc, [provider, models]) => { - models.forEach(model => acc[model] = provider); - return acc; -}, {} as { [model: string]: string }); -export const IMAGE_MODEL_MAP = { - "zhipu": ["glm-4v", "glm-4v-plus-0111", "glm-4v-flash", "glm-4.6v"], - "alibaba": ["qwen-vl-max", "qwen-vl-plus", "qwen2.5-vl-72b-instruct"], - "openai": ["gpt-4o", "gpt-4o-mini", "gpt-4.1"], - "google": ["gemini-2.5-pro", "gemini-2.5-flash"], - "siliconflow": ["Qwen/Qwen2.5-VL-72B-Instruct", "THUDM/GLM-4.6V-9B"] -}; -export const IMAGE_MODEL_TO_PROVIDER = Object.entries(IMAGE_MODEL_MAP).reduce((acc, [provider, models]) => { - models.forEach(model => acc[model] = provider); - return acc; -}, {} as { [model: string]: string }); -export const EMBEDDING_MODEL_MAP = { - "alibaba": ["text-embedding-v4", "text-embedding-v3", "text-embedding-v2"], - "openai": ["text-embedding-3-large", "text-embedding-3-small", "text-embedding-ada-002"], - "zhipu": ["embedding-3"], - "siliconflow": ["BAAI/bge-m3", "BAAI/bge-large-zh-v1.5", "Qwen/Qwen3-Embedding-0.6B"] -}; -export const EMBEDDING_MODEL_TO_PROVIDER = Object.entries(EMBEDDING_MODEL_MAP).reduce((acc, [provider, models]) => { - models.forEach(model => acc[model] = provider); - return acc; -}, {} as { [model: string]: string }); - -export const DEFAULT_CHAT_MODEL_BODY = { - "max_tokens": 8192, - "stop": null, - "stream": false -} -export const DEFAULT_IMAGE_MODEL_BODY = { - "max_tokens": 4096, - "stop": null, - "stream": false -} -export const DEFAULT_EMBEDDING_MODEL_BODY = { - "encoding_format": "float", - "dimensions": 1024 -} diff --git a/src/config/static_config/help.ts b/src/config/static_config/help.ts new file mode 100644 index 0000000..0fcf299 --- /dev/null +++ b/src/config/static_config/help.ts @@ -0,0 +1,26 @@ +export const HELP_MAP = { + "ID": `: +【QQ:1234567890】 私聊窗口 +【QQ-Group:1234】 群聊窗口 +【now】当前窗口`, + "会话权限": `<会话权限>:任意数字,越大权限越高`, + "指令": `<指令>:指令名称和参数,多个指令用-连接,如ai-sb`, + "权限限制": `<权限限制>:数字0-数字1-数字2,如0-0-0,含义如下: +0: 会话所需权限, 1: 会话检查通过后用户所需权限, 2: 强行触发指令用户所需权限, 进行检查时若通过0和1则无需检查2 +【-30】黑名单用户 +【0】普通用户 +【40】邀请者 +【50】群管理员 +【60】群主 +【70】白名单用户 +【100】骰主`, + "参数": `<参数>: +【c】计数器模式,接收消息数达到后触发 +单位/条,默认10条 +【t】计时器模式,最后一条消息后达到时限触发 +单位/秒,默认60秒 +【p】概率模式,每条消息按概率触发 +单位/%,默认10% +【a】活跃时间段和活跃次数 +格式为"开始时间-结束时间-活跃次数"(如"09:00-18:00-5")` +} diff --git a/src/config/static_config/index.ts b/src/config/static_config/index.ts new file mode 100644 index 0000000..515e09e --- /dev/null +++ b/src/config/static_config/index.ts @@ -0,0 +1,10 @@ +// 静态常量汇总:各分类模块统一从这里 re-export,保持历史导入路径(../config/static_config)不变 +export * from "./meta"; +export * from "./cq"; +export * from "./permission"; +export * from "./help"; +export * from "./alias"; +export * from "./face"; +export * from "./providers"; +export * from "./model_defaults"; +export * from "./memory"; diff --git a/src/config/static_config/memory.ts b/src/config/static_config/memory.ts new file mode 100644 index 0000000..2332860 --- /dev/null +++ b/src/config/static_config/memory.ts @@ -0,0 +1,2 @@ +// 记忆检索内置常量(原「默认向量相似度」配置,改为硬编码) +export const VECTOR_SIMILARITY = 0.8; diff --git a/src/config/static_config/meta.ts b/src/config/static_config/meta.ts new file mode 100644 index 0000000..b6259eb --- /dev/null +++ b/src/config/static_config/meta.ts @@ -0,0 +1,8 @@ +// 静态常量:版本/作者/模型映射表/权限等级/别名/表情表等 +export const VERSION = "4.15.0"; +export const AUTHOR = "baiyu&错误"; +export const NAME = "aiplugin4"; + +export const CONFIG_CACHE_TTL = 60000; +// 存储结构版本:存档结构变更时递增,用于后续数据迁移判断 +export const STORAGE_VERSION = 1; diff --git a/src/config/static_config/model_defaults.ts b/src/config/static_config/model_defaults.ts new file mode 100644 index 0000000..3f9db18 --- /dev/null +++ b/src/config/static_config/model_defaults.ts @@ -0,0 +1,14 @@ +export const DEFAULT_CHAT_MODEL_BODY = { + "max_tokens": 8192, + "stop": null, + "stream": false +} +export const DEFAULT_IMAGE_MODEL_BODY = { + "max_tokens": 2048, + "stop": null, + "stream": false +} +export const DEFAULT_EMBEDDING_MODEL_BODY = { + "encoding_format": "float", + "dimensions": 1024 +} diff --git a/src/config/static_config/permission.ts b/src/config/static_config/permission.ts new file mode 100644 index 0000000..9123dda --- /dev/null +++ b/src/config/static_config/permission.ts @@ -0,0 +1,9 @@ +export const PRIVILEGE_LEVEL_MAP = { + "master": 100, + "whitelist": 70, + "owner": 60, + "admin": 50, + "inviter": 40, + "user": 0, + "blacklist": -30 +} diff --git a/src/config/static_config/providers.ts b/src/config/static_config/providers.ts new file mode 100644 index 0000000..97adc95 --- /dev/null +++ b/src/config/static_config/providers.ts @@ -0,0 +1,50 @@ +export const PROVIDER_MAP: { [provider: string]: string } = { + "deepseek": "https://api.deepseek.com/v1", + "zhipu": "https://open.bigmodel.cn/api/paas/v4", + "alibaba": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "openai": "https://api.openai.com/v1", + "google": "https://generativelanguage.googleapis.com/v1beta/openai", + "anthropic": "https://api.anthropic.com/v1", + "moonshot": "https://api.moonshot.cn/v1", + "xai": "https://api.x.ai/v1", + "mistral": "https://api.mistral.ai/v1", + "siliconflow": "https://api.siliconflow.cn/v1" +} + +export const CHAT_MODEL_MAP = { + "deepseek": ["deepseek-chat", "deepseek-reasoner", "deepseek-v4-pro", "deepseek-v4-flash"], + "openai": ["gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini", "gpt-5", "gpt-5-mini", "o4-mini"], + "google": ["gemini-3-pro-preview-low", "gemini-3-flash", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"], + "zhipu": ["glm-4", "glm-4-flash", "glm-4.5", "glm-4.7-flash"], + "alibaba": ["qwen-max", "qwen-plus", "qwen-turbo", "qwen3-max", "qwen3.5-plus", "qwen3.5-flash"], + "anthropic": ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5"], + "moonshot": ["kimi-k2-0905-preview", "moonshot-v1-32k"], + "xai": ["grok-3", "grok-4"], + "mistral": ["mistral-large-latest", "mistral-small-latest"], + "siliconflow": ["deepseek-ai/DeepSeek-V3.2", "Qwen/Qwen3-235B-A22B-Instruct"] +}; +export const CHAT_MODEL_TO_PROVIDER = Object.entries(CHAT_MODEL_MAP).reduce((acc, [provider, models]) => { + models.forEach(model => acc[model] = provider); + return acc; +}, {} as { [model: string]: string }); +export const IMAGE_MODEL_MAP = { + "zhipu": ["glm-4v", "glm-4v-plus-0111", "glm-4v-flash", "glm-4.6v"], + "alibaba": ["qwen-vl-max", "qwen-vl-plus", "qwen2.5-vl-72b-instruct"], + "openai": ["gpt-4o", "gpt-4o-mini", "gpt-4.1"], + "google": ["gemini-2.5-pro", "gemini-2.5-flash"], + "siliconflow": ["Qwen/Qwen2.5-VL-72B-Instruct", "THUDM/GLM-4.6V-9B"] +}; +export const IMAGE_MODEL_TO_PROVIDER = Object.entries(IMAGE_MODEL_MAP).reduce((acc, [provider, models]) => { + models.forEach(model => acc[model] = provider); + return acc; +}, {} as { [model: string]: string }); +export const EMBEDDING_MODEL_MAP = { + "alibaba": ["text-embedding-v4", "text-embedding-v3", "text-embedding-v2"], + "openai": ["text-embedding-3-large", "text-embedding-3-small", "text-embedding-ada-002"], + "zhipu": ["embedding-3"], + "siliconflow": ["BAAI/bge-m3", "BAAI/bge-large-zh-v1.5", "Qwen/Qwen3-Embedding-0.6B"] +}; +export const EMBEDDING_MODEL_TO_PROVIDER = Object.entries(EMBEDDING_MODEL_MAP).reduce((acc, [provider, models]) => { + models.forEach(model => acc[model] = provider); + return acc; +}, {} as { [model: string]: string }); diff --git a/src/memory/manager.ts b/src/memory/manager.ts index 3f3c2b1..7d825e6 100644 --- a/src/memory/manager.ts +++ b/src/memory/manager.ts @@ -30,8 +30,8 @@ export class MemoryManager { } /** 写入记忆:统一入口(内部含去重合并与向量生成) */ - static async addMemory(ctx: seal.MsgContext | null, session: Session, uiList: UserInfo[], giList: GroupInfo[], keywords: string[], images: Image[], text: string) { - return session.memory.addMemory(ctx, session, uiList, giList, keywords, images, text); + static async addMemory(ctx: seal.MsgContext | null, session: Session, uiList: UserInfo[], giList: GroupInfo[], keywords: string[], images: Image[], text: string, visibility: 'public' | 'private' = 'public') { + return session.memory.addMemory(ctx, session, uiList, giList, keywords, images, text, visibility); } /** 检索记忆:统一入口 */ diff --git a/src/memory/memory.ts b/src/memory/memory.ts index 0f38274..bdb206f 100644 --- a/src/memory/memory.ts +++ b/src/memory/memory.ts @@ -1,9 +1,11 @@ // 记忆服务:MemoryItem 存取/检索/权重/短期记忆(含旧格式迁移) import Agent from "../agent/agent"; import Config from "../config/config"; +import { VECTOR_SIMILARITY } from "../config/static_config"; import type { Context } from "../context/context"; import Logger from "../logger"; import Model from "../model/model"; +import { MEMORY_TEMPLATE } from "../prompt/templates"; import Image from "../resource/image"; import { Session } from "../session/session"; import { GroupInfo, SessionInfo, UserInfo } from "../session/types"; @@ -13,9 +15,6 @@ import { generateId, getCommonItem, revive, TypeDescriptor } from "../utils/util import MemoryItem from "./memory_item"; import { MemorySource, searchOptions } from "./types"; -// 向量记忆检索的相似度下限(低于该值的记忆不返回),内置硬编码 -const VECTOR_SIMILARITY = 0.8; - export default class MemoryService { static validKeysMap: { [key in keyof MemoryService]?: TypeDescriptor } = { memoryMap: { array: MemoryItem }, @@ -127,12 +126,14 @@ export default class MemoryService { } } - async addMemory(_ctx: seal.MsgContext | null, session: Session, ul: UserInfo[], gl: GroupInfo[], kws: string[], images: Image[], text: string) { + async addMemory(_ctx: seal.MsgContext | null, session: Session, ul: UserInfo[], gl: GroupInfo[], kws: string[], images: Image[], text: string, visibility: 'public' | 'private' = 'public') { const id = this.generateMemoryId(); const now = Math.floor(Date.now() / 1000); const m = new MemoryItem(); m.id = id; m.sessionId = session.sessionId; + // 可见性:public 对相关会话开放;private 仅创建会话可读(search 时按调用方会话过滤) + m.visibility = visibility; // 防注入:记忆内容中的内部上下文标签(from/msg_id/system/time)直接剥离 m.content = stripInternalTags(text); m.createAt = now; @@ -351,7 +352,7 @@ export default class MemoryService { groups = [], filterUsers = [], filterGroups = [], - sessionId = '', + sessionId = (this as MemoryService & { sessionId?: string }).sessionId || '', method = 'score' } = options; @@ -499,7 +500,6 @@ export default class MemoryService { buildMemoriesPrompt(sources: MemorySource[]): string { if (sources.length === 0) return ''; const { MEMORY } = Config.memory; - const { MEMORY_TEMPLATE } = Config.prompt; return MEMORY_TEMPLATE({ "MEMORY": MEMORY, "sources": sources diff --git a/src/memory/session_memory.ts b/src/memory/session_memory.ts index 4117b5d..29220eb 100644 --- a/src/memory/session_memory.ts +++ b/src/memory/session_memory.ts @@ -2,6 +2,7 @@ import Agent from "../agent/agent"; import Config from "../config/config"; import Logger from "../logger"; +import { SUMMARY_PROMPT_TEMPLATE, SUMMARY_TEMPLATE } from "../prompt/templates"; import Group from "../session/group"; import { Session } from "../session/session"; import User from "../session/user"; @@ -65,7 +66,7 @@ export default class SessionMemoryService extends MemoryService { const groupNumber = isPrivate ? '' : sessionId.replace(/^.+:/, ''); const userName = isPrivate ? (User.get(sessionId).userName || userNumber) : ''; const groupName = isPrivate ? '' : (Group.get(sessionId).groupName || groupNumber); - const prompt = Config.prompt.SUMMARY_PROMPT_TEMPLATE({ + const prompt = SUMMARY_PROMPT_TEMPLATE({ "角色设定": roleSetting, "平台": '', "私聊": isPrivate, @@ -97,6 +98,7 @@ export default class SessionMemoryService extends MemoryService { keywords?: string[], userList?: string[], groupList?: string[], + visibility?: 'public' | 'private', }[] }; @@ -109,7 +111,7 @@ export default class SessionMemoryService extends MemoryService { this.limitSummaries(); await Promise.all(memoryData.memories.map(m => - this.addMemory(null, this.session, [], [], m.keywords || [], [], m.text) + this.addMemory(null, this.session, [], [], m.keywords || [], [], m.text, m.visibility === 'private' ? 'private' : 'public') )); } catch (e) { Logger.error('更新短期记忆失败: ' + (e instanceof Error ? e.message : String(e))); @@ -128,7 +130,6 @@ export default class SessionMemoryService extends MemoryService { buildSummaryPrompt(): string { if (this.summaries.length === 0) return ''; const { SUMMARY } = Config.memory; - const { SUMMARY_TEMPLATE } = Config.prompt; return SUMMARY_TEMPLATE({ "SUMMARY": SUMMARY, "summaries": this.summaries diff --git a/src/memory/types.ts b/src/memory/types.ts index 8061e4a..d91bac5 100644 --- a/src/memory/types.ts +++ b/src/memory/types.ts @@ -12,7 +12,7 @@ export interface searchOptions { filterUsers?: string[]; /** 严格群组过滤:非空时记忆须命中至少一个群组(groups 为宽松过滤,条目为空视为全局可见) */ filterGroups?: string[]; - /** 当前会话 ID:私有记忆仅对创建会话可见 */ + /** 调用方会话 ID:私有记忆仅对创建会话可见;缺省时取记忆服务所属会话(基类公共记忆为空) */ sessionId?: string; } export interface MemorySource { diff --git a/src/prompt/builder.ts b/src/prompt/builder.ts index fff39e8..56bbbdd 100644 --- a/src/prompt/builder.ts +++ b/src/prompt/builder.ts @@ -10,6 +10,8 @@ import { getSkillSummaries } from "../tool/skills"; import Tool from "../tool/tool"; import { stripInternalTags } from "../utils/string"; +import { SYSTEM_MESSAGE_TEMPLATE } from "./templates"; + export interface SystemPromptSection { name: string; content: string; @@ -62,7 +64,7 @@ export async function buildSystemPromptContent( // 能力段:工具函数 + 可用技能(MCP 工具已并入工具列表) const toolPrompt = STATUS && PROMPT_ENGINEERING ? Tool.getToolsInfoPrompt(session) : ''; - let content = Config.prompt.SYSTEM_MESSAGE_TEMPLATE({ + let content = SYSTEM_MESSAGE_TEMPLATE({ instruction: roleSetting, platform: ctx.endPoint.platform, sessionType: ctx.isPrivate ? 'private' : 'group', diff --git a/src/config/configs/prompt.ts b/src/prompt/templates.ts similarity index 82% rename from src/config/configs/prompt.ts rename to src/prompt/templates.ts index 70ec960..2a70c19 100644 --- a/src/config/configs/prompt.ts +++ b/src/prompt/templates.ts @@ -1,7 +1,7 @@ -// prompt 模板:内置 Handlebars 模板(不再注册为可配置项,避免用户误改导致渲染损坏) +// 内置 prompt 模板:Handlebars 模板常量(不再注册为可配置项,避免用户误改导致渲染损坏) import Handlebars from "handlebars"; -import Logger from "../../logger"; +import Logger from "../logger"; const TEMPLATES: { [key: string]: string } = { "system prompt模板": `你是一名QQ中的掷骰机器人,也称骰娘,用于线上TRPG中。你需要扮演以下角色在群聊和私聊中与人聊天。 @@ -152,14 +152,14 @@ const TEMPLATES: { [key: string]: string } = { }, "memories": { type: 'array', - description: '记忆数组。单条记忆应只有一个话题或事件。若对话内容对记忆有重要影响时返回,否则返回空数组', + description: '记忆数组。单条记忆应只有一个话题或事件。若对话内容对记忆有重要影响时返回,否则返回空数组。除非用户明确要求记忆只在本会话中生效,否则不要传 visibility 字段(默认 public)', items: { type: 'object', description: '记忆对象', properties: { "memory_type": { type: "string", - description: "记忆类型,个人或群聊。", + description: "记忆归属,个人或群聊,与可见性无关。", enum: ["private", "group"] }, "name": { @@ -190,6 +190,11 @@ const TEMPLATES: { [key: string]: string } = { items: { type: 'string' } + }, + "visibility": { + type: "string", + description: "记忆可见性,仅当用户明确要求记忆只在本会话中生效时才传 private;其余情况不传(默认 public)", + enum: ["public", "private"] } }, "required": ['memory_type', 'name', 'text'] @@ -198,23 +203,15 @@ const TEMPLATES: { [key: string]: string } = { }` }; -export default class PromptConfig { - static register() { - // prompt 模板已转为内置,不再注册配置项 - } - - static get() { - return { - SYSTEM_MESSAGE_TEMPLATE: compileTemplate("system prompt模板"), - MEMORY_TEMPLATE: compileTemplate("长期记忆prompt模板"), - SUMMARY_TEMPLATE: compileTemplate("总结记忆prompt模板"), - TOOLS_PROMPT_TEMPLATE: compileTemplate("工具函数prompt模板"), - IMAGE_PROMPT_TEMPLATE: compileTemplate("图片识别prompt模板"), - SUMMARY_PROMPT_TEMPLATE: compileTemplate("记忆总结prompt模板") - } - } -} +// 编译后的模板:模块加载即编译一次,渲染期异常不再兜底(避免掩盖模板 bug),由调用方自行处理 +export const SYSTEM_MESSAGE_TEMPLATE = compileTemplate("system prompt模板"); +export const MEMORY_TEMPLATE = compileTemplate("长期记忆prompt模板"); +export const SUMMARY_TEMPLATE = compileTemplate("总结记忆prompt模板"); +export const TOOLS_PROMPT_TEMPLATE = compileTemplate("工具函数prompt模板"); +export const IMAGE_PROMPT_TEMPLATE = compileTemplate("图片识别prompt模板"); +export const SUMMARY_PROMPT_TEMPLATE = compileTemplate("记忆总结prompt模板"); +/** 编译内置模板:仅编译期(Handlebars.compile)失败时回退空函数,渲染期异常由调用方处理 */ function compileTemplate(key: string): HandlebarsTemplateDelegate { try { return Handlebars.compile(TEMPLATES[key] || ''); diff --git a/src/resource/image.ts b/src/resource/image.ts index 456e5dd..d65fa9e 100644 --- a/src/resource/image.ts +++ b/src/resource/image.ts @@ -4,6 +4,7 @@ import { ext } from "../config/config"; import Config from "../config/config"; import { logger } from "../logger"; import Model from "../model/model"; +import { IMAGE_PROMPT_TEMPLATE } from "../prompt/templates"; import { getSessionId } from "../utils/seal"; import { MessageSegment } from "../utils/string"; import { generateId, resolveLocalPath, revive, TypeDescriptor } from "../utils/utils"; @@ -129,8 +130,8 @@ export default class Image { logger.info(`图片模型开关未开启,跳过识别: ${this.imageId}`); return; } - const { IMAGE_DEFAULT_PROMPT, URL_TO_BASE64 } = Config.image; - const defaultPrompt = Config.prompt.IMAGE_PROMPT_TEMPLATE({}) || IMAGE_DEFAULT_PROMPT; + const { URL_TO_BASE64 } = Config.image; + const defaultPrompt = IMAGE_PROMPT_TEMPLATE({}); if (URL_TO_BASE64 == '总是' && this.type === 'url') await this.urlToBase64(); diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 9e0ffb6..57dc89c 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -1,6 +1,7 @@ // 工具系统:Tool 注册表、工具调用(含扩展指令/提示词工程)与注册 import Config from "../config/config" import Logger from "../logger" +import { TOOLS_PROMPT_TEMPLATE } from "../prompt/templates" import { Session } from "../session/session"; import { SessionType } from "../session/types"; import { getSessionId } from "../utils/seal"; @@ -325,7 +326,6 @@ export default class Tool { static getToolsInfoPrompt(session: Session): string { const { PROMPT_ENGINEERING } = Config.tool; - const { TOOLS_PROMPT_TEMPLATE } = Config.prompt; const tools = this.getToolsInfo(session); let s = ''; diff --git a/src/tool/tools/memory/tool_memory.ts b/src/tool/tools/memory/tool_memory.ts index 5196e10..0762eb9 100644 --- a/src/tool/tools/memory/tool_memory.ts +++ b/src/tool/tools/memory/tool_memory.ts @@ -11,13 +11,13 @@ export function registerMemory() { type: 'function', function: { name: 'add_memory', - description: '添加个人记忆或群聊记忆,尽量不要重复记忆', + description: '添加个人记忆或群聊记忆,尽量不要重复记忆;仅当用户明确要求记忆只在本会话中生效时才传 visibility=private,其余情况不要传(默认 public,相关会话均可读取)', parameters: { type: 'object', properties: { memory_type: { type: "string", - description: "记忆类型,个人或群聊。", + description: "记忆归属,个人或群聊(决定存到哪个会话),与可见性无关。", enum: ["private", "group"] }, name: { @@ -48,6 +48,11 @@ export function registerMemory() { items: { type: 'string' } + }, + visibility: { + type: 'string', + description: '仅当用户明确要求记忆只在本会话中生效时才传 private;其余情况不要传(默认 public,相关会话均可读取)', + enum: ['public', 'private'] } }, required: ['memory_type', 'name', 'text'] @@ -55,7 +60,9 @@ export function registerMemory() { } }); toolAdd.solve = async (ctx, _, session, args) => { - const { memory_type, name, text, keywords = [], userList = [], groupList = [] } = args; + const { memory_type, name, text, keywords = [], userList = [], groupList = [], visibility = 'public' } = args; + // 规范化可见性,避免模型传入非法枚举值 + const normalizedVisibility: 'public' | 'private' = visibility === 'private' ? 'private' : 'public'; if (memory_type === "private") { const ui = await session.context.findUser(ctx, name, true); @@ -85,7 +92,7 @@ export function registerMemory() { } //记忆相关处理 - await MemoryManager.addMemory(ctx, session, uiList, giList, Array.isArray(keywords) ? keywords : [], [], text); + await MemoryManager.addMemory(ctx, session, uiList, giList, Array.isArray(keywords) ? keywords : [], [], text, normalizedVisibility); SessionService.save(session); return `添加记忆成功`; @@ -215,6 +222,8 @@ export function registerMemory() { }); toolSearch.solve = async (ctx, _, session, args) => { const { memory_type, name = '', query = '', topK = 5, keywords = [], userList = [], groupList = [], method = 'similarity' } = args; + // 记录调用方会话:私有记忆只对创建它的会话可见,搜索其他会话时按调用方会话过滤 + const callerSessionId = session.sessionId; const si: SessionInfo = { isPrivate: false, @@ -256,7 +265,8 @@ export function registerMemory() { users: uiList.map(u => u.id), groups: giList.map(g => g.id), relatedMemories: [], - method: method + method: method, + sessionId: callerSessionId } const memoryList = await session.memory.search(query, options); diff --git a/src/update.ts b/src/update.ts index 730807b..f8b14f0 100644 --- a/src/update.ts +++ b/src/update.ts @@ -6,6 +6,7 @@ export const updateInfo: { [version: string]: string } = { - .ai help <一级子指令>:查看对应子命令的帮助(如 .ai help tool) - 图片转文字结果超长时自动调用压缩智能体压缩后入库,不再截断 - 允许连续调用函数次数默认调为 10,设为 0 时不限制连续调用次数 +- 移除单次对话 10 轮工具调用的硬编码上限:连续调用次数完全由「允许连续调用函数次数」配置控制(0 为不限制) - 知识库重构:配置从 TOML 改为 Markdown 模板(每条配置一份完整文档,可直接粘贴 .md 文件,按 # / ## / ### 标题与段落自动分块);新增 kb_search / kb_read / kb_list 工具与 .ai kb 指令,知识库只读,内容由管理员配置维护,AI 按需检索 ## 配置项更改 - prompt 模板全部转内置:删除「prompt模板」页签下的全部配置项,不再可配置 @@ -25,6 +26,7 @@ export const updateInfo: { [version: string]: string } = { - 修正「接收图片」「接收骰子发送的消息」「禁止回复复读」等配置描述 ## 重要 - 本次需要恢复默认的配置项:「角色扮演设定」「MCP服务器配置」「技能配置」「音乐服务配置」「知识库」(原「知识库记忆」为 TOML 格式,需改为 Markdown 模板);自定义过 prompt 模板的用户无需操作(模板已内置) +- 旧版「角色扮演设定」迁移到新格式时,每条设定的第一行会被当作角色名称剥离;若旧设定是多行文本且第一行不是名称,恢复默认前请先手动调整,避免内容丢失 - 使用了偷图、随机发图功能的需要同步移除对应配置值 ## 修复 - 修复知识库 kb_read 首次调用未初始化导致的空结果;资源/语音/牌堆工具描述改为动态读取配置,修改配置后无需重载即可生效