mirror of
https://github.com/m5stack/ESP-Claw.git
synced 2026-05-20 11:51:49 -07:00
- Add `cap_lua_add_package_path_dir()` API to register shared Lua library directories at runtime - Automatically prepend the running script's own directory and all registered shared dirs to `package.path` before execution - Script-local directory takes priority over shared library dirs - Validate paths: must be absolute, no ".." traversal, within size limit - Deduplicate entries in the path dir linked list
713 lines
23 KiB
C
713 lines
23 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include "app_capabilities.h"
|
|
|
|
#include "app_lua_modules.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#if CONFIG_APP_CLAW_CAP_FILES
|
|
#include "cap_files.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_FEISHU
|
|
#include "cap_im_feishu.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_QQ
|
|
#include "cap_im_qq.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_TG
|
|
#include "cap_im_tg.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_WECHAT
|
|
#include "cap_im_wechat.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_LLM_INSPECT
|
|
#include "cap_llm_inspect.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_LUA
|
|
#include "cap_lua.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_MCP_CLIENT
|
|
#include "cap_mcp_client.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_MCP_SERVER
|
|
#include "cap_mcp_server.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_ROUTER_MGR
|
|
#include "cap_router_mgr.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SCHEDULER
|
|
#include "cap_scheduler.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SESSION_MGR
|
|
#include "cap_session_mgr.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SKILL_MGR
|
|
#include "cap_skill_mgr.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SYSTEM
|
|
#include "cap_system.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_TIME
|
|
#include "cap_time.h"
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_WEB_SEARCH
|
|
#include "cap_web_search.h"
|
|
#endif
|
|
#include "claw_cap.h"
|
|
#include "claw_memory.h"
|
|
#include "esp_check.h"
|
|
#include "esp_log.h"
|
|
|
|
static const char *TAG = "app_capabilities";
|
|
|
|
#define APP_IM_ATTACHMENT_MAX_BYTES (2 * 1024 * 1024)
|
|
|
|
typedef esp_err_t (*app_cap_prepare_fn)(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths);
|
|
typedef esp_err_t (*app_cap_register_fn)(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths);
|
|
|
|
typedef struct {
|
|
const char *group_id;
|
|
const char *display_name;
|
|
const char *label;
|
|
bool llm_visible_by_default;
|
|
app_cap_prepare_fn prepare;
|
|
app_cap_register_fn reg;
|
|
} app_capability_group_entry_t;
|
|
|
|
static bool app_cap_groups_config_empty(const char *value)
|
|
{
|
|
size_t i;
|
|
|
|
if (!value) {
|
|
return true;
|
|
}
|
|
|
|
for (i = 0; value[i]; i++) {
|
|
if (!isspace((unsigned char)value[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static char *app_cap_trim_token(char *token)
|
|
{
|
|
char *end;
|
|
|
|
if (!token) {
|
|
return NULL;
|
|
}
|
|
|
|
while (*token && isspace((unsigned char)*token)) {
|
|
token++;
|
|
}
|
|
|
|
end = token + strlen(token);
|
|
while (end > token && isspace((unsigned char)end[-1])) {
|
|
end--;
|
|
}
|
|
*end = '\0';
|
|
return token;
|
|
}
|
|
|
|
static int app_cap_find_group_entry(const app_capability_group_entry_t *entries,
|
|
size_t count,
|
|
const char *group_id)
|
|
{
|
|
size_t i;
|
|
|
|
if (!entries || !group_id || !group_id[0]) {
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (entries[i].group_id && strcmp(entries[i].group_id, group_id) == 0) {
|
|
return (int)i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static esp_err_t app_cap_build_group_map(const char *configured_groups,
|
|
const app_capability_group_entry_t *entries,
|
|
size_t entry_count,
|
|
bool *selected_map,
|
|
bool select_all_if_empty,
|
|
bool use_llm_defaults_if_empty)
|
|
{
|
|
char *groups_copy = NULL;
|
|
char *saveptr = NULL;
|
|
char *token = NULL;
|
|
size_t i;
|
|
|
|
if (!entries || !selected_map) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
memset(selected_map, 0, entry_count * sizeof(selected_map[0]));
|
|
|
|
if (app_cap_groups_config_empty(configured_groups)) {
|
|
for (i = 0; i < entry_count; i++) {
|
|
selected_map[i] = select_all_if_empty || (use_llm_defaults_if_empty &&
|
|
entries[i].llm_visible_by_default);
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
groups_copy = malloc(strlen(configured_groups) + 1);
|
|
if (!groups_copy) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
memcpy(groups_copy, configured_groups, strlen(configured_groups) + 1);
|
|
|
|
for (token = strtok_r(groups_copy, ",", &saveptr);
|
|
token;
|
|
token = strtok_r(NULL, ",", &saveptr)) {
|
|
char *trimmed = app_cap_trim_token(token);
|
|
int index;
|
|
|
|
if (!trimmed || !trimmed[0]) {
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(trimmed, "__none__") == 0 || strcmp(trimmed, "none") == 0) {
|
|
continue;
|
|
}
|
|
|
|
index = app_cap_find_group_entry(entries, entry_count, trimmed);
|
|
if (index < 0) {
|
|
ESP_LOGW(TAG, "Ignoring unknown or unavailable capability group: %s", trimmed);
|
|
continue;
|
|
}
|
|
|
|
selected_map[index] = true;
|
|
}
|
|
|
|
free(groups_copy);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t app_cap_register_entry(const app_capability_group_entry_t *entry,
|
|
const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
claw_cap_list_t cap_list;
|
|
claw_cap_group_list_t group_list;
|
|
esp_err_t err;
|
|
|
|
if (!entry || !entry->reg) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
err = entry->reg(config, paths);
|
|
cap_list = claw_cap_list();
|
|
group_list = claw_cap_list_groups();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "%s failed: %s (groups=%u, caps=%u)",
|
|
entry->label, esp_err_to_name(err),
|
|
(unsigned)group_list.count,
|
|
(unsigned)cap_list.count);
|
|
return err;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "%s ok (groups=%u, caps=%u)",
|
|
entry->label,
|
|
(unsigned)group_list.count,
|
|
(unsigned)cap_list.count);
|
|
return ESP_OK;
|
|
}
|
|
|
|
#if CONFIG_APP_CLAW_CAP_FILES
|
|
static esp_err_t app_cap_prepare_files(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
return cap_files_set_base_dir(paths->fatfs_base_path);
|
|
}
|
|
|
|
static esp_err_t app_cap_register_files(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_files_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_IM_QQ
|
|
static esp_err_t app_cap_prepare_im_qq(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
ESP_RETURN_ON_ERROR(cap_im_qq_set_attachment_config(&(cap_im_qq_attachment_config_t) {
|
|
.storage_root_dir = paths->im_attachment_root,
|
|
.max_inbound_file_bytes = APP_IM_ATTACHMENT_MAX_BYTES,
|
|
.enable_inbound_attachments = true,
|
|
}),
|
|
TAG, "Failed to set QQ attachment config");
|
|
|
|
if (config->qq_app_id[0] && config->qq_app_secret[0]) {
|
|
ESP_RETURN_ON_ERROR(cap_im_qq_set_credentials(config->qq_app_id, config->qq_app_secret),
|
|
TAG, "Failed to set QQ credentials");
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t app_cap_register_im_qq(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_im_qq_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_IM_FEISHU
|
|
static esp_err_t app_cap_prepare_im_feishu(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
ESP_RETURN_ON_ERROR(cap_im_feishu_set_attachment_config(&(cap_im_feishu_attachment_config_t) {
|
|
.storage_root_dir = paths->im_attachment_root,
|
|
.max_inbound_file_bytes = APP_IM_ATTACHMENT_MAX_BYTES,
|
|
.enable_inbound_attachments = true,
|
|
}),
|
|
TAG, "Failed to set Feishu attachment config");
|
|
|
|
if (config->feishu_app_id[0] && config->feishu_app_secret[0]) {
|
|
ESP_RETURN_ON_ERROR(cap_im_feishu_set_credentials(config->feishu_app_id,
|
|
config->feishu_app_secret),
|
|
TAG, "Failed to set Feishu credentials");
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t app_cap_register_im_feishu(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_im_feishu_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_IM_TG
|
|
static esp_err_t app_cap_prepare_im_tg(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
ESP_RETURN_ON_ERROR(cap_im_tg_set_attachment_config(&(cap_im_tg_attachment_config_t) {
|
|
.storage_root_dir = paths->im_attachment_root,
|
|
.max_inbound_file_bytes = APP_IM_ATTACHMENT_MAX_BYTES,
|
|
.enable_inbound_attachments = true,
|
|
}),
|
|
TAG, "Failed to set Telegram attachment config");
|
|
|
|
if (config->tg_bot_token[0]) {
|
|
ESP_RETURN_ON_ERROR(cap_im_tg_set_token(config->tg_bot_token),
|
|
TAG, "Failed to set Telegram bot token");
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t app_cap_register_im_tg(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_im_tg_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_IM_WECHAT
|
|
static esp_err_t app_cap_prepare_im_wechat(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
ESP_RETURN_ON_ERROR(cap_im_wechat_set_attachment_config(&(cap_im_wechat_attachment_config_t) {
|
|
.storage_root_dir = paths->im_attachment_root,
|
|
.max_inbound_file_bytes = APP_IM_ATTACHMENT_MAX_BYTES,
|
|
.enable_inbound_attachments = true,
|
|
}),
|
|
TAG, "Failed to set WeChat attachment config");
|
|
|
|
if (config->wechat_token[0] && config->wechat_base_url[0]) {
|
|
ESP_RETURN_ON_ERROR(cap_im_wechat_set_client_config(&(cap_im_wechat_client_config_t) {
|
|
.token = config->wechat_token,
|
|
.base_url = config->wechat_base_url,
|
|
.cdn_base_url = config->wechat_cdn_base_url,
|
|
.account_id = config->wechat_account_id,
|
|
}),
|
|
TAG, "Failed to set WeChat client config");
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t app_cap_register_im_wechat(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_im_wechat_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_SCHEDULER
|
|
static esp_err_t app_cap_register_scheduler(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_scheduler_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_LUA
|
|
static esp_err_t app_cap_prepare_lua(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
char path_temp[96];
|
|
snprintf(path_temp, sizeof(path_temp), "%s/builtin", paths->lua_root_dir);
|
|
cap_lua_add_package_path_dir(path_temp);
|
|
|
|
return app_lua_modules_register(config, paths->fatfs_base_path);
|
|
}
|
|
|
|
static esp_err_t app_cap_register_lua(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
return cap_lua_register_group(paths->lua_root_dir);
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_MCP_CLIENT
|
|
static esp_err_t app_cap_register_mcp_client(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_mcp_client_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_MCP_SERVER
|
|
static esp_err_t app_cap_register_mcp_server(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_mcp_server_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_SKILL_MGR
|
|
static esp_err_t app_cap_register_skill_mgr(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_skill_mgr_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_SYSTEM
|
|
static esp_err_t app_cap_register_system(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_system_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_MEMORY_MODE_FULL
|
|
static esp_err_t app_cap_register_memory(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return claw_memory_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_TIME
|
|
static esp_err_t app_cap_register_time(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_time_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_LLM_INSPECT
|
|
static esp_err_t app_cap_register_llm_inspect(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_llm_inspect_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_WEB_SEARCH
|
|
static esp_err_t app_cap_prepare_web_search(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)paths;
|
|
|
|
if (config->search_brave_key[0]) {
|
|
ESP_RETURN_ON_ERROR(cap_web_search_set_brave_key(config->search_brave_key),
|
|
TAG, "Failed to set Brave search key");
|
|
}
|
|
|
|
if (config->search_tavily_key[0]) {
|
|
ESP_RETURN_ON_ERROR(cap_web_search_set_tavily_key(config->search_tavily_key),
|
|
TAG, "Failed to set Tavily search key");
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t app_cap_register_web_search(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_web_search_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_ROUTER_MGR
|
|
static esp_err_t app_cap_register_router_mgr(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_router_mgr_register_group();
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_APP_CLAW_CAP_SESSION_MGR
|
|
static esp_err_t app_cap_register_session_mgr(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
(void)config;
|
|
(void)paths;
|
|
return cap_session_mgr_register_group();
|
|
}
|
|
#endif
|
|
|
|
static const app_capability_group_entry_t s_capability_group_entries[] = {
|
|
#if CONFIG_APP_CLAW_CAP_IM_QQ
|
|
{ "cap_im_qq", "QQ", "Register QQ cap", false, app_cap_prepare_im_qq, app_cap_register_im_qq },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_FEISHU
|
|
{ "cap_im_feishu", "Feishu", "Register Feishu cap", false, app_cap_prepare_im_feishu, app_cap_register_im_feishu },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_TG
|
|
{ "cap_im_tg", "Telegram", "Register Telegram cap", false, app_cap_prepare_im_tg, app_cap_register_im_tg },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_WECHAT
|
|
{ "cap_im_wechat", "WeChat", "Register WeChat cap", false, app_cap_prepare_im_wechat, app_cap_register_im_wechat },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_FILES
|
|
{ "cap_files", "Files", "Register files cap", true, app_cap_prepare_files, app_cap_register_files },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SCHEDULER
|
|
{ "cap_scheduler", "Scheduler", "Register scheduler cap", false, NULL, app_cap_register_scheduler },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_LUA
|
|
{ "cap_lua", "Lua", "Register Lua cap", true, app_cap_prepare_lua, app_cap_register_lua },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_MCP_CLIENT
|
|
{ "cap_mcp_client", "MCP Client", "Register MCP client cap", false, NULL, app_cap_register_mcp_client },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_MCP_SERVER
|
|
{ "cap_mcp_server", "MCP Server", "Register MCP server cap", false, NULL, app_cap_register_mcp_server },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SKILL_MGR
|
|
{ "cap_skill", "Skill Manager", "Register skill cap", true, NULL, app_cap_register_skill_mgr },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SYSTEM
|
|
{ "cap_system", "System", "Register system cap", true, NULL, app_cap_register_system },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_MEMORY_MODE_FULL
|
|
{ "claw_memory", "Memory", "Register claw_memory group", true, NULL, app_cap_register_memory },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_TIME
|
|
{ "cap_time", "Time", "Register time cap", false, NULL, app_cap_register_time },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_LLM_INSPECT
|
|
{ "cap_llm_inspect", "LLM Inspect", "Register LLM inspect cap", false, NULL, app_cap_register_llm_inspect },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_WEB_SEARCH
|
|
{ "cap_web_search", "Web Search", "Register web search cap", false, app_cap_prepare_web_search, app_cap_register_web_search },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_ROUTER_MGR
|
|
{ "cap_router_mgr", "Router Manager", "Register router manager cap", false, NULL, app_cap_register_router_mgr },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SESSION_MGR
|
|
{ "cap_session_mgr", "Session Manager", "Register session manager cap", false, NULL, app_cap_register_session_mgr },
|
|
#endif
|
|
};
|
|
|
|
static const app_capability_group_info_t s_capability_group_infos[] = {
|
|
#if CONFIG_APP_CLAW_CAP_IM_QQ
|
|
{ "cap_im_qq", "QQ", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_FEISHU
|
|
{ "cap_im_feishu", "Feishu", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_TG
|
|
{ "cap_im_tg", "Telegram", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_IM_WECHAT
|
|
{ "cap_im_wechat", "WeChat", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_FILES
|
|
{ "cap_files", "Files", true },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SCHEDULER
|
|
{ "cap_scheduler", "Scheduler", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_LUA
|
|
{ "cap_lua", "Lua", true },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_MCP_CLIENT
|
|
{ "cap_mcp_client", "MCP Client", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_MCP_SERVER
|
|
{ "cap_mcp_server", "MCP Server", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SKILL_MGR
|
|
{ "cap_skill", "Skill Manager", true },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SYSTEM
|
|
{ "cap_system", "System", true },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_MEMORY_MODE_FULL
|
|
{ "claw_memory", "Memory", true },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_TIME
|
|
{ "cap_time", "Time", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_LLM_INSPECT
|
|
{ "cap_llm_inspect", "LLM Inspect", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_WEB_SEARCH
|
|
{ "cap_web_search", "Web Search", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_ROUTER_MGR
|
|
{ "cap_router_mgr", "Router Manager", false },
|
|
#endif
|
|
#if CONFIG_APP_CLAW_CAP_SESSION_MGR
|
|
{ "cap_session_mgr", "Session Manager", false },
|
|
#endif
|
|
};
|
|
|
|
esp_err_t app_capabilities_init(const app_claw_config_t *config,
|
|
const app_claw_storage_paths_t *paths)
|
|
{
|
|
const size_t entry_count = sizeof(s_capability_group_entries) / sizeof(s_capability_group_entries[0]);
|
|
bool *enabled_map = NULL;
|
|
bool *llm_visible_map = NULL;
|
|
const char **llm_visible_groups = NULL;
|
|
size_t llm_visible_group_count = 0;
|
|
size_t i;
|
|
esp_err_t ret = ESP_OK;
|
|
|
|
if (!config || !paths) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
ESP_RETURN_ON_ERROR(claw_cap_init(), TAG, "Failed to init claw_cap");
|
|
|
|
enabled_map = calloc(entry_count > 0 ? entry_count : 1, sizeof(enabled_map[0]));
|
|
llm_visible_map = calloc(entry_count > 0 ? entry_count : 1, sizeof(llm_visible_map[0]));
|
|
llm_visible_groups = calloc(entry_count > 0 ? entry_count : 1, sizeof(llm_visible_groups[0]));
|
|
if (!enabled_map || !llm_visible_map || !llm_visible_groups) {
|
|
free(enabled_map);
|
|
free(llm_visible_map);
|
|
free(llm_visible_groups);
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
ESP_GOTO_ON_ERROR(app_cap_build_group_map(config->enabled_cap_groups,
|
|
s_capability_group_entries,
|
|
entry_count,
|
|
enabled_map,
|
|
true,
|
|
false),
|
|
cleanup, TAG, "Failed to parse capability whitelist");
|
|
ESP_GOTO_ON_ERROR(app_cap_build_group_map(config->llm_visible_cap_groups,
|
|
s_capability_group_entries,
|
|
entry_count,
|
|
llm_visible_map,
|
|
false,
|
|
true),
|
|
cleanup, TAG, "Failed to parse LLM-visible capability groups");
|
|
|
|
for (i = 0; i < entry_count; i++) {
|
|
const app_capability_group_entry_t *entry = &s_capability_group_entries[i];
|
|
|
|
if (!enabled_map[i]) {
|
|
ESP_LOGI(TAG, "Skipping capability group at init: %s", entry->group_id);
|
|
continue;
|
|
}
|
|
|
|
if (entry->prepare) {
|
|
ESP_GOTO_ON_ERROR(entry->prepare(config, paths),
|
|
cleanup, TAG, "Failed to prepare %s", entry->group_id);
|
|
}
|
|
|
|
ESP_GOTO_ON_ERROR(app_cap_register_entry(entry, config, paths),
|
|
cleanup, TAG, "Failed to register %s", entry->group_id);
|
|
|
|
if (llm_visible_map[i]) {
|
|
llm_visible_groups[llm_visible_group_count++] = entry->group_id;
|
|
}
|
|
}
|
|
|
|
ret = claw_cap_set_llm_visible_groups(llm_visible_groups, llm_visible_group_count);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to set LLM-visible capability groups: %s", esp_err_to_name(ret));
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = claw_cap_start_all();
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to start capabilities: %s", esp_err_to_name(ret));
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
free(enabled_map);
|
|
free(llm_visible_map);
|
|
free(llm_visible_groups);
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t app_capabilities_get_compiled_groups(const app_capability_group_info_t **groups,
|
|
size_t *count)
|
|
{
|
|
if (!groups || !count) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
*groups = s_capability_group_infos;
|
|
*count = sizeof(s_capability_group_infos) / sizeof(s_capability_group_infos[0]);
|
|
return ESP_OK;
|
|
}
|