已启用连接器:github(通过 GitHub 连接器对 ikechan8370/node-chaite 做了代码、README、变更记录与 PR 审查)。
本次研究结论可以概括为三句话:
node-chaite 已经具备“可控工具调用循环”的雏形:其 AbstractClient.sendMessage() 在模型返回 toolCalls 时,会在同一进程内解析工具、执行 tool.run()、把工具结果写入历史,再递归继续对话,直到模型返回非工具调用响应。fileciteturn63file0L1-L1 这意味着它不是“纯 SDK”,而是一个已经带有最小 agent loop 的“可嵌入式运行内核”。
node-chaite 的当前定位非常贴近你最新的“依赖库+抽象+远程优先”方向:项目自述定位为 chatgpt-plugin 与 karin 生态插件的核心库;提供多模型适配(OpenAI / Gemini / Claude)、工具系统、处理器/触发器、RAG、预设与频道管理、以及(可选)Express API/前端,并已经把存储抽象成 BasicStorage。fileciteturn62file0L1-L1 fileciteturn80file0L1-L1 fileciteturn84file0L1-L1
要把它升级为“高级 Agent 环境”的关键不在于堆叠算法,而在于补齐 harness engineer 关注的那套“可运行、可测、可控、可回滚、可观测”的工程构件:权限与沙箱、远程执行与降级、本地资源预算、并发与重试、审计与指标、CI/CD 与模拟 harness。此类能力与 OWASP 对 agentic 系统风险(Prompt Injection、Insecure Plugin Design、Excessive Agency 等)高度对应。citeturn3search1
因此,本报告给出一条“库化+抽象+远程执行优先”的可落地路线:
通过 新增 ToolExecutor / Skill / Workflow / RuntimeHarness 等抽象接口与轻量默认实现,微改 现有 AbstractClient 的工具执行路径(优先走远程执行器,失败再本地降级),实现 AgentSkills 兼容的 skill 目录(SKILL.md) 与 MCP 工具接入(远程工具发现与调用),并为 QQ 生态(yunzai-bot/karin)提供“会话/并发/成本/安全”一体化的嵌入式运行方式。citeturn0search1turn1search1turn1search4turn6search0
node-chaite 的 npm 包元信息明确其生态定位(yunzai、karin、chatgpt 关键词),并表述为“core for chatgpt-plugin and karin-plugin-chatgpt”。fileciteturn85file0L1-L1 README 进一步归纳其能力:多模型适配(OpenAI/Gemini/Claude)、工具调用、预设与频道、处理器、触发器、RAG、文件解析(PDF/Word/Excel/图片等)、多密钥策略等。fileciteturn62file0L1-L1
核心入口 Chaite 类会根据用户模式选择预设、再根据 model 选择 channel/adapter 创建 client,并把请求下沉到 client.sendMessage();同时它已具备 shareable manager(工具/处理器/预设/工具组/触发器)与 cloud service 设置、以及运行 API server 的能力。fileciteturn84file0L1-L1
这与“依赖库+抽象”路线天然兼容:你无需把 node-chaite 变成一个重型常驻 agent 平台;它已经是一个可被 QQ Bot 事件流驱动的嵌入式运行时骨架,只需要把“本地执行、强耦合存储与运行环境”的部分进一步抽象化,并把默认执行策略改为“远程优先”。
AbstractClient.sendMessage() 的关键逻辑是:
- 填充工具与处理器;
- 带历史发送消息;
- 若响应包含
toolCalls:逐个找到工具实例,执行tool.run(args, context); - 保存工具调用消息与结果到 history;
- 递归调用
sendMessage继续对话; - 若启用
disableHistorySave,会临时保存以支撑 tool calling,最终可删除对话以达成“不留历史”的表观效果。fileciteturn63file0L1-L1
这段代码是你做“远程执行优先”的最低成本切入点:只要把 “tool.run()” 替换/旁路为 “ToolExecutor.execute()”,就能把工具执行移到远端(MCP/自研 worker),同时保留现有的对话与历史管理能力。
ExecutableShareableManager(工具、处理器)会扫描指定目录下的 .js 文件,通过动态 import() 加载默认导出对象,并用 chokidar 监听目录变更以热重载。fileciteturn65file0L1-L1 ToolManager 基于该机制实现工具代码与 DTO 的序列化/存储协作。fileciteturn64file0L1-L1
这是一种非常实用的“插件式工具系统”雏形,但它隐含一个 harness engineer 必须直面的风险:加载并执行 JS 工具代码等价于执行不受信任代码。Node 官方明确指出 node:vm 不是安全机制,不应被用于运行不受信任代码。citeturn4search4 因此“远程执行优先”不仅是资源/伸缩策略,更是安全策略:把工具执行移到隔离环境(容器/沙箱/独立主机),并将本地降级严格限定在“可信工具、最小权限、可审计”的范围。
CHANGELOG 显示该库持续围绕“多模型适配、工具调用稳定性、配置能力、触发器上下文、Express server 导出”等演进;例如 1.8.0 提到导出 express server,1.9.x 聚焦 Gemini 兼容与依赖修复。fileciteturn67file0L1-L1
PR #69(已合并)反映了依赖与发布流程的调整(pnpm、依赖归类等),以及对运行时依赖(如 pdf-parse)放置位置的讨论。fileciteturn77file0L1-L1 PR #73(草稿)展示了“通过 options 扩展适配器能力并补测试”的演进模式(为 Gemini client 增加 apiVersion 选项并添加测试)。fileciteturn79file0L1-L1 这对你后续“以 options/接口扩展 runtime harness 能力、保持向后兼容”的改造方式是直接参考。
这里将“harness engineer”界定为:负责 agent 系统在真实环境中 运行、测试、集成、监控、回滚、隔离、安全与成本控制 的工程角色(介于 SRE/平台工程/测试工程与集成工程之间)。对当代 AI Agent 来说,它的影响不是“锦上添花”,而是决定系统能否上线与可持续运营的硬门槛。
在“会调用工具、可执行动作”的 agent 系统里,风险与不确定性来自三条链路:
- 模型不确定性:输出可能幻觉、可能被 prompt injection 影响、可能产生越权意图(Excessive Agency)。OWASP LLM Top 10 将 Prompt Injection、Insecure Plugin Design、Excessive Agency 等列为关键风险类型。citeturn3search1
- 工具不确定性:工具可能失败、超时、返回异常;更严重的是工具本身是代码或外部系统,存在供应链与执行风险(插件、脚本、二进制)。citeturn3search1
- 运行环境不确定性:并发、资源、网络、配置、依赖、版本漂移导致不可复现与不可回滚。
harness engineer 的工作就是把这三条不确定性链路“工程化封装”,形成可控边界。落到可编码细节,核心是一套 “策略 + 机制 + 可观测”:
- 策略:权限模型、预算(token/时间/工具调用次数)、变更与灰度、回滚规则、强制人工确认点。
- 机制:隔离执行(远程/沙箱)、并发调度、重试与退避、错误分类、状态快照与恢复、确定性测试 harness。
- 可观测:审计日志、指标、分布式追踪(trace/span)、告警门槛。
在你的约束(1C2G、QQ bot 常驻、生态插件多、技能/工作流扩展)下,本地执行面临两类硬问题:
- 资源与稳定性:复杂工具(浏览器自动化、文档解析、爬虫、代码执行)对 CPU/内存/IO 有尖峰;并发场景下易拖垮 bot 主进程。
- 安全与隔离:本地动态加载
.js工具或社区 skill 等属于“执行第三方代码”;即使采用 Node Permission Model,它也被官方描述为“seat belt”,并不提供恶意代码存在时的安全保证,恶意代码仍可绕过并执行任意代码。citeturn4search1
因此,“远程执行优先”在工程上等价于把 agent 的危险半径收敛:
- 工具执行在远端:可用容器/沙箱(如 gVisor 作为容器额外隔离层,强调可用于运行不受信任/用户上传/LLM 生成代码)。citeturn4search0turn4search2
- 本地只保留 orchestrator:负责意图路由、权限检查、状态管理、成本预算、审计与回放;这些任务比执行重工具更适合 1C2G。
对于“远程工具调用 + 远程模型调用”的组合系统,harness engineer 的关键实现点有:
- 重试与退避:对可重试错误(网关超时、连接重置、429/5xx 等)使用带 jitter 的指数退避,可显著降低“同步重试风暴”。AWS 的经典总结给出了 full jitter、equal jitter 等策略,并解释了 jitter 如何减少竞争与峰值。citeturn3search0
- 健康检查与回滚:当你把远程执行引入多个组件(bot、remote tool runner、向量库、模型代理)后,需要“就绪/存活/启动”语义区分,避免未就绪服务被流量击穿。Kubernetes 的探针机制对这类分层健康治理有清晰定义:存活探针触发重启,就绪探针控制接流量,启动探针保护慢启动服务。citeturn5search1
- 观测闭环:用 trace 把一个用户请求跨“bot → agent orchestration → LLM → tool runner → storage”串起来。OpenTelemetry 将 trace 定义为请求在系统中的路径,由 spans 构成,并给出 span/属性/事件等概念。citeturn3search2turn3search3
ReAct 的核心是“推理轨迹与行动交替生成”,用行动与环境交互来抑制幻觉,并提升可解释性;其论文在摘要中直接强调 reasoning traces 与 actions 的交织带来的协同。citeturn7search24
Toolformer 的重点是“模型学习何时调用什么工具”,通过自监督数据构造与微调,让模型在 token 级别学会插入 API 调用并利用结果。citeturn8search1 这类方法更偏“模型侧能力内化”,对工程 harness 的要求反而更高:因为工具调用不再完全依赖 prompt 约束,而是模型会更频繁地产生行动意图,需要更强的权限与审计边界。
AutoGPT 与 BabyAGI 的共同特征是更“长期化/自治化”:引入任务队列、记忆、循环执行与自我改写等能力。AutoGPT 项目自述为“构建、部署、运行 AI agents 的平台”,包含 server、marketplace、监控等平台化组件。citeturn7search2 BabyAGI(该仓库说明其为实验框架)强调函数/工具的存储、管理、执行与可视化 dashboard,且明示“不用于生产”。citeturn8search4 这些项目经验更接近“agent 平台工程”,对 1C2G 的 QQ bot 运行形态不友好,但其模块切分(函数注册、执行日志、触发器、dashboard)对你的“库化抽象”很有参考价值。
LangChain Agents 在工程实现上形成了两条思路:
- “Action agents”多受 ReAct 启发;
- “Plan-and-Execute”引入计划器与执行器分离,适合复杂长期规划,但以更多 LLM 调用为代价(LangChain 官方博客明确指出这一点)。citeturn2search4
对你的结论:在 QQ 生态与 1C2G 约束下,最佳组合往往不是“更自治”,而是“更可控”:
- 默认走短环(ReAct 风格的“对话—工具—对话”闭环);
- 工作流/计划作为可选层(显式编排、可审计、可模拟);
- 远程执行器负责重工具与隔离;本地 orchestrator 负责路由与治理。
OpenClaw 明确采用 AgentSkills 兼容的 skill folder:每个 skill 至少包含 SKILL.md(YAML frontmatter + 指令正文),并支持多来源加载与优先级覆盖(bundled、用户目录、workspace 等),还会基于环境/配置/二进制存在性过滤技能。citeturn1search1turn1search2
AgentSkills 规范对 SKILL.md 的字段、目录结构与“渐进式披露(progressive disclosure)”给出细则:启动时只加载 name/description 元数据,激活 skill 时加载正文,额外资源按需加载;并定义了实验性的 allowed-tools 白名单字段。citeturn0search1
对 node-chaite 的映射:node-chaite 已有 ChatPreset(提示词/模型/参数)、ToolsGroup(工具集合)、processors(前后处理)、triggers(触发器)等概念。fileciteturn84file0L1-L1 你可以把“Skill”定义为这些构件的组合与绑定:
Skill = instructions(SKILL.md正文) + tool allowlist/工具组 + 可选 processors + 可选 workflow
进而把 OpenClaw/AgentSkills 的组织方式引入 QQ 生态,获得更好的可移植性与可分享性。
MCP 官方规范将其定义为连接 LLM 应用与外部数据源/工具的开放协议,基于 JSON-RPC 2.0,包含生命周期管理、能力协商、server features(resources/prompts/tools)等模块,并提供多版本修订。citeturn1search3turn1search4 MCP 的规范与文档在官方 GitHub 仓库中维护。citeturn1search0
MCP 还提供官方 SDK,TypeScript SDK 作为 Tier 1,npm 包为 @modelcontextprotocol/sdk,宣称实现完整规范并支持 stdio、Streamable HTTP 等传输。citeturn6search0turn6search1turn6search7
对你当前目标的关键互补点:
- node-chaite 的 Tool 接口是“function schema + run()”。fileciteturn81file0L1-L1
- MCP 的 tools 是“标准化远程工具发现与调用”。citeturn1search4turn1search0
把 MCP 接入 node-chaite 的最优方式不是“把 node-chaite 改成 MCP host 平台”,而是让 node-chaite 提供一个 MCP ToolExecutor:
- 远程发现 tools → 映射为 node-chaite Tool schema(或直接绕过 Tool.run,走 executor);
- 远程调用 tools → 返回 tool result → 写入 node-chaite 既有 history & loop;
从而实现“远程执行优先、可本地降级”。
本节从宏观到可编码细节,给出“功能完善的高级 Agent”所需组件,并明确哪些放在 node-chaite(库内),哪些应放在远端(可选服务)。
库内(node-chaite)应承担的最小闭环(适配 1C2G):
- 会话入口与路由:面向 QQ 事件(群/私聊/指令),生成
AgentRequest,路由到 skill/workflow 或默认对话。 - 技能/工具治理层(harness):权限校验、工具白名单、预算(时间/token/工具次数)、并发控制、重试与退避、审计与指标事件上报。
- 模型调用与工具调用闭环:复用 node-chaite 现有 client/adapter 与 tool-calling 递归循环。fileciteturn63file0L1-L1
- 状态与记忆抽象:用
BasicStorage抽象存储(in-memory 默认 + 可插拔远端)。fileciteturn80file0L1-L1
远端(可选服务)应承载的重与险部分:
- 工具执行器(Remote Tool Runner):运行代码、浏览器、爬虫、Shell、文件处理等重工具,提供隔离/配额。
- MCP servers:把远端工具以 MCP 标准暴露。
- 观测/审计汇聚:OTel collector、日志平台、指标平台。
建议定义如下稳定数据流(可直接映射到代码接口):
- 入口:QQ bot 收到事件
EventMessage(node-chaite 已有事件结构与ChaiteContext)。fileciteturn83file0L1-L1 - Skill 解析:从 skill registry 读取元数据(name/description/allowed-tools),决定激活 skill(或 workflow)。规范参考 AgentSkills。citeturn0search1
- 工具集装配:根据 skill allowlist + toolsGroup + user policy 生成可用工具集合(或 tool executor 的 allowlist)。
- 调用模型:通过
Chaite.sendMessage → client.sendMessage,并将toolExecutor注入 client options。fileciteturn84file0L1-L1 - 工具执行:当模型返回 toolCalls,由
ToolExecutor负责远程优先执行与降级,并产生审计/指标事件;client 将结果写入 history 并递归继续。fileciteturn63file0L1-L1 - 返回结果:输出给 QQ bot,并把(可选)摘要记忆、运行快照写入 storage。
建议把错误分三类(可编码为 error.kind):
RETRYABLE:网络抖动、429、5xx、连接重置、远端 tool runner 短暂不可用 → 指数退避 + jitter。citeturn3search0POLICY_DENIED:权限拒绝、越权工具、敏感操作需确认、预算耗尽 → 直接终止或请求用户确认(human-in-the-loop)。citeturn3search1FATAL:参数校验失败、工具逻辑错误、反序列化失败、不可恢复超时 → 记录审计并终止。
此外,强烈建议保留 node-chaite 现有的防无限工具调用机制(如 toolCallLimit);你可以将其上移为 harness 的“预算/终止策略”。fileciteturn82file0L1-L1
建议采用“三层权限”:
- Skill 层:
allowed-tools(AgentSkills 规范字段)或工具组约束(node-chaite ToolsGroup)。citeturn0search1turn81file0L1-L1 - 用户/群策略层:对 QQ 群/用户做工具开关、额度、危险操作确认点(例如“发消息给群成员”“转账/支付”“执行 shell”必须确认)。这对应 OWASP 的 Excessive Agency 风险治理。citeturn3search1
- 执行环境层:远程 runner 以容器/沙箱隔离执行(gVisor 等),将“即使工具被攻破也仅在沙箱内”的风险半径固化。citeturn4search2turn4search6
本地降级时,不应把 node:vm 当作沙箱;官方已明确不安全。citeturn4search4 Node Permission Model 可作为“防误操作 seat belt”,但不能当作对抗恶意代码的隔离。citeturn4search1
下表基于 README、核心代码与类型定义梳理。fileciteturn62file0L1-L1 fileciteturn63file0L1-L1 fileciteturn81file0L1-L1 fileciteturn80file0L1-L1
| 模块 | 现有 node-chaite | 目标 Agent 依赖库形态(远程优先) | 改动清单(摘要) |
|---|---|---|---|
| LLM 适配 | OpenAI/Gemini/Claude adapters;统一 client 接口与 history manager | 保持不变;新增“运行时 harness 注入点” | BaseClientOptions/SendMessageOption 添加 harness 钩子(toolExecutor、budgets、audit emitter) |
| 工具系统 | Tool = function schema + run();ToolManager 动态加载 .js 并热重载 |
工具执行默认走远端;本地只允许可信工具/或受控沙箱 | 新增 ToolExecutor;修改 client 工具执行路径“优先 executor” |
| Skill | 无 AgentSkills 目录规范;有 preset、tools-group、processors、triggers | 引入 SKILL.md(AgentSkills兼容);skill 绑定 preset/tool groups/workflow |
新增 SkillRegistry、skill parser;新增 SkillDTO/manifest |
| MCP | 无 | 支持 MCP 远程工具发现与调用(默认远程执行路径) | 新增 MCP client/transport 与 McpToolExecutor |
| Workflow 编排 | 有 triggers/processors;无显式 workflow DSL | 提供轻量 workflow(顺序/分支/子调用)并可下沉远端 | 新增 WorkflowEngine 抽象与 SimpleWorkflowRunner |
| State/Memory | BasicStorage 抽象已存在;user state、history manager 等 |
继续抽象化;增加 run snapshot/restore 与可复现回放 | 新增 RunStore、Snapshot 序列化;补齐接口 |
| 观测与审计 | logger、部分 debug;无标准 trace/metrics | 事件驱动的审计/指标/trace 适配(OTel 可选) | 新增 AuditEmitter、MetricsSink 接口;提供 OTel adapter(可选依赖) |
| 安全沙箱 | 本地动态 import 执行工具;风险外露 | 远程 runner 隔离;本地降级仅 seat belt + 白名单 | 提供 “remote-first + local-safe” 策略;文档与示例 |
| CI/CD 与测试 | jest;已有部分 adapter 测试;发布流程见 PR/CHANGELOG | 增加 harness/skill/mcp/workflow 的单测与模拟集成测 | 新增 test harness fixtures;GitHub Actions 示例 |
以下以“尽量少破坏现有 API”为原则(参考 PR #73 的“新增 option + 补测试”模式)。fileciteturn79file0L1-L1
-
src/agent/contracts.ts
定义关键抽象接口:ToolExecutor、SkillRegistry、WorkflowEngine、RunStore、AuditEmitter、BudgetPolicy等。 -
src/agent/tool-executors/localToolExecutor.ts
把现有tool.run()执行封装成 executor(用于本地降级路径)。 -
src/agent/tool-executors/mcpToolExecutor.ts
远程优先工具执行器:支持 tool discovery(缓存)、callTool()、超时、重试、jitter backoff。退避策略参考 AWS。citeturn3search0 -
src/agent/skills/skillRegistry.ts
读取 skill 目录(skills/**/SKILL.md),支持 AgentSkills progressive disclosure 与allowed-tools。citeturn0search1 -
src/agent/workflow/simpleWorkflowRunner.ts
轻量 workflow 引擎:顺序步骤 + 条件分支 + 子流程;每个 step 可触发一次 LLM 或一次 tool call。 -
src/agent/runtime/agentRunner.ts
面向 QQ bot 的高层入口:skill 选择 → prompt 组装 → 调用Chaite.sendMessage→ 返回结果;同时产出审计与 metrics 事件。 -
src/agent/runtime/scheduler.ts
并发与队列:按 userId / groupId 限流,防止 1C2G 被并发击穿。 -
src/agent/runtime/snapshot.ts
serialize/restore:保存 runId、conversationId、parentMessageId、skill、预算消耗、关键上下文引用。
-
src/types/common.ts
BaseClientOptions增加可选字段:toolExecutor?: ToolExecutorauditEmitter?: AuditEmitterbudgetPolicy?: BudgetPolicyscheduler?: Scheduler
对应你要在 adapter/client 里注入 harness 的需求。现有ChaiteContext也可扩展:加入runId、skill、traceContext等字段。fileciteturn83file0L1-L1
-
src/adapters/clients.ts
将tool.run()的执行分支替换为:- 若配置了
toolExecutor:await toolExecutor.execute(toolCall, ctx) - 否则 fallback:
await tool.run()
并在执行前后发出审计事件(tool.start/tool.end/tool.error),以及统一错误分类与重试。当前这里是工具循环的“唯一总闸”,改动收益最大。fileciteturn63file0L1-L1
- 若配置了
-
src/types/adapter.ts
为SendMessageOption增加(可选)harness 参数,例如:runId?: stringtoolTimeoutMs?: numberretryPolicy?: RetryPolicyrequiredHumanConfirmTools?: string[]
保持序列化/反序列化一致。fileciteturn82file0L1-L1
-
src/core/chaite.ts
Chaite.sendMessage()创建 client 时,将 channel options 上的toolExecutor/auditEmitter等传入 context 或 options(保持 channel 级默认策略)。fileciteturn84file0L1-L1 -
src/index.ts
为控制包体积,建议不要把新 agent 子系统全部export *到根入口;考虑新增子路径入口(例如chaite/agent)。当前src/index.ts导出范围很大(含 controllers)。fileciteturn86file0L1-L1 -
package.json
将 MCP SDK 设为可选peerDependency(类似现有pdf-parseoptional peerDependency),避免默认膨胀包体积;如果你实现自研轻量 MCP client,则 SDK 可选用于增强传输/兼容性。fileciteturn85file0L1-L1 citeturn6search1
- 若你高度强调“依赖库轻量”,建议将
controllers(Express server & frontend)迁移到独立包或子路径导出,避免 QQ bot 仅使用 SDK 时被动携带 server 相关代码路径。当前 controllers 负责路由挂载与静态前端服务。fileciteturn66file0L1-L1 - 但考虑 CHANGELOG 已将“导出 express server”作为特性提供,fileciteturn67file0L1-L1 更稳妥做法是保留但懒加载/子路径导出。
以下示例体现“远程执行优先、可本地降级”的设计原则;可直接用于你在 node-chaite 中落地(命名可按你的风格调整)。
// src/agent/contracts.ts
export type ToolName = string;
export interface SkillFrontmatter {
name: string; // 对齐 AgentSkills: 1-64, a-z0-9-
description: string; // 对齐 AgentSkills
license?: string;
compatibility?: string;
metadata?: Record<string, unknown>;
allowedTools?: ToolName[]; // 由 allowed-tools 解析成数组
}
export interface Skill {
id: string; // 可用 `${name}@${version}` 或 hash
rootDir: string;
frontmatter: SkillFrontmatter;
/**
* Progressive disclosure: 只在 skill 激活时才加载正文
*/
loadInstructions(): Promise<string>;
/**
* 可选:加载 references/assets/scripts 等按需资源
*/
readFile(relPath: string): Promise<Buffer>;
}
export interface SkillRegistry {
listSkillMetas(): Promise<Array<Pick<Skill, "id" | "rootDir" | "frontmatter">>>;
getSkillByName(name: string): Promise<Skill | null>;
}// src/agent/contracts.ts
export interface ToolCall {
id: string;
name: string;
arguments: Record<string, unknown>;
}
export interface ToolResult {
id: string;
name: string;
ok: boolean;
outputText: string; // 兼容 node-chaite Tool.run(): Promise<string>
error?: {
kind: "RETRYABLE" | "POLICY_DENIED" | "FATAL";
message: string;
cause?: unknown;
};
meta?: Record<string, unknown>;
}
export interface ExecutionContext {
runId: string;
userId: string;
groupId?: string;
conversationId?: string;
skillName?: string;
timeoutMs?: number;
// 用于审计与可观测
traceparent?: string;
tags?: Record<string, string>;
}
export interface ToolExecutor {
/**
* 远程优先:实现方决定是 MCP/HTTP/RPC
*/
execute(call: ToolCall, ctx: ExecutionContext): Promise<ToolResult>;
/**
* 可选:用于工具发现与缓存
*/
listAvailableTools?(ctx: ExecutionContext): Promise<Array<{ name: string; description?: string; schema?: unknown }>>;
}// src/agent/tool-executors/mcpToolExecutor.ts
type McpEndpoint = {
baseUrl: string; // 例如 https://tool-runner.example.com/mcp
authHeader?: string;
};
export class McpToolExecutor implements ToolExecutor {
constructor(
private readonly endpoint: McpEndpoint,
private readonly fallback?: ToolExecutor, // LocalToolExecutor
) {}
async execute(call: ToolCall, ctx: ExecutionContext): Promise<ToolResult> {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ctx.timeoutMs ?? 15_000);
// MCP 以 JSON-RPC 2.0 通信(规范要求消息遵循 JSON-RPC 2.0):
// 这里仅示意 tools/call;完整初始化/能力协商可按需补齐
const res = await fetch(this.endpoint.baseUrl, {
method: "POST",
headers: {
"content-type": "application/json",
...(this.endpoint.authHeader ? { authorization: this.endpoint.authHeader } : {}),
},
body: JSON.stringify({
jsonrpc: "2.0",
id: call.id,
method: "tools/call",
params: { name: call.name, arguments: call.arguments },
}),
signal: controller.signal,
}).finally(() => clearTimeout(timeout));
if (!res.ok) {
// 这里可按状态码分类 RETRYABLE/FATAL
throw new Error(`MCP HTTP ${res.status}`);
}
const json = await res.json();
const text = (json?.result?.content ?? [])
.filter((c: any) => c?.type === "text")
.map((c: any) => c.text)
.join("\n")
.trim();
return { id: call.id, name: call.name, ok: true, outputText: text || "" };
} catch (err) {
// 远程失败:优先降级到本地(如果允许)
if (this.fallback) {
return await this.fallback.execute(call, ctx);
}
return {
id: call.id,
name: call.name,
ok: false,
outputText: "",
error: { kind: "RETRYABLE", message: "remote tool execution failed", cause: err },
};
}
}
}// 修改 src/adapters/clients.ts 中 toolCalls 执行段(示意)
import type { ToolExecutor, ToolCall, ExecutionContext } from "../agent/contracts";
// 在 AbstractClient 内新增/读取 options.toolExecutor(来自 BaseClientOptions)
const toolExecutor: ToolExecutor | undefined = (this.options as any).toolExecutor;
for (const toolCall of modelResponse.toolCalls ?? []) {
const call: ToolCall = {
id: toolCall.id,
name: toolCall.name,
arguments: toolCall.args,
};
if (toolExecutor) {
const ctx: ExecutionContext = {
runId: options?.conversationId ?? "run-unknown",
userId: context.getEvent()?.sender.user_id?.toString() ?? "unknown",
groupId: context.getEvent()?.group_id?.toString(),
conversationId: options?.conversationId,
skillName: context.getData()?.skillName,
timeoutMs: options?.toolTimeoutMs ?? 15_000,
};
const result = await toolExecutor.execute(call, ctx);
toolCallResults.push({
tool_call_id: toolCall.id,
role: "tool",
name: toolCall.name,
content: result.ok ? result.outputText : `ERROR: ${result.error?.message ?? "unknown"}`,
});
continue;
}
// fallback:旧逻辑 tool.run()
const tool = filledTools.find((t) => t.name === toolCall.name);
const toolCallOutput = await tool!.run(toolCall.args, this.context);
toolCallResults.push({ tool_call_id: toolCall.id, role: "tool", name: toolCall.name, content: toolCallOutput });
}// src/agent/runtime/snapshot.ts
export interface RunSnapshot {
runId: string;
userId: string;
groupId?: string;
conversationId?: string;
parentMessageId?: string;
activeSkill?: string;
budgets: {
toolCalls: number;
llmTokens?: number;
wallTimeMs: number;
};
// 可加:最近 N 条摘要记忆、最近一次错误等
}
export interface RunStore {
save(snapshot: RunSnapshot): Promise<void>;
load(runId: string): Promise<RunSnapshot | null>;
delete(runId: string): Promise<void>;
}/**
* 重要说明:
* - node:vm 不可用于不可信代码隔离
* - Node Permission Model 是 seat belt,不是对抗恶意代码的沙箱
* 因此“本地执行”建议只用于你签名/审核过的可信工具;
* 或把本地执行也变成“本地 Docker/远端 Runner”。
*/
// 伪代码:将工具放入独立进程,并用最小化环境变量 + 超时控制
import { spawn } from "node:child_process";
export async function runTrustedToolInChildProcess(
entry: string,
argsJson: string,
timeoutMs = 10_000,
): Promise<string> {
return await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [entry, argsJson], {
stdio: ["ignore", "pipe", "pipe"],
env: { NODE_ENV: "production" }, // 最小 env
});
const timer = setTimeout(() => {
child.kill("SIGKILL");
reject(new Error("tool timeout"));
}, timeoutMs);
let out = "";
child.stdout.on("data", (d) => (out += d.toString("utf8")));
child.stderr.on("data", (d) => (out += `\n[stderr]\n${d.toString("utf8")}`));
child.on("exit", (code) => {
clearTimeout(timer);
if (code === 0) resolve(out.trim());
else reject(new Error(`tool exit ${code}: ${out}`));
});
});
}上述沙箱仅用于“可信工具的隔离运行”。对不可信代码,建议使用远端 runner,并采用容器/隔离层(如 gVisor)。citeturn4search2turn4search4
sequenceDiagram
autonumber
participant QQ as QQ Bot (yunzai/karin)
participant AR as AgentRunner (node-chaite/agent)
participant SR as SkillRegistry
participant CH as Chaite.sendMessage
participant CL as AbstractClient
participant LLM as LLM Provider
participant TE as ToolExecutor (Remote-first)
participant MCP as MCP Server / Remote Tool Runner
participant HS as HistoryManager / Storage
participant OBS as Audit/Metrics/Tracing
QQ->>AR: onMessage(EventMessage)
AR->>SR: listSkillMetas() / selectSkill()
SR-->>AR: skill meta (name/desc/allowed-tools)
AR->>SR: loadInstructions() (activate skill)
SR-->>AR: SKILL.md body
AR->>CH: sendMessage(userMsg, event, options+toolExecutor)
CH->>CL: client.sendMessage(...)
CL->>HS: load history (optional)
CL->>LLM: sendMessageWithHistory(tools, prompt)
LLM-->>CL: response (toolCalls?)
alt toolCalls present
CL->>TE: execute(toolCall, ctx)
TE->>MCP: tools/call (JSON-RPC)
MCP-->>TE: tool result
TE-->>CL: ToolResult
CL->>HS: save tool call + tool result
CL->>OBS: emit tool span/audit
CL->>LLM: sendMessageWithHistory(updatedHistory)
LLM-->>CL: final answer
end
CL-->>CH: ModelResponse
CH-->>AR: ModelResponse
AR-->>QQ: reply(text/attachments)
AR->>OBS: emit run end metrics
建议按“单测优先、集成测覆盖远程路径”的策略补齐:
-
Skill 解析
- 解析
SKILL.mdYAML frontmatter(name/description/allowed-tools) - progressive disclosure:只读 meta vs 激活后读正文
- 不合法字段/命名规则的错误提示(对齐 AgentSkills)
- 解析
-
ToolExecutor(核心治理)
- 远程成功:MCP 调用返回 text content 拼接
- 远程超时:触发 REJECT + retry/backoff(可用 fake timers)
- 远程失败降级:fallback LocalToolExecutor 被调用
- 权限拒绝:allowed-tools 不包含时返回 POLICY_DENIED
- 预算耗尽:tool call 次数超过上限时终止
-
AbstractClient 集成测试(不依赖真实 LLM)
- stub 一个 adapter:第一次返回 toolCalls,第二次返回 final answer
- 断言:history 保存顺序、递归次数、disableHistorySave 的删除逻辑仍可用(参考现有实现注释与逻辑)。fileciteturn63file0L1-L1
-
QQ 生态集成测试(建议作为单独 repo 或 examples)
- yunzai/karin 的 message hook → AgentRunner → reply
- 并发限制:同一用户串行,不同群并行(可配置)
name: ci
on:
pull_request:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
node: [20, 22]
steps:
- uses: actions/checkout@v4
- name: Use Node.js
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Build
run: npm run build
- name: Test
run: npm test在 1C2G 上建议只跑 bot + node-chaite orchestrator,把工具执行与观测汇聚放远端。
Dockerfile(bot 容器,示意):
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "dist/bot.js"]docker-compose.yml(本地只保留可选 OTel collector;也可完全不在本地跑):
services:
qq-bot:
build: .
environment:
# 远程工具执行(MCP endpoint)
MCP_ENDPOINT: "https://tool-runner.example.com/mcp"
# 观测外送(OTLP)
OTEL_EXPORTER_OTLP_ENDPOINT: "https://otel-collector.example.com"
restart: unless-stopped
# 可选:本地 collector(1C2G 上通常不建议常驻)
otel-collector:
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otelcol/config.yaml"]
volumes:
- ./otelcol.config.yaml:/etc/otelcol/config.yaml:ro
restart: unless-stopped以下估时以“熟悉 TS / node-chaite 代码结构的 1 人”为基准(偏保守),可并行。
里程碑 A:harness 注入点与远程优先工具执行(3–5 天)
- 在
BaseClientOptions增加toolExecutor等可选字段,并在AbstractClient工具执行路径优先调用 executor。fileciteturn83file0L1-L1 fileciteturn63file0L1-L1 - 实现
LocalToolExecutor(封装旧tool.run)。 - 实现最小
McpToolExecutor(HTTP JSON-RPC + 超时)。 - 增加单测:远程成功、远程失败降级、权限拒绝。
里程碑 B:AgentSkills 兼容 skill 系统(4–7 天)
SkillRegistry:扫描skills/**/SKILL.md并解析 frontmatter。citeturn0search1- 将 skill 绑定到 node-chaite 的 preset/tool groups(先用代码配置映射,后续再引入 cloud share)。fileciteturn81file0L1-L1
- AgentRunner:在 QQ bot 入口处做 skill 选择与 prompt 拼装(先规则/关键词,后续可用 LLM 路由)。
里程碑 C:workflow 编排(可选,5–10 天)
- 实现
SimpleWorkflowRunner(顺序 + 分支 + 子流程)。 - 将 triggers(定时/事件)映射为 workflow 入口(node-chaite 已有 trigger manager)。fileciteturn84file0L1-L1
- 补集成测:workflow → LLM step → tool step → 输出。
里程碑 D:观测与审计(3–6 天)
AuditEmitter:统一输出 run/tool/llm 事件(JSON line)。- 可选 OTel adapter:把事件映射为 spans(trace/span 语义按 OTel 概念)。citeturn3search2turn3search3
- 指标:QPS、成功率、p95 延迟、工具错误率、token 消耗。
远程执行不可用导致功能瘫痪
- 缓解:本地降级只允许“可信工具”;对危险工具(文件写入/网络访问/执行命令)强制不降级;并对远程调用加入退避重试与熔断。退避带 jitter。citeturn3search0
社区 skill/工具供应链风险
- 缓解:严格执行 allowlist(skill.allowed-tools + 用户策略),并把工具执行放远端隔离;对不可信代码不要使用
node:vm。citeturn4search4turn3search1
“工具循环”导致成本失控或死循环
- 缓解:保留 toolCallLimit,并在 harness 层加入预算(最大 wall time、最大 tool calls、最大 token)。fileciteturn82file0L1-L1
观测缺失导致不可定位/不可回滚
- 缓解:把 runId/conversationId/toolCallId 全链路贯穿;引入 trace/span。citeturn3search2turn3search3
- 在部署层使用就绪/存活/启动探针模式做回滚/摘流量。citeturn5search1
成本拆解建议以一次“用户请求 run”为单位:
- LLM 成本:
Σ (prompt_tokens + completion_tokens) * 单价
node-chaite 的ModelResponse已返回usage并在历史消息返回结构中携带 usage。fileciteturn63file0L1-L1 - 工具成本:
- 远程 runner:
tool_cpu_ms * 单价 + tool_mem_mb_s * 单价 + egress(取决于部署) - 第三方 API:按调用计费
- 远程 runner:
- 延迟预算:
T_total ≈ T_queue + Σ(T_llm_call) + Σ(T_tool_call) + overhead(history)- 其中重试项按退避策略估计期望额外时延(带 jitter 的重试可降低峰值但增加尾延迟)。citeturn3search0
建议你把以下指标作为 1C2G 的硬约束监控:
- 本地进程:heap、event loop lag、并发 run 数、队列长度
- 远程:tool 调用 p95/p99、失败率、超时率、熔断次数
- 模型:token 消耗、toolCall 次数分布(识别异常模式)
- 安全:POLICY_DENIED 计数(越权企图)、高风险工具确认次数(Excessive Agency 治理信号)。citeturn3search1
优先级 A(项目一手资料:node-chaite)
- 仓库与 README(功能与定位、使用方式):见 node-chaite README。fileciteturn62file0L1-L1
- 核心工具循环实现(
toolCalls→tool.run→ history → recursion):src/adapters/clients.ts。fileciteturn63file0L1-L1 - Tool 类型定义与权限字段(DTO):
src/types/tools.ts。fileciteturn81file0L1-L1 - 存储抽象
BasicStorage:src/types/storage.ts。fileciteturn80file0L1-L1 - Chaite 入口与 managers/cloud service:
src/core/chaite.ts。fileciteturn84file0L1-L1 - Changelog(release notes):
CHANGELOG.md。fileciteturn67file0L1-L1 - PR 示例(扩展 options + 补测试):#73。fileciteturn79file0L1-L1
优先级 A(OpenClaw / AgentSkills / MCP 官方资料)
- OpenClaw skills 文档(AgentSkills-compatible、加载优先级、过滤规则):citeturn1search1turn1search2
- AgentSkills
SKILL.md规范(frontmatter、allowed-tools、progressive disclosure):citeturn0search1 - MCP 官方规范(JSON-RPC、生命周期、能力等):citeturn1search3turn1search4
- MCP 官方规范与文档仓库:citeturn1search0
- MCP 官方 SDK(TypeScript SDK、npm 包):citeturn6search0turn6search1turn6search7
优先级 A(学术与安全/可靠性权威)
- ReAct 论文(推理与行动交织):citeturn7search24
- Toolformer(模型自监督学会工具调用):citeturn8search1
- LangChain Plan-and-Execute(更强规划,更多 LLM 调用成本):citeturn2search4
- OWASP LLM Top 10(Prompt Injection、Insecure Plugin Design、Excessive Agency 等):citeturn3search1
- OpenTelemetry traces 概念(trace/span):citeturn3search2turn3search3
- AWS 指数退避 + jitter(重试治理):citeturn3search0
- Node
vm非安全机制、Permission Model seat belt(本地降级的安全边界):citeturn4search4turn4search1 - gVisor(运行不可信代码的隔离层):citeturn4search2turn4search0
- Kubernetes 存活/就绪/启动探针(服务回滚与摘流量语义):citeturn5search1
为满足“提供仓库链接与优先级来源列表”的可复制性,以下以代码块给出原始链接(可直接复制):
node-chaite:
- https://github.com/ikechan8370/node-chaite
OpenClaw / AgentSkills:
- https://docs.openclaw.ai/skills
- https://agentskills.io/specification
MCP:
- https://modelcontextprotocol.io/specification/2025-06-18/basic/index
- https://github.com/modelcontextprotocol/modelcontextprotocol
- https://modelcontextprotocol.io/docs/sdk
- https://www.npmjs.com/package/@modelcontextprotocol/sdk
Papers:
- https://arxiv.org/pdf/2210.03629 (ReAct)
- https://arxiv.org/abs/2302.04761 (Toolformer, DOI:10.48550/arXiv.2302.04761)
Security / Reliability:
- https://owasp.org/www-project-top-10-for-large-language-model-applications/
- https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter
- https://opentelemetry.io/docs/concepts/signals/traces/
- https://nodejs.org/api/vm.html
- https://nodejs.org/api/permissions.html
- https://gvisor.dev/docs/
- https://kubernetes.io/zh-cn/docs/concepts/configuration/liveness-readiness-startup-probes/