引言
能力(capability)是 LLM 在 esp-claw 里可调用的“工具”:模型决定“做什么”,固件负责“动手做”。本章以 components/claw_modules/claw_cap 为主角,精读其注册、分发、授权与生命周期管理的源码逻辑,回答三个核心问题:一份 C 结构体如何变成模型可见的工具定义;18 个异构的 cap_* 组件为何能用同一契约接入;以及 cap_lua、cap_files、cap_scheduler 三个代表能力各自的设计考量。读完本章,你将看清从模型吐出 tool_call 到设备上真实动作发生的完整链路,以及与第 4 章 Agent 循环、第 6 章事件路由、第 11 章 Lua 驱动体系的精确边界。
1. 能力系统的设计动机:工具为什么在固件里
1.1 从“会说”到“会做”
tool calling 的通用形态并不复杂:模型输出结构化的调用请求(工具名加参数 JSON),宿主程序执行后把结果回填进对话,模型据此继续推理。这个“宿主程序”放在哪里,直接决定了 Agent 的能力上限。esp-claw 的选择是把它做成固件层的一等公民:claw_cap 组件提供统一的注册与分发层,18 个 cap_* 组件各贡献一批具体能力,从读写文件、发消息、跑 Lua 脚本到管理系统重启。第 4 章《Agent 核心设计》的推理循环本身并不知道任何具体工具——它只持有一个函数指针。工具世界的全部知识(有哪些工具、参数长什么样、谁有权调用)都沉淀在能力层,这正是“智能在云、行动在端”的架构分工。
1.2 固件级能力的三个不可替代性
为什么不把这些工具做成云端插件,由云侧 Agent 调用后再下发指令?背后有三个固件层独有的理由。
第一是掉线可用。cap_scheduler 的定时触发、事件路由的本地规则、Lua 脚本的定时巡检都不依赖网络,断网后设备依然是完整的自动化节点。
第二是毫秒级响应。GPIO 翻转、UART 收发这类操作一旦绕道云端,来回至少是秒级延迟。而固件内从 claw_cap_call 到驱动执行,只有一次函数指针调用。
第三是隐私与安全边界。文件能力(cap_files)的沙箱根之内,数据不出设备,LLM 只拿到路径与内容文本,不接触存储介质本身。
此外还有一个工程理由:C 组件可被 Kconfig 静态裁剪(app_claw.c 中每个能力组都有对应的 CONFIG_APP_CLAW_CAP_* 开关),链接期即确定内存占用,这是云端插件永远给不了的确定性。
2. claw_cap:注册与分发的中枢
2.1 能力描述符:一份自描述的说明书
一切从 claw_cap.h 中的 claw_cap_descriptor_t 开始。每个能力就是一份静态填写的说明书:
typedef struct {
const char *id; /* 稳定标识,注册表内部与卸载时使用 */
const char *name; /* LLM 看到的工具名,查找时二者皆可 */
const char *family; /* 归类(files/system/automation…) */
const char *description; /* 工具说明,模型据此决定是否调用 */
claw_cap_kind_t kind; /* CALLABLE / EVENT_SOURCE / HYBRID */
uint32_t cap_flags; /* 旗标:LLM 可调、根代理专属… */
const char *input_schema_json; /* JSON Schema 字符串,原样透传给模型 */
claw_cap_lifecycle_fn init; /* 以下三个生命周期回调均可选 */
claw_cap_lifecycle_fn start;
claw_cap_lifecycle_fn stop;
claw_cap_execute_fn execute; /* 可调用类能力的唯一执行入口 */
} claw_cap_descriptor_t;
两个设计点值得聊聊。其一,input_schema_json 是字符串而非 C 结构体。JSON Schema 本来就是 LLM API 的工具描述格式,存成字符串意味着注册表只做透传、不做二次建模,能力作者也只需写一行 JSON,不必调 cJSON 逐字段拼装。其二,所有描述符表都是 static const 数组,按值拷入注册表槽位——描述符本体进 flash,注册表运行期零碎片,这在堆内存紧张的 ESP32 上不是小事。
2.2 组注册与生命周期状态机
真正的注册路径是 claw_cap_register_group():以 claw_cap_group_t(组标识、插件名、版本、描述符数组、组级回调)为原子单位入库。单个 claw_cap_register() 只是把描述符包成一个“合成组”再走同一条路——组才是唯一的管理单元,启停、可见性、卸载全部按组进行。
组内校验(claw_cap_validate_group_locked)会拒绝空标识、缺 execute 的 LLM 可调项,以及任何与现存能力的 id/name 冲突。注册表用槽位数组实现,初始容量仅 4,按需以 max(所需/4, 4) 的余量扩容——宁可多 realloc 几次,也不为 18 个能力预分配大数组。
状态机有五态:REGISTERED、STARTED、DISABLED、DRAINING、UNLOADING。启动顺序是组回调 group_start 先行,再对成员逐个 init(仅首次)与 start;停止则严格逆序(成员 stop 倒序、最后 group_stop),与嵌入式驱动的初始化惯例一致。应用侧的 app_capabilities.c 会按配置依次调用各组件的 cap_xxx_register_group(),随后一次 claw_cap_start_all() 拉起全部生命周期。
2.3 分发流程:锁外的执行
分发入口 claw_cap_call() 的加锁策略,是 claw_cap 中最见功力的一段代码。它把并发控制、生命周期管理和错误处理压缩在了几次锁操作里:
claw_cap_lock();
descriptor_slot_index = claw_cap_find_descriptor_slot_index_locked(id_or_name);
...
{
claw_cap_descriptor_slot_t *slot = &s_runtime.descriptor_slots[descriptor_slot_index];
if (ctx && claw_cap_caller_is_agent(ctx->caller)) {
/* Agent 调用者必须过六道授权闸门,拒绝则写错误文本返回 */
claw_cap_auth_status_t s = claw_cap_authorize_llm_tool_locked(slot, ctx);
if (s != CLAW_CAP_AUTH_OK) { ... return ESP_ERR_INVALID_STATE; }
}
slot->active_calls++; /* 登记在执行计数,供排水卸载判断 */
execute = slot->descriptor.execute; /* 快照函数指针与名字 */
name = slot->descriptor.name;
}
claw_cap_unlock(); /* 先解锁再执行:慢能力不阻塞注册表 */
err = execute(input_json ? input_json : "{}", ctx, output, output_size);
为什么要把 execute 挪到锁外?因为能力执行可能长达数秒——HTTP 请求、60 秒的 Lua 脚本都走这条路。若持锁执行,一次慢调用就会卡住工具清单生成、其他能力注册乃至整个 Agent 循环。锁内只做三件事:查槽位、验授权、计数加一;执行期间的互斥交给 active_calls 计数——它正是卸载排水的依据。

流程执行说明:
- 注册阶段(步骤 1-3):启动时
app_capabilities 逐组调用 claw_cap_register_group(),注册表校验后占用描述符槽位(state=REGISTERED);claw_cap_start_all() 依次触发组与成员的生命周期回调,state=STARTED。
- 触发阶段(步骤 4-5):云端模型在推理响应中给出
tool_calls[];claw_core 的 claw_core_append_tool_results_messages() 逐项处理,经配置注入的 call_cap 指针(即 claw_cap_call_from_core())进入能力层。
- 授权阶段(步骤 7):
claw_cap_call() 加锁查找槽位,claw_cap_authorize_llm_tool_locked() 执行六道检查;agent 类调用者必验,system/console 调用者放行。
- 执行阶段(步骤 8-10):授权通过则解锁后调用
slot->descriptor.execute(),能力实现(图中为 cap_files_write_file_execute)自行完成沙箱校验与实际动作,把结果文本写入调用方提供的 output 缓冲。
- 回填阶段(步骤 13-15):错误路径下 output 也带有可读文本;正常路径返回 32 KB 缓冲的输出,
claw_core 组装成 tool_results 消息数组回传模型,进入下一轮推理。授权拒绝不中断循环,模型读到错误文本后可自行改道。
2.4 错误、超时与排水卸载
错误语义是“双通道”:返回码 esp_err_t 给程序判断,output 缓冲永远填有给 LLM 看的文本——"Error: unknown cap 'xxx'"、"Error: cap 'xxx' is not exposed to the LLM.",失败时兜底 esp_err_to_name(err)。这样模型收到的是“可读的失败原因”而非中断,自我纠错成为可能。
claw_cap_call_from_core() 为每次调用分配 32 KB 输出缓冲(CLAW_CAP_CORE_OUTPUT_SIZE,heap_caps_calloc_prefer 优先 SPIRAM)——工具结果动辄是长文件或网页内容,内部 RAM 撑不起。
框架层不给能力执行设超时(时钟权交给能力自己,如 cap_lua 的 deadline),但注册表管理有明确的等待语义:claw_cap_unregister_group() 先把全组置为 DRAINING,随后每 20 ms(CLAW_CAP_UNLOAD_POLL_MS)轮询 active_calls,归零才进入 UNLOADING、逆序回调 stop、清空槽位;超过 timeout_ms 返回 ESP_ERR_TIMEOUT,组保持 DRAINING 拒绝新调用。“排水”这个比喻在这里非常贴切:先关闸门,等在途水流排尽,再拆管道。
3. 从 schema 到模型可见的工具
3.1 双格式工具清单的生成
claw_cap_build_llm_tools_json() 把注册表翻译成模型 API 的 tools 数组:遍历槽位,逐个过授权检查,通过者输出 {name, description, input_schema} 三元组。这里藏着三个防御细节:描述超过 256 字节(CLAW_CAP_TOOL_DESCRIPTION_MAX)即截断,且尾部按 UTF-8 连续字节规则回退,保证不切出半个汉字;schema 缺 properties 时打告警——这类畸形定义会直接被 LLM API 以 400 拒绝;schema 解析失败则退回空对象而非丢弃整个工具。
wrap_for_responses_api 为真时再包一层 OpenAI Function Calling 格式({"type":"function","function":{"name","description","parameters"}}),让同一份注册表同时服务两种 API 方言。
清单如何进入请求?靠三个导出的 claw_core_context_provider_t:claw_cap_root_agent_tools_provider 与 claw_cap_sub_agent_tools_provider 分别以根代理、子代理身份收集(claw_agent_mgr.c 在创建 agent 时按身份挑选),claw_cap_tools_provider 是兼容别名。根代理与子代理因此能看到不同的工具集。
3.2 六道授权闸门与两级可见性
claw_cap_authorize_llm_tool_locked() 是一条短路的检查链,顺序本身就是安全优先级:槽位可用 → 有 execute → kind 为 CALLABLE/HYBRID → 带 CLAW_CAP_FLAG_CALLABLE_BY_LLM → 子代理不碰 ROOT_AGENT_ONLY → 组可见。授权失败有两种后果:调用被拒(输出错误文本),以及更彻底的——工具根本不出现在清单里,模型不知道这个工具存在。
可见性分两级:全局的 claw_cap_set_llm_visible_groups()(应用启动时由 app_capabilities.c 依据配置注入)与会话级的 claw_cap_set_session_llm_visible_groups();两级列表都为空时默认全可见,一旦设置,组必须在名单内。
调用者身份(claw_cap_caller_t)分 system、root_agent、sub_agent、console 四类,只有 agent 类走授权链——CLAW_CAP_FLAG_RESTRICTED 的 llm_config_command、session_command 因此只能由控制台触发,密钥管理等敏感操作对模型彻底隐身。
4. C 风格面向对象在能力层的落地
4.1 一张函数指针表统一十八般兵器
第 2 章已述,ESP-IDF 的 C 面向对象在此层的“ops 表”就是描述符里那四个函数指针(init/start/stop/execute)。18 个异构能力——HTTP 客户端、Lua 虚拟机、文件系统、四家 IM 的 WebSocket 网关——没有一个实现被 claw_cap #include,中枢只认 claw_cap_execute_fn 这一个签名。
与常见的 xxx_ops_t 按类型查表不同,这里的函数指针在注册时就与描述符一起按值入库:多态发生在入库瞬间,而非每次调用时查 vtable。代价是描述符不能在注册后换实现,收益是分发路径上零间接层开销、且 static const 表可整块进 flash。
kind 与旗标则承担了“接口语义”的角色:EVENT_SOURCE 类能力(如各 IM 网关)没有 execute 也能注册,只是永远不出现在工具清单里——同一套结构覆盖“被调用”与“发事件”两种角色。
4.2 回调注入:claw_cap 与 claw_core 互不相识
依赖方向是单向的:claw_cap.h include 了 claw_core.h,用到 claw_core_request_t、claw_core_context_provider_t 等类型;而 claw_core 头文件里找不到 claw_cap 的任何痕迹——它只有 claw_core_config_t 中的函数指针 call_cap(类型 claw_core_call_cap_fn)。
装配发生在应用层:app_claw.c 写下 core_config->call_cap = claw_cap_call_from_core;,从此推理循环的工具调用全部流经能力层。反向的数据流(工具清单进入请求上下文)同样经 provider 结构体注入。这是把依赖倒置原则落到实处的范例:claw_core 定义协议、claw_cap 实现协议、app 层缝合。测试时可以给 claw_core 塞一个假 call_cap,也可以给 claw_cap 喂假请求结构,两个中枢各自独立演化。
claw_cap_call_from_core() 还借一个带魔数(CLAW_CAP_CORE_CALL_USER_CTX_MAGIC)的 user_ctx 把真实的调用者身份(根代理还是子代理、agent_id、agent_type)从 manager 层带进能力层——授权检查因此能区分到具体 agent。
5. 十八个能力组件全景
5.1 能力清单表
下表覆盖 components/claw_capabilities/ 全部 18 个组件,信息取自各组件真实的描述符定义与注册代码:
| 组件 |
代表工具(注册名) |
职责 |
典型触发 |
| cap_agent_mgr |
spawn_agent、send_agent_followup、list_agents 等 6 个 |
子代理的派生、跟进、查看与回收(全部 ROOT_AGENT_ONLY) |
根代理并行拆分长任务 |
| cap_boards |
无运行时工具 |
构建期由 gen_board_metadata.yaml 生成 board_hardware_info 技能文档 |
用户询问板级硬件知识(经第8章技能) |
| cap_cli |
run_cli_command |
把控制台命令目录包装成一个动态描述的能力 |
执行诊断与维护命令 |
| cap_files |
read_file、write_file、list_dir 等 6 个 |
双根沙箱内的文本文件读写 |
读写笔记、保存 Lua 脚本 |
| cap_http_request |
http_request |
带域名白名单的通用 HTTP 调用 |
查询未内置 SDK 的开放接口 |
| cap_im_local |
local_gateway、local_send_message |
进程内 IM 网关(事件源)与本地发消息 |
本地调试客户端收发 |
| cap_im_platform |
feishu/qq/tg/wechat_gateway 及 send |
四大 IM 平台网关与消息、图片、文件发送(第9章) |
用户在 IM 说话、代理回复 |
| cap_llm_config |
llm_config_command |
/llm 配置命令处理(RESTRICTED,控制台专用) |
运维切换模型与密钥 |
| cap_llm_inspect |
inspect_image |
把本地图片路径交给视觉模型分析 |
用户发图提问 |
| cap_lua |
lua_run_script、lua_run_script_async 等 7 个 |
Lua 脚本作业的运行与管理 |
固件工具覆盖不到的动态逻辑 |
| cap_mcp_client |
mcp_list_tools、mcp_call_tool、mcp_discover |
发现并调用远程 MCP 服务器工具 |
接入外部工具生态(第10章) |
| cap_mcp_server |
反向:esp_mcp 工具 |
把固件功能经 MCP 暴露给远程客户端 |
手机或 PC 端 Agent 操控设备 |
| cap_router_mgr |
add/list/update/delete_router_rule 等 6 个 |
自动化路由规则的增删改查 |
“每天早上发天气”落地为规则 |
| cap_scheduler |
scheduler_add、scheduler_list 等 11 个 |
一次性/间隔/Cron 定时项管理 |
定时提醒与巡检 |
| cap_session_mgr |
session_command |
/session 会话命令处理(RESTRICTED) |
运维切换与清理会话 |
| cap_skill_mgr |
register_skill、activate_skill 等 4 个 |
技能 Markdown 的注册与激活 |
加载新技能进上下文(第8章) |
| cap_system |
get_system_info、get_current_time、restart_device |
系统信息、时间与计划重启 |
自检、报时、远程重启 |
| cap_web_search |
web_search |
经配置的搜索引擎联网检索 |
回答实时性问题 |
5.2 kind 与旗标的分布规律
清单里能读出三条规律。其一,EVENT_SOURCE 集中在“网关”类工具(local_gateway、feishu_gateway、qq_gateway、tg_gateway 等):它们只负责把外部世界变成事件流,不可被 LLM 调用,与同组件内可调用的 *_send_* 形成进出两个半区。
其二,HYBRID 只有 cap_scheduler 一家:CRUD 工具是它的 CALLABLE 面,到点触发事件是它的 EVENT_SOURCE 面——一个组件同时扮演“被管理的对象”与“主动的触发源”。
其三,旗标的分布体现权限梯度:ROOT_AGENT_ONLY 全部落在 cap_agent_mgr(子代理不得再生子代理,防递归爆炸);RESTRICTED 落在两个控制台命令;list_skill 的 cap_flags 为 0(LLM 不可见,供系统内部枚举技能)。
另有两个“体制外”成员:cap_boards 不注册任何运行时能力,它在构建期用 Python 从板级元数据生成技能文档,本质是技能系统的供料者(第 8 章、第 12 章);cap_mcp_server 不注册 claw_cap 描述符,而是把固件功能注册成 esp_mcp_tool 暴露给远程 MCP 客户端,方向与 cap_mcp_client 正好相反(第 10 章)。
6. 三个代表能力深析
6.1 cap_lua:自我编程的基石
固件工具集是编译期定死的,Lua 让 LLM 突破这层天花板:模型可以用 write_file 写一段脚本,再调 lua_run_script 或 lua_run_script_async 执行它——“写代码、跑代码、改代码”的闭环一旦成立,设备就获得了运行期自我扩展的能力,这正是“自我编程”的含义。
cap_lua 注册 7 个工具:同步执行、异步执行(返回 job_id,timeout_ms=0 表示直到取消)、列出/查看/追踪作业、停止单个或全部作业。安全与资源边界做成了“四件套”:
if (st.st_size <= 0 || st.st_size > CAP_LUA_MAX_SCRIPT_SIZE) { /* 脚本上限 64 KB */ }
L = luaL_newstate(); /* 每次运行全新状态,作业间零共享 */
luaL_openlibs(L);
cap_lua_load_registered_modules(L); /* 再叠加显式注册的固件模块 */
...
lua_pushcclosure(L, cap_lua_print_capture, 1);
lua_setglobal(L, "print"); /* print 改道为输出收集 */
lua_sethook(L, cap_lua_timeout_hook, LUA_MASKCOUNT, 100); /* 每100条指令检查 */
status = luaL_dofile(L, path);
第一件,入口收紧:脚本必须是合法 .lua 路径且不超过 64 KB;第二件,状态隔离:每个作业独立 luaL_newstate(),args 以 JSON 注入全局、输出被截断回收,作业崩溃不污染他人;第三件,协作式看门狗:cap_lua_timeout_hook 每 100 条 VM 指令醒来一次,先查停止请求、再查 deadline——同步执行默认 60 秒超时,异步默认不限时(守护型脚本靠 lua_stop_async_job 主动停);第四件,容量治理:最多 16 个作业槽、4 个并发,每个任务 12 KB 栈,name/exclusive 键加 replace 语义解决同名接管与互斥冲突。
回调钩子的取舍值得注意:Lua 没有抢占式终止,luaL_error 只能在指令边界生效,死循环外的 C 阻塞调用仍可能拖住任务——这是把灵活性放给脚本、把确定性留给容量的折中。
6.2 cap_files:双根沙箱的守门人
文件能力是“自我编程”闭环的另一半(写脚本的前提),也最容易变成安全漏洞。cap_files 的答案是一个极简的双根模型:
static const struct {
claw_path_root_t key;
bool writable;
} s_root_specs[] = {
{ CLAW_PATH_DATA, true }, /* 数据根:可写工作区(flash 或 SD) */
{ CLAW_PATH_SYSTEM, false }, /* 系统根:只读固件分区 */
};
cap_files_match_root() 用前缀包含判断路径落在哪个根(strncmp 加边界字符校验,杜绝 /data-x 冒充 /data),cap_files_resolve_path() 要求路径必须绝对、必须命中某根,否则报 path escapes roots;写类工具再检查 writable 位。
两个根不是编译期常量而是每次经 claw_paths_get() 活取——因为第 12 章《板级适配与配置服务》允许 DATA 根在 flash 与 SD 卡间迁移,能力层跟随运行期真实挂载点,而不是记住一份过期的快照。
注册本身也有自检:cap_files_register_group() 发现没有任何根配置时直接拒绝注册(返回 ESP_ERR_INVALID_STATE),宁可少一个能力,不开一个没有边界的口子。六个工具全部按同一模板实现(解析参数、校验路径、执行、格式化结果),schema 里的 description 反复强调 "under a writable directory"——工具说明本身就是给模型的第一道软约束。
6.3 cap_scheduler:把时间变成事件
调度器要回答一个直白的问题:LLM 只在被调用时醒着,谁来在凌晨三点叫醒它?cap_scheduler 把“时间”翻译成第 6 章《事件路由系统》能理解的事件。每个条目(cap_scheduler_item_t)声明 ONCE、INTERVAL 或 CRON 之一,附上要发布的事件类型、来源渠道、文本与负载 JSON。
后台任务以 tick_ms 为周期轮询 cap_scheduler_fire_due_entries(),到期即发布:
snprintf(event.event_id, sizeof(event.event_id), "sch-%08" PRIx32, ...);
strlcpy(event.source_cap, "cap_scheduler", sizeof(event.source_cap));
strlcpy(event.event_type, entry->item.event_type, sizeof(event.event_type));
snprintf(event.correlation_id, sizeof(event.correlation_id),
"%s:%" PRId64, entry->item.id, planned_time_ms); /* id:计划触发时刻 */
...
entry->status = CAP_SCHEDULER_STATUS_RUNNING;
publish_err = s_cap_scheduler.config.publish_event(&event); /* 注入的发布函数 */
发布函数不是直接调用事件路由器,而是 cap_scheduler_config_t 里的 claw_event_publish_fn 回调——组件自身不依赖路由器,装配期注入(第 3 章外壳完成缝合)。
三个细节见功力:其一,错过即放弃——若 now > next_fire + tick(设备刚从深睡或断网中恢复),只累计 missed_count 并刷新下次触发,绝不补发风暴;其二,时间不可信问题——冷启动后系统时钟可能是 1970 年,外壳在 SNTP 首次对时成功后调用 cap_scheduler_handle_time_sync() 重校准全部条目;其三,persist_after_fire 让运行态(run_count、下次触发时刻)随发布落盘,重启后无缝续跑。
至此 HYBRID 的意义完整浮现:LLM 用 scheduler_add 定义“何时发生”(CALLABLE 面),到点后调度器自己制造一次“发生了”(EVENT_SOURCE 面),两者拼成无需 LLM 在场的自动化。
7. 与相邻层的衔接与边界
7.1 向上衔接:第4章的工具调用循环
完整链路再走一遍:模型返回 tool_calls[] → 第 4 章 claw_core_append_tool_results_messages() 逐项取出工具名与参数 → 经 core->call_cap 指针进入 claw_cap_call_from_core() → claw_cap_call() 授权并分发 → 能力 execute 写 output → 结果组装成 tool 消息回传模型,直至 max_tool_iterations 上限。
两层的资源护栏互为犄角:能力层保证单次调用的 32 KB 输出边界与错误文本,核心层限制迭代次数与请求队列深度;授权被拒在能力层是 ESP_ERR_INVALID_STATE 加一句给模型看的解释,在循环里只是一条普通 tool 消息——模型换个工具重试,循环永不因权限而中断。
7.2 向旁让路:与第11章 Lua 模块层的分工
一句话概括:能力是 LLM 的手,Lua 模块是驱动的脚。cap_lua 作为能力,负责“运行脚本”这件事本身;GPIO、I2C、UART、RMT 等硬件绑定模块全部住在 lua_modules/ 的 lua_driver_* 组件里,通过 cap_lua_register_module() 登记进虚拟机(第 11 章)。
能力层与脚本层的组合产出真正的智能硬件行为:cap_files 写脚本、cap_scheduler 定时跑、驱动模块提供 API、cap_lua 提供带超时与配额的执行环境。最后把本章放回 12 章地图:能力向上承接第 4 章的推理循环,向下借第 6 章事件路由对外发声,与第 8 章技能(知识注入)、第 9 章 IM(双向通道)、第 10 章 MCP(协议互通)、第 12 章板级配置(路径与裁剪)环环相扣——它是 esp-claw 从“会聊天的芯片”变成“会做事的设备”的那道闸门。
参考文献
[1] esp-claw 官方仓库:https://github.com/espressif/esp-claw
[2] OpenAI Function Calling 指南:https://platform.openai.com/docs/guides/function-calling
[3] JSON Schema 规范:https://json-schema.org/
[4] Lua 5.4 参考手册(调试钩子与 lua_sethook):https://www.lua.org/manual/5.4/manual.html#4.9
[5] Espressif Lua 组件:https://components.espressif.com/components/espressif/lua
[6] Model Context Protocol 规范:https://modelcontextprotocol.io/