跳转至

07 - 开发指南

环境与常用命令

npm install         # 安装依赖
npm run build       # 生产构建 → dist/aiplugin4.js(头部拼接 header.txt)
npm run build-dev   # 开发构建 → dev/aiplugin4.js(带 sourcemap)
npm run smoke       # 冒烟:在 Node 中用 seal 桩加载 dist/aiplugin4.js
npm run lint        # ESLint 检查 src/**/*.ts
npx tsc --noEmit    # 类型检查(直跑 tsconfig,不要加 --strict 覆盖严格选项)
npm run test:unit   # 单元测试(esbuild 打包 scripts/unit-test-entry.ts 后运行)
npm run test:integration # 跨仓库联测:配置生成器产出 → 插件 v5/domain 解析(生成器仓库在 ../aiplugin4-config 时运行,否则 SKIP)
node scripts/gen-domain-default-data.mjs # 从旧静态默认值重新生成 src/config/domain/default_data.ts(改默认角色/技能/MCP/知识库后跑)
npm run sim         # 上下文成本治理端到端模拟(代码路径级,15 项)
npm run sim:matrix  # 消息顺序/时序场景矩阵(90 组顺序×预算 + 时序/状态/边界,76 项)
npm run fuzz        # 随机化长跑 fuzz(确定性 PRNG,可 seed 回放)
npm run package:check # 构建 + 同步 sealpack 源 + 包格式与体积校验

验证体系补充:sim / sim:matrix / fuzz 都是代码路径级模拟(真实 Context/Session/handleMessages + 假存储/假模型),能抓回归但不能替代真机——覆盖不到真实厂商 API 的请求体校验、goja 沙箱 storage 行为、QQ 消息形态与多会话并发时序。fuzz 曾抓到「tool 结果与发起它的 assistant 不相邻」的真实 400 bug,适合在改上下文/工具配对后跑。

构建说明(tools/build.js + tools/build-config.js):

  • 使用 esbuild,入口 src/index.ts,target 为 es6(生产)/es2020(dev),external: ['csharp','puerts']。
  • 产物输出到 dist/aiplugin4.js,并把 header.txt(UserScript 注释)拼到文件头部。
  • 生产构建不做压缩(minify=false),保持可读、便于用户自行查错。

冒烟测试(scripts/smoke.js):用 seal 桩(Proxy 兜底所有方法)在 Node 中 require('../dist/aiplugin4.js'),可在没有 SealDice 的环境下提前发现加载期 ReferenceError/TypeError。改完核心逻辑建议 npm run build && npm run smoke。

代码组织约定

  • 核心逻辑放在 src/ 对应模块目录,src/index.ts 只做装配,事件处理统一走 src/pipeline.ts。
  • 持久化类需要定义 static validKeysMap,并用 revive() 恢复(见 06-数据存储)。
  • 新增消息级能力优先考虑做成"工具"而非写死在命令里,便于 AI 通过函数调用使用。
  • 模板文案(如 prompt)放配置模板,不要硬编码在业务逻辑中。

Git 与 Shell 约定

  • 代码/功能改动不要直接在 main 分支上做修改,先开分支或 PR,review 通过后再合入 main;纯文档改动(如本开发指南)可直接提交 main。
  • PR 合入统一使用 squash merge。
  • PR 合入 main 后,在本地和远程清理掉对应 PR 分支。

添加一个工具

  1. 在 src/tool/tools/ 下新建文件(或放入已有分类目录),导出一个 registerXxx() 函数。
  2. 创建 new Tool({ type:'function', function:{ name, description, parameters } }),实现 solve(ctx, msg, session, args),返回字符串给 AI。
  3. 可选配置:sessionType(group/user)、sensitive(敏感标记)。如需调用海豹扩展指令,使用 run_ext_command;它会在每次调用时现场构造 CmdArgs,不依赖历史指令或 .r。
  4. 在 src/tool/tools/init.ts 的 registerTools() 中导入并调用注册函数(src/tool/tool.ts 的 Tool.registerTool() 统一调用它)。

示例:

// src/tool/tools/tool_say.ts
import Tool from "../tool";

export function registerSay() {
    const tool = new Tool({
        type: "function",
        function: {
            name: "say_hi",
            description: "打招呼",
            parameters: {
                type: "object",
                properties: {
                    arg1: { type: "string", description: "说点什么" }
                },
                required: ["arg1"]
            }
        }
    });
    tool.solve = async (_ctx, _msg, _session, args) => {
        return `你好,${args.arg1}`;
    };
}
// src/tool/tools/init.ts 的 registerTools() 中
import { registerSay } from "./tools/tool_say";
registerSay();

添加一个配置分组

  1. 在 src/config/configs/ 下新建文件,定义类,实现 register()(用 seal.ext.registerXxxConfig 注册)与 get()(读取并返回类型化对象)。
  2. 在 src/config/config.ts 的 configMap 中登记该分组。
  3. 使用处直接 Config.<分组>.<字段> 读取(带缓存)。

可用注册函数:registerTemplateConfig(多行文本/模板)、registerStringConfig、registerIntConfig、registerFloatConfig、registerBoolConfig、registerOptionConfig(下拉)。

  • registerTemplateConfig 的默认值不能传空数组,至少保留一个元素;仅作占位/示例展示时可用 [''](一个空字符串元素)。

添加一个子命令

  1. 在 src/cmd/sub_cmd/ 新建文件,导出 registerCmdXxx(),创建 new SubCmd('name')。
  2. 设置 desc/help/priv,实现 solve(scc: SubCmdContext),返回 ret。
  3. 在 src/cmd/root_cmd.ts 的 SubCmd.register() 中调用注册函数。
  4. 如需要子参数权限,在 priv.args 中按命令链嵌套声明。

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。
  2. 在 src/agent/agents/index.ts 的 initAgents() 中调用。
  3. 若智能体需要独立的模型用途(如 compression、summarization),其请求走 Model.getChatModel(this.use) 的 chat 类用途默认链:直接取该用途首个可用的 chat 类候选(压缩/总结/judge 等子智能体与 chat 共用同一批文本/视觉候选,落到同一条默认链——各用途首个文本/视觉模型);需要其它模型时用 .ai model <用途> <模型> 为对应用途绑定。

为其他插件提供 API(globalThis.aiplugin4)

插件启动时会把智能体 API 挂载到 globalThis.aiplugin4(幂等,重载不重复覆盖),海豹所有 JS 插件共享同一全局环境,因此其他插件可直接调用,无需依赖本插件的内部实现。调用前建议先判空,兼容本插件未安装的情况。

暴露的方法:

成员 说明
name / version 插件名与版本号
Agent Agent 类,可自行 get/save 智能体
getAgent(name?) 获取智能体实例,name 缺省为默认智能体 *
getSession(agentName, sessionId) 获取指定智能体的会话
chat(prompt, agentName?) 单轮对话:不经过会话上下文与工具,直接返回模型回复文本
chatMessages(messages, agentName?) 对话:直接发送 OpenAI 风格 messages 数组到对话模型,返回回复文本
imageToText(source, prompt?) 识图:对图片 id 或 http(s) 图片地址调用图片识别模型,返回描述文本
embed(text) 嵌入:调用嵌入模型返回文本向量(未配置嵌入模型时返回空数组)
run(ctx, msg, options?) 在当前聊天中触发一次完整对话编排(上下文/工具/回复发送),等价于该会话收到一次触发
registerTool(info, options?) 注册一个工具,供 AI 通过函数调用/提示词工程使用;同名内置工具不可覆盖,成功返回 true

run 的 options:agentName(智能体名,缺省 *)、reason(触发原因,缺省「外部插件调用」)、toolChoice(强制工具选择策略,缺省交给模型)。registerTool 的 options:sensitive(敏感工具)、sessionType(any/group/user)、callBack(是否回调对话:工具结果写回上下文并继续生成,缺省 true;false 时工具静默执行、结果不回调)、solve(执行函数,返回给 AI 的文本)。

示例(其他插件内):

const api = globalThis.aiplugin4;
if (!api) return; // 本插件未安装

// 单轮对话,直接拿回复文本
const reply = await api.chat('用一句话介绍你自己');

// 对话:构建 messages 数组直接调用模型
const reply2 = await api.chatMessages([
    { role: 'system', content: '你是翻译助手' },
    { role: 'user', content: '翻译:hello' }
]);

// 识图:传图片 id 或 URL,返回描述文本
const desc = await api.imageToText('https://example.com/a.png', '画面里有什么?');

// 嵌入:返回文本向量
const vec = await api.embed('要检索的文本');

// 指定智能体(如 KP 插件驱动 kp_agent)
const hint = await api.chat('根据当前剧情给主持人的私聊提示', 'kp_agent');

// 完整编排:在收到消息的会话中触发 AI 回复(走上下文/工具/分段发送)
await api.run(ctx, msg, { agentName: 'kp_agent', reason: 'KP插件调用' });

// 底层用法:直接拿 Agent 实例
const agent = api.getAgent('*');
const text = await agent.chat('你好');

// 注册工具:供 AI 函数调用/提示词工程使用
api.registerTool({
    type: 'function',
    function: {
        name: 'my_tool',
        description: '示例工具:把传入的 text 原样回显',
        parameters: {
            type: 'object',
            properties: {
                text: { type: 'string', description: '要回显的内容' }
            },
            required: ['text']
        }
    }
}, {
    solve: async (_ctx, _msg, _session, args) => `回显: ${args.text}`
});

注意:chat 是直连模型的无状态单轮对话;需要多轮上下文、工具调用或主动回复聊天窗口时使用 run。run 与插件自身触发一致,受请求并发限制与配额(令牌桶)约束。

仓库内附带可直接加载的完整示例插件 examples/use-aiplugin4.js(只注册一个 .apitest 命令,子命令 status/chat/agent/run/tool 覆盖智能体调用与 registerTool 注册工具),可复制到海豹中加载体验。注意示例插件的扩展必须调用 seal.ext.register(ext) 注册,否则指令不会进入海豹指令表。

修改 prompt 模板

  • 模板在 src/prompt/templates.ts 中以内置常量定义(不再注册为配置项,WebUI 无默认值可还原;改动模板后需重新构建)。
  • system prompt 的分节组装逻辑在 src/prompt/builder.ts,新增节时需要同步修改模板骨架与构建函数。
  • 记忆/知识库/工具段的开关在 src/memory/manager.ts 与 src/config/configs/memory.ts。

开发完成检查清单

每个功能/改动开发完成并验证后,发布前必须同步以下三处,否则发版说明与用户文档会与代码脱节:

  1. src/changelog.ts:在 changelog 的最新版本条目中补充本次改动(格式 - 描述),发布工作流会读取它生成 GitHub Release 正文。
  2. README.md:同步配置手册/命令手册/可用工具函数等对应章节,保持与代码一致。
  3. 知识库 docs/:涉及命令/配置/工具的改动需同步对应文档(常用:05-命令与配置.md、04-工具系统.md、03-核心模块详解.md、09-注意事项与常见问题.md)。
  4. 配置项更改(新增/删除/修改默认值/弃用)必须在 src/changelog.ts 发版日志中注明,用「配置项更改」小节列出受影响配置项;涉及模板等默认值变更时,还需在「重要」小节提醒用户恢复默认。

发布流程

.github/workflows/:

  • build-check.yml:main 分支 push / PR 时执行 lint、tsc strict、构建并校验 sealpack(npm run package:check)、冒烟测试。
  • release.yml:推送 v* 标签时自动发版——verify(校验标签与 VERSION/changelog.ts 一致)→ node scripts/build-release.js 打包(本体 JS + 本体豹包 + 完整豹包)→ 用 SEALPACK_TOKEN 分别发布两个豹包到 SealRepo → 从 src/changelog.ts 提取版本日志作为 GitHub Release 正文,附带两个豹包与 dist/aiplugin4.js。

打包源 sealpack/:scripts/main.js 与 info.toml 版本由 scripts/prepare-sealpack.js 自动同步;sealpack-full/ 为完整包生成目录(复制本体 + 下载依赖插件,已 gitignore)。完整包依赖插件在 scripts/deps.cjs 的 dependencies 中配置(url 为依赖插件 JS 的 raw 地址,可选 filename),当前只配置一个依赖:ob11 网络连接依赖。4.24.0 起生成音频(tts)/ 生成图片(tti)已并入主插件——语音合成与文生图走「模型」页 api连接 + 模型规则的 tts / image-generation 用途(src/config/static_config/media_presets.ts 供应商内置预设 + src/model/media_template.ts 预设合成与占位符引擎 + src/model/media.ts 请求编排 + src/media/task_queue.ts 异步任务队列),不再读取 globalThis.tts / globalThis.tti / globalThis.aiDrawing。包图标放 sealpack/assets/icon.png;SealRepo Token 放 GitHub Actions secrets(SEALPACK_TOKEN)。

配套后端服务已迁移到独立仓库 aiplugin4-backends,由 launcher.py 一键管理(list/setup/start/stop/status/port/package/webui),Windows/Linux 通用;默认不启动任何后端,首次启动某后端时才自动安装其环境(独立 venv),由该仓库自身发版流程打包发布。详见 08-相关后端项目。

发版前记得同步:src/config/static_config/meta.ts 的 VERSION、header.txt 的 @version、package.json 与 package-lock.json 的 version、src/changelog.ts 的 changelog(且需包含新版本条目);sealpack/info.toml 的版本由 scripts/prepare-sealpack.js 自动同步,发版提交也会一并保持一致。

开发小贴士

  • 日志统一走 Logger(脱敏/截断),不要直接 console.log 明文密钥。
  • 涉及网络请求统一走 withTimeout + fetchData/requestModel,避免请求卡死。
  • 修改会持久化的类字段后,检查其 validKeysMap 是否需要同步更新。
  • 新增工具名不要与已有工具重复;敏感操作(发消息/禁言/改名)记得置 sensitive = true。