diff --git a/README.md b/README.md index 1afe0ee..309efa3 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ esp-claw/ │ ├── claw_capabilities/ │ │ ├── cap_im_feishu / cap_im_qq / cap_im_tg / cap_im_wechat │ │ ├── cap_mcp_client / cap_mcp_server -│ │ ├── cap_lua / cap_skill / cap_scheduler / cap_router_mgr +│ │ ├── cap_lua / cap_skill_mgr / cap_scheduler / cap_router_mgr │ │ ├── cap_files / cap_time / cap_web_search / cap_cli │ │ └── ... # More agent-facing capabilities │ └── lua_modules/ diff --git a/README_CN.md b/README_CN.md index cdc82eb..454c939 100644 --- a/README_CN.md +++ b/README_CN.md @@ -157,7 +157,7 @@ esp-claw/ │ ├── claw_capabilities/ │ │ ├── cap_im_feishu / cap_im_qq / cap_im_tg / cap_im_wechat │ │ ├── cap_mcp_client / cap_mcp_server -│ │ ├── cap_lua / cap_skill / cap_scheduler / cap_router_mgr +│ │ ├── cap_lua / cap_skill_mgr / cap_scheduler / cap_router_mgr │ │ ├── cap_files / cap_time / cap_web_search / cap_cli │ │ └── ... # More agent-facing capabilities │ └── lua_modules/ diff --git a/application/basic_demo/fatfs_image/automation/automations.json b/application/basic_demo/fatfs_image/router_rules/router_rules.json similarity index 68% rename from application/basic_demo/fatfs_image/automation/automations.json rename to application/basic_demo/fatfs_image/router_rules/router_rules.json index ca4f398..d1972d2 100644 --- a/application/basic_demo/fatfs_image/automation/automations.json +++ b/application/basic_demo/fatfs_image/router_rules/router_rules.json @@ -28,15 +28,15 @@ ] }, { - "id": "im_hello_claw", - "description": "Intercept IM hello! esp-claw and reply directly.Just for demonstration", + "id": "im_hello_clawgent", + "description": "Intercept IM hello! clawgent and reply directly.Just for demonstration", "enabled": true, "ack": "{{event.source_channel}} hello intercepted", "match": { "event_type": "message", "event_key": "text", "content_type": "text", - "text": "hello! esp-claw" + "text": "hello! clawgent" }, "actions": [ { @@ -90,31 +90,6 @@ } ] }, - { - "id": "mcp_state_report_to_script", - "description": "Route MCP state reports into Lua for deterministic handling.", - "enabled": true, - "ack": "mcp state report scripted", - "match": { - "event_type": "mcp_device_state_report", - "source_cap": "mcp_server", - "source_channel": "mcp" - }, - "actions": [ - { - "type": "run_script", - "input": { - "path": "/fatfs/data/lua/hello.lua", - "async": false, - "event_type": "{{event.event_type}}", - "source_channel": "{{event.source_channel}}", - "text": "{{event.text}}", - "payload_json": "{{event.payload_json}}", - "chat_id": "{{event.chat_id}}" - } - } - ] - }, { "id": "im_any_message_agent", "description": "Route IM text messages to the agent.", @@ -131,30 +106,15 @@ "type": "run_agent", "input": { "target_channel": "{{event.source_channel}}", - "target_chat_id": "{{event.chat_id}}", "session_policy": "chat" } - } - ] - }, - { - "id": "agent_response_send_message", - "description": "Deliver final agent responses to the original IM chat.", - "enabled": true, - "consume_on_match": true, - "ack": "{{event.source_channel}} agent response sent", - "match": { - "source_cap": "claw_core", - "event_type": "agent_response", - "content_type": "text" - }, - "actions": [ + }, { "type": "send_message", "input": { "channel": "{{event.source_channel}}", "chat_id": "{{event.chat_id}}", - "message": "{{event.text}}" + "message": "{{last.output}}" } } ] diff --git a/application/basic_demo/fatfs_image/lua/audio_demo.lua b/application/basic_demo/fatfs_image/scripts/audio_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/audio_demo.lua rename to application/basic_demo/fatfs_image/scripts/audio_demo.lua diff --git a/application/basic_demo/fatfs_image/lua/audio_play_test_wav.lua b/application/basic_demo/fatfs_image/scripts/audio_play_test_wav.lua similarity index 85% rename from application/basic_demo/fatfs_image/lua/audio_play_test_wav.lua rename to application/basic_demo/fatfs_image/scripts/audio_play_test_wav.lua index 757394b..271b315 100644 --- a/application/basic_demo/fatfs_image/lua/audio_play_test_wav.lua +++ b/application/basic_demo/fatfs_image/scripts/audio_play_test_wav.lua @@ -11,9 +11,6 @@ if not output_codec then return end -print(string.format("[audio_play_test_wav] output_codec=%s, output_rate=%d, output_channels=%d, output_bits=%d", - output_codec, output_rate, output_channels, output_bits)) - local output, out_err = audio.new_output(output_codec, output_rate, output_channels, output_bits) if not output then print("[audio_play_test_wav] ERROR: new_output failed: " .. tostring(out_err)) diff --git a/application/basic_demo/fatfs_image/lua/button_demo.lua b/application/basic_demo/fatfs_image/scripts/button_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/button_demo.lua rename to application/basic_demo/fatfs_image/scripts/button_demo.lua diff --git a/application/basic_demo/fatfs_image/lua/button_play_test_wav.lua b/application/basic_demo/fatfs_image/scripts/button_play_test_wav.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/button_play_test_wav.lua rename to application/basic_demo/fatfs_image/scripts/button_play_test_wav.lua diff --git a/application/basic_demo/fatfs_image/lua/camera_capture_demo.lua b/application/basic_demo/fatfs_image/scripts/camera_capture_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/camera_capture_demo.lua rename to application/basic_demo/fatfs_image/scripts/camera_capture_demo.lua diff --git a/application/basic_demo/fatfs_image/lua/camera_preview_demo.lua b/application/basic_demo/fatfs_image/scripts/camera_preview_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/camera_preview_demo.lua rename to application/basic_demo/fatfs_image/scripts/camera_preview_demo.lua diff --git a/application/basic_demo/fatfs_image/lua/display_demo.lua b/application/basic_demo/fatfs_image/scripts/display_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/display_demo.lua rename to application/basic_demo/fatfs_image/scripts/display_demo.lua diff --git a/application/basic_demo/fatfs_image/lua/hello.lua b/application/basic_demo/fatfs_image/scripts/hello.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/hello.lua rename to application/basic_demo/fatfs_image/scripts/hello.lua diff --git a/application/basic_demo/fatfs_image/lua/lcd_touch_demo.lua b/application/basic_demo/fatfs_image/scripts/lcd_touch_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/lcd_touch_demo.lua rename to application/basic_demo/fatfs_image/scripts/lcd_touch_demo.lua diff --git a/application/basic_demo/fatfs_image/lua/lcd_touch_paint.lua b/application/basic_demo/fatfs_image/scripts/lcd_touch_paint.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/lcd_touch_paint.lua rename to application/basic_demo/fatfs_image/scripts/lcd_touch_paint.lua diff --git a/application/basic_demo/fatfs_image/lua/led_strip_demo.lua b/application/basic_demo/fatfs_image/scripts/led_strip_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/lua/led_strip_demo.lua rename to application/basic_demo/fatfs_image/scripts/led_strip_demo.lua diff --git a/application/basic_demo/fatfs_image/lua/random_demo_stress.lua b/application/basic_demo/fatfs_image/scripts/random_demo_stress.lua similarity index 97% rename from application/basic_demo/fatfs_image/lua/random_demo_stress.lua rename to application/basic_demo/fatfs_image/scripts/random_demo_stress.lua index 83bfdbe..d784e67 100644 --- a/application/basic_demo/fatfs_image/lua/random_demo_stress.lua +++ b/application/basic_demo/fatfs_image/scripts/random_demo_stress.lua @@ -2,7 +2,7 @@ local delay = require("delay") local esp_heap = require("esp_heap") local storage = require("storage") -local LUA_ROOT = storage.join_path(storage.get_root_dir(), "lua") +local LUA_ROOT = storage.join_path(storage.get_root_dir(), "scripts") local caps = esp_heap.caps local iterations = (args and args.iterations) or 20 @@ -84,7 +84,7 @@ local function print_task_watermarks(tag) end local function run_demo(name) - local path = storage.join_path(LUA_ROOT, name) + local path = LUA_ROOT .. "/" .. name local original_print = print local saw_error_output = false local result_ok = false diff --git a/application/basic_demo/main/app_claw.c b/application/basic_demo/main/app_claw.c index 368bc2d..3d83047 100644 --- a/application/basic_demo/main/app_claw.c +++ b/application/basic_demo/main/app_claw.c @@ -6,6 +6,7 @@ #include "app_claw.h" #include +#include #include #include @@ -22,9 +23,9 @@ #include "cap_mcp_client.h" #include "cap_mcp_server.h" #include "cap_router_mgr.h" -#include "cap_session_mng.h" +#include "cap_session_mgr.h" #include "cap_scheduler.h" -#include "cap_skill.h" +#include "cap_skill_mgr.h" #include "cap_time.h" #include "cap_web_search.h" #include "claw_event_publisher.h" @@ -45,15 +46,6 @@ static const char *const BASIC_DEMO_LLM_VISIBLE_GROUPS[] = { "cap_skill", }; -#define BASIC_DEMO_MEMORY_SESSION_ROOT "/fatfs/data/sessions" -#define BASIC_DEMO_MEMORY_LONG_TERM_PATH "/fatfs/data/memory/MEMORY.md" -#define BASIC_DEMO_SKILLS_ROOT_DIR "/fatfs/data/skills" -#define BASIC_DEMO_LUA_ROOT_DIR "/fatfs/data/lua" -#define BASIC_DEMO_FATFS_BASE_PATH "/fatfs/data" -#define BASIC_DEMO_AUTOMATION_RULES_PATH "/fatfs/data/automation/automations.json" -#define BASIC_DEMO_SCHEDULER_RULES_PATH "/fatfs/data/scheduler/schedules.json" -#define BASIC_DEMO_SCHEDULER_STATE_PATH "/fatfs/data/scheduler/scheduler_state.json" -#define BASIC_DEMO_IM_ATTACHMENT_ROOT "/fatfs/data/inbox" #define BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES (2 * 1024 * 1024) #define BASIC_DEMO_SYSTEM_PROMPT \ @@ -67,6 +59,36 @@ static const char *const BASIC_DEMO_LLM_VISIBLE_GROUPS[] = { esp_err_t basic_demo_cli_start(void); +typedef struct { + char memory_session_root[64]; + char memory_long_term_path[96]; + char skills_root_dir[64]; + char lua_root_dir[64]; + char router_rules_path[96]; + char scheduler_rules_path[96]; + char scheduler_state_path[96]; + char im_attachment_root[64]; +} basic_demo_paths_t; + +static esp_err_t basic_demo_init_paths(basic_demo_paths_t *paths) +{ + const char *base = basic_demo_fatfs_base_path; + if (!paths || !base || base[0] != '/') { + return ESP_ERR_INVALID_STATE; + } + if (snprintf(paths->memory_session_root, sizeof(paths->memory_session_root), "%s/sessions", base) >= sizeof(paths->memory_session_root) || + snprintf(paths->memory_long_term_path, sizeof(paths->memory_long_term_path), "%s/memory/MEMORY.md", base) >= sizeof(paths->memory_long_term_path) || + snprintf(paths->skills_root_dir, sizeof(paths->skills_root_dir), "%s/skills", base) >= sizeof(paths->skills_root_dir) || + snprintf(paths->lua_root_dir, sizeof(paths->lua_root_dir), "%s/scripts", base) >= sizeof(paths->lua_root_dir) || + snprintf(paths->router_rules_path, sizeof(paths->router_rules_path), "%s/router_rules/router_rules.json", base) >= sizeof(paths->router_rules_path) || + snprintf(paths->scheduler_rules_path, sizeof(paths->scheduler_rules_path), "%s/scheduler/schedules.json", base) >= sizeof(paths->scheduler_rules_path) || + snprintf(paths->scheduler_state_path, sizeof(paths->scheduler_state_path), "%s/scheduler/scheduler_state.json", base) >= sizeof(paths->scheduler_state_path) || + snprintf(paths->im_attachment_root, sizeof(paths->im_attachment_root), "%s/inbox", base) >= sizeof(paths->im_attachment_root)) { + return ESP_ERR_INVALID_SIZE; + } + return ESP_OK; +} + static bool basic_demo_time_network_ready(void *ctx) { (void)ctx; @@ -89,11 +111,11 @@ static void basic_demo_time_sync_success(bool had_valid_time, void *ctx) } } -static esp_err_t init_memory(void) +static esp_err_t init_memory(const basic_demo_paths_t *paths) { claw_memory_config_t memory_config = { - .session_root_dir = BASIC_DEMO_MEMORY_SESSION_ROOT, - .long_term_memory_path = BASIC_DEMO_MEMORY_LONG_TERM_PATH, + .session_root_dir = paths->memory_session_root, + .long_term_memory_path = paths->memory_long_term_path, .max_session_messages = 20, .max_message_chars = 1024, }; @@ -108,19 +130,18 @@ static esp_err_t init_memory(void) return ESP_OK; } -static esp_err_t init_skills(void) +static esp_err_t init_skills(const basic_demo_paths_t *paths) { - ESP_RETURN_ON_ERROR(claw_skill_init(&(claw_skill_config_t) { - .skills_root_dir = BASIC_DEMO_SKILLS_ROOT_DIR, - .session_state_root_dir = BASIC_DEMO_MEMORY_SESSION_ROOT, - .max_file_bytes = 10*1024, - }), - TAG, - "Failed to init claw_skill"); + ESP_RETURN_ON_ERROR(claw_skill_init(&(claw_skill_config_t){ + .skills_root_dir = paths->skills_root_dir, + .session_state_root_dir = paths->memory_session_root, + .max_file_bytes = 10 * 1024, + }), + TAG, "Failed to init claw_skill"); return ESP_OK; } -static esp_err_t init_capabilities(const basic_demo_settings_t *settings) +static esp_err_t init_capabilities(const basic_demo_settings_t *settings, const basic_demo_paths_t *paths) { claw_cap_config_t cap_config = { .max_capabilities = 64, @@ -129,138 +150,83 @@ static esp_err_t init_capabilities(const basic_demo_settings_t *settings) ESP_RETURN_ON_ERROR(claw_cap_init(&cap_config), TAG, "Failed to init claw_cap"); - ESP_RETURN_ON_ERROR(cap_time_set_timezone(settings->time_timezone), - TAG, - "Failed to set time cap timezone"); - ESP_RETURN_ON_ERROR(cap_files_set_base_dir(BASIC_DEMO_FATFS_BASE_PATH), - TAG, - "Failed to set files cap base dir"); - ESP_RETURN_ON_ERROR(cap_lua_set_base_dir(BASIC_DEMO_LUA_ROOT_DIR), - TAG, - "Failed to set Lua base dir"); - ESP_RETURN_ON_ERROR(cap_im_qq_set_attachment_config( - &(cap_im_qq_attachment_config_t) { - .storage_root_dir = BASIC_DEMO_IM_ATTACHMENT_ROOT, - .max_inbound_file_bytes = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, - .enable_inbound_attachments = true, - }), - TAG, - "Failed to set QQ attachment config"); - ESP_RETURN_ON_ERROR(cap_im_tg_set_attachment_config( - &(cap_im_tg_attachment_config_t) { - .storage_root_dir = BASIC_DEMO_IM_ATTACHMENT_ROOT, - .max_inbound_file_bytes = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, - .enable_inbound_attachments = true, - }), - TAG, - "Failed to set Telegram attachment config"); - ESP_RETURN_ON_ERROR(cap_im_wechat_set_attachment_config( - &(cap_im_wechat_attachment_config_t) { - .storage_root_dir = BASIC_DEMO_IM_ATTACHMENT_ROOT, - .max_inbound_file_bytes = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, - .enable_inbound_attachments = true, - }), - TAG, - "Failed to set WeChat attachment config"); - ESP_RETURN_ON_ERROR(cap_im_feishu_set_attachment_config( - &(cap_im_feishu_attachment_config_t) { - .storage_root_dir = BASIC_DEMO_IM_ATTACHMENT_ROOT, - .max_inbound_file_bytes = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, - .enable_inbound_attachments = true, - }), - TAG, - "Failed to set Feishu attachment config"); + ESP_RETURN_ON_ERROR(cap_time_set_timezone(settings->time_timezone), TAG, "Failed to set time cap timezone"); + ESP_RETURN_ON_ERROR(cap_files_set_base_dir(basic_demo_fatfs_base_path), TAG, "Failed to set files cap base dir"); + ESP_RETURN_ON_ERROR(cap_lua_set_base_dir(paths->lua_root_dir), TAG, "Failed to set Lua base dir"); + 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 = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, + .enable_inbound_attachments = true, + }), + TAG, "Failed to set QQ attachment config"); + 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 = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, + .enable_inbound_attachments = true, + }), + TAG, "Failed to set Telegram attachment config"); + 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 = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, + .enable_inbound_attachments = true, + }), + TAG, "Failed to set WeChat attachment config"); + 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 = BASIC_DEMO_IM_ATTACHMENT_MAX_BYTES, + .enable_inbound_attachments = true, + }), + TAG, "Failed to set Feishu attachment config"); if (settings->qq_app_id[0] && settings->qq_app_secret[0]) { - ESP_RETURN_ON_ERROR(cap_im_qq_set_credentials(settings->qq_app_id, - settings->qq_app_secret), - TAG, - "Failed to set QQ credentials"); + ESP_RETURN_ON_ERROR(cap_im_qq_set_credentials(settings->qq_app_id, settings->qq_app_secret), TAG, "Failed to set QQ credentials"); } if (settings->feishu_app_id[0] && settings->feishu_app_secret[0]) { - ESP_RETURN_ON_ERROR(cap_im_feishu_set_credentials(settings->feishu_app_id, - settings->feishu_app_secret), - TAG, - "Failed to set Feishu credentials"); + ESP_RETURN_ON_ERROR(cap_im_feishu_set_credentials(settings->feishu_app_id, settings->feishu_app_secret), TAG, "Failed to set Feishu credentials"); } if (settings->tg_bot_token[0]) { - ESP_RETURN_ON_ERROR(cap_im_tg_set_token(settings->tg_bot_token), - TAG, - "Failed to set Telegram bot token"); + ESP_RETURN_ON_ERROR(cap_im_tg_set_token(settings->tg_bot_token), TAG, "Failed to set Telegram bot token"); } if (settings->wechat_token[0] && settings->wechat_base_url[0]) { - ESP_RETURN_ON_ERROR(cap_im_wechat_set_client_config( - &(cap_im_wechat_client_config_t) { - .token = settings->wechat_token, - .base_url = settings->wechat_base_url, - .cdn_base_url = settings->wechat_cdn_base_url, - .account_id = settings->wechat_account_id, - }), - TAG, - "Failed to set WeChat client config"); + ESP_RETURN_ON_ERROR(cap_im_wechat_set_client_config(&(cap_im_wechat_client_config_t){ + .token = settings->wechat_token, + .base_url = settings->wechat_base_url, + .cdn_base_url = settings->wechat_cdn_base_url, + .account_id = settings->wechat_account_id, + }), + TAG, "Failed to set WeChat client config"); } if (settings->search_brave_key[0]) { - ESP_RETURN_ON_ERROR(cap_web_search_set_brave_key(settings->search_brave_key), - TAG, - "Failed to set Brave search key"); + ESP_RETURN_ON_ERROR(cap_web_search_set_brave_key(settings->search_brave_key), TAG, "Failed to set Brave search key"); } if (settings->search_tavily_key[0]) { - ESP_RETURN_ON_ERROR(cap_web_search_set_tavily_key(settings->search_tavily_key), - TAG, - "Failed to set Tavily search key"); + ESP_RETURN_ON_ERROR(cap_web_search_set_tavily_key(settings->search_tavily_key), TAG, "Failed to set Tavily search key"); } ESP_RETURN_ON_ERROR(cap_im_qq_register_group(), TAG, "Failed to register QQ cap"); - ESP_RETURN_ON_ERROR(cap_im_feishu_register_group(), - TAG, - "Failed to register Feishu cap"); - ESP_RETURN_ON_ERROR(cap_im_tg_register_group(), - TAG, - "Failed to register Telegram cap"); - ESP_RETURN_ON_ERROR(cap_im_wechat_register_group(), - TAG, - "Failed to register WeChat cap"); + ESP_RETURN_ON_ERROR(cap_im_feishu_register_group(), TAG, "Failed to register Feishu cap"); + ESP_RETURN_ON_ERROR(cap_im_tg_register_group(), TAG, "Failed to register Telegram cap"); + ESP_RETURN_ON_ERROR(cap_im_wechat_register_group(), TAG, "Failed to register WeChat cap"); ESP_RETURN_ON_ERROR(cap_files_register_group(), TAG, "Failed to register files cap"); - ESP_RETURN_ON_ERROR(basic_demo_lua_modules_register(), - TAG, - "Failed to register app Lua modules"); - ESP_RETURN_ON_ERROR(cap_scheduler_register_group(), - TAG, - "Failed to register scheduler cap"); + ESP_RETURN_ON_ERROR(basic_demo_lua_modules_register(), TAG, "Failed to register app Lua modules"); + ESP_RETURN_ON_ERROR(cap_scheduler_register_group(), TAG, "Failed to register scheduler cap"); ESP_RETURN_ON_ERROR(cap_lua_register_group(), TAG, "Failed to register Lua cap"); - ESP_RETURN_ON_ERROR(cap_mcp_client_register_group(), - TAG, - "Failed to register MCP client cap"); - ESP_RETURN_ON_ERROR(cap_mcp_server_register_group(), - TAG, - "Failed to register MCP server cap"); - ESP_RETURN_ON_ERROR(cap_skill_register_group(), - TAG, - "Failed to register skill cap"); + ESP_RETURN_ON_ERROR(cap_mcp_client_register_group(), TAG, "Failed to register MCP client cap"); + ESP_RETURN_ON_ERROR(cap_mcp_server_register_group(), TAG, "Failed to register MCP server cap"); + ESP_RETURN_ON_ERROR(cap_skill_mgr_register_group(), TAG, "Failed to register skill cap"); ESP_RETURN_ON_ERROR(cap_time_register_group(), TAG, "Failed to register time cap"); - ESP_RETURN_ON_ERROR(cap_llm_inspect_register_group(), - TAG, - "Failed to register LLM inspect cap"); - ESP_RETURN_ON_ERROR(cap_web_search_register_group(), - TAG, - "Failed to register web search cap"); - ESP_RETURN_ON_ERROR(cap_router_mgr_register_group(), - TAG, - "Failed to register router manager cap"); - ESP_RETURN_ON_ERROR(cap_session_mng_register_group(), - TAG, - "Failed to register session manager cap"); + ESP_RETURN_ON_ERROR(cap_llm_inspect_register_group(), TAG, "Failed to register LLM inspect cap"); + ESP_RETURN_ON_ERROR(cap_web_search_register_group(), TAG, "Failed to register web search cap"); + ESP_RETURN_ON_ERROR(cap_router_mgr_register_group(), TAG, "Failed to register router manager cap"); + ESP_RETURN_ON_ERROR(cap_session_mgr_register_group(), TAG, "Failed to register session manager cap"); ESP_RETURN_ON_ERROR(claw_cap_set_llm_visible_groups( - BASIC_DEMO_LLM_VISIBLE_GROUPS, - sizeof(BASIC_DEMO_LLM_VISIBLE_GROUPS) / - sizeof(BASIC_DEMO_LLM_VISIBLE_GROUPS[0])), - TAG, - "Failed to set LLM-visible capability groups"); + BASIC_DEMO_LLM_VISIBLE_GROUPS, sizeof(BASIC_DEMO_LLM_VISIBLE_GROUPS) / sizeof(BASIC_DEMO_LLM_VISIBLE_GROUPS[0])), + TAG, "Failed to set LLM-visible capability groups"); ESP_RETURN_ON_ERROR(claw_cap_start_all(), TAG, "Failed to start capabilities"); return ESP_OK; @@ -273,12 +239,10 @@ static const char *basic_demo_llm_provider_name(const basic_demo_settings_t *set } if ((settings->llm_backend_type[0] && strcmp(settings->llm_backend_type, "anthropic") == 0) || - (settings->llm_profile[0] && strcmp(settings->llm_profile, "anthropic") == 0)) { + (settings->llm_profile[0] && strcmp(settings->llm_profile, "anthropic") == 0)) { return "Anthropic"; } - if (settings->llm_profile[0] && - (strcmp(settings->llm_profile, "qwen") == 0 || - strcmp(settings->llm_profile, "qwen_compatible") == 0)) { + if (settings->llm_profile[0] && (strcmp(settings->llm_profile, "qwen") == 0 || strcmp(settings->llm_profile, "qwen_compatible") == 0)) { return "Qwen Compatible"; } if (settings->llm_profile[0] && strcmp(settings->llm_profile, "openai") == 0) { @@ -289,24 +253,22 @@ static const char *basic_demo_llm_provider_name(const basic_demo_settings_t *set static bool basic_demo_llm_is_configured(const basic_demo_settings_t *settings) { - return settings && - settings->llm_api_key[0] && - settings->llm_model[0] && - settings->llm_profile[0]; + return settings && settings->llm_api_key[0] && settings->llm_model[0] && settings->llm_profile[0]; } esp_err_t app_claw_start(const basic_demo_settings_t *settings) { + basic_demo_paths_t paths = {0}; claw_core_config_t core_config = {0}; claw_event_router_config_t router_config = { - .rules_path = BASIC_DEMO_AUTOMATION_RULES_PATH, + .rules_path = NULL, .task_stack_size = 8 * 1024, .task_priority = 5, .task_core = tskNO_AFFINITY, .core_submit_timeout_ms = 1000, .core_receive_timeout_ms = 130000, .default_route_messages_to_agent = false, - .session_builder = cap_session_mng_build_session_id, + .session_builder = cap_session_mgr_build_session_id, }; bool llm_enabled = false; @@ -317,41 +279,30 @@ esp_err_t app_claw_start(const basic_demo_settings_t *settings) llm_enabled = basic_demo_llm_is_configured(settings); router_config.default_route_messages_to_agent = llm_enabled; - ESP_RETURN_ON_ERROR(cap_session_mng_set_session_root_dir(BASIC_DEMO_MEMORY_SESSION_ROOT), - TAG, - "Failed to configure session manager"); - ESP_RETURN_ON_ERROR(claw_event_router_init(&router_config), - TAG, - "Failed to init event router"); - ESP_RETURN_ON_ERROR(cap_scheduler_init(&(cap_scheduler_config_t) { - .schedules_path = BASIC_DEMO_SCHEDULER_RULES_PATH, - .state_path = BASIC_DEMO_SCHEDULER_STATE_PATH, - .default_timezone = settings->time_timezone, - .tick_ms = 1000, - .max_items = 32, - .task_stack_size = 6144, - .task_priority = 5, - .task_core = tskNO_AFFINITY, - .publish_event = claw_event_router_publish, - .persist_after_fire = true, - }), - TAG, - "Failed to init scheduler"); - ESP_RETURN_ON_ERROR(init_memory(), TAG, "Failed to init memory"); - ESP_RETURN_ON_ERROR(init_skills(), TAG, "Failed to init skills"); - ESP_RETURN_ON_ERROR(init_capabilities(settings), TAG, "Failed to init capabilities"); - ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("qq", "qq_send_message"), - TAG, - "Failed to bind QQ outbound"); - ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("feishu", "feishu_send_message"), - TAG, - "Failed to bind Feishu outbound"); - ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("telegram", "tg_send_message"), - TAG, - "Failed to bind Telegram outbound"); - ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("wechat", "wechat_send_message"), - TAG, - "Failed to bind WeChat outbound"); + ESP_RETURN_ON_ERROR(basic_demo_init_paths(&paths), TAG, "Failed to init storage paths"); + router_config.rules_path = paths.router_rules_path; + ESP_RETURN_ON_ERROR(cap_session_mgr_set_session_root_dir(paths.memory_session_root), TAG, "Failed to configure session manager"); + ESP_RETURN_ON_ERROR(claw_event_router_init(&router_config), TAG, "Failed to init event router"); + ESP_RETURN_ON_ERROR(cap_scheduler_init(&(cap_scheduler_config_t){ + .schedules_path = paths.scheduler_rules_path, + .state_path = paths.scheduler_state_path, + .default_timezone = settings->time_timezone, + .tick_ms = 1000, + .max_items = 32, + .task_stack_size = 6144, + .task_priority = 5, + .task_core = tskNO_AFFINITY, + .publish_event = claw_event_router_publish, + .persist_after_fire = true, + }), + TAG, "Failed to init scheduler"); + ESP_RETURN_ON_ERROR(init_memory(&paths), TAG, "Failed to init memory"); + ESP_RETURN_ON_ERROR(init_skills(&paths), TAG, "Failed to init skills"); + ESP_RETURN_ON_ERROR(init_capabilities(settings, &paths), TAG, "Failed to init capabilities"); + ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("qq", "qq_send_message"), TAG, "Failed to bind QQ outbound"); + ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("feishu", "feishu_send_message"), TAG, "Failed to bind Feishu outbound"); + ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("telegram", "tg_send_message"), TAG, "Failed to bind Telegram outbound"); + ESP_RETURN_ON_ERROR(claw_event_router_register_outbound_binding("wechat", "wechat_send_message"), TAG, "Failed to bind WeChat outbound"); core_config.api_key = settings->llm_api_key; core_config.backend_type = settings->llm_backend_type; @@ -372,45 +323,28 @@ esp_err_t app_claw_start(const basic_demo_settings_t *settings) core_config.max_context_providers = 5; if (!llm_enabled) { - ESP_LOGW(TAG, - "LLM is not fully configured. Provider=%s profile=%s model=%s. " - "The demo will start without claw_core; ask, auto-route-to-agent, and image analysis stay disabled until LLM API key, profile, and model are set.", - basic_demo_llm_provider_name(settings), - settings->llm_profile[0] ? settings->llm_profile : "(empty)", + ESP_LOGW(TAG, "LLM is not fully configured. Provider=%s profile=%s model=%s. " + "The demo will start without claw_core; ask, auto-route-to-agent, and image analysis stay disabled until LLM API key, profile, and model are set.", + basic_demo_llm_provider_name(settings), settings->llm_profile[0] ? settings->llm_profile : "(empty)", settings->llm_model[0] ? settings->llm_model : "(empty)"); } else { - ESP_LOGI(TAG, - "Starting LLM provider=%s profile=%s backend=%s model=%s", - basic_demo_llm_provider_name(settings), - settings->llm_profile, - settings->llm_backend_type[0] ? settings->llm_backend_type : "(default)", - settings->llm_model); + ESP_LOGI(TAG, "Starting LLM provider=%s profile=%s backend=%s model=%s", basic_demo_llm_provider_name(settings), settings->llm_profile, + settings->llm_backend_type[0] ? settings->llm_backend_type : "(default)", settings->llm_model); ESP_RETURN_ON_ERROR(claw_core_init(&core_config), TAG, "Failed to init claw_core"); - ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_memory_long_term_provider), - TAG, - "Failed to add long-term memory provider"); - ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_memory_session_history_provider), - TAG, - "Failed to add session history provider"); - ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_skill_skills_list_provider), - TAG, - "Failed to add skills list provider"); - ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_skill_active_skill_docs_provider), - TAG, - "Failed to add active skill docs provider"); - ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_cap_tools_provider), - TAG, - "Failed to add cap tools provider"); + ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_memory_long_term_provider), TAG, "Failed to add long-term memory provider"); + ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_memory_session_history_provider), TAG, "Failed to add session history provider"); + ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_skill_skills_list_provider), TAG, "Failed to add skills list provider"); + ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_skill_active_skill_docs_provider), TAG, "Failed to add active skill docs provider"); + ESP_RETURN_ON_ERROR(claw_core_add_context_provider(&claw_cap_tools_provider), TAG, "Failed to add cap tools provider"); ESP_RETURN_ON_ERROR(claw_core_start(), TAG, "Failed to start claw_core"); } ESP_RETURN_ON_ERROR(claw_event_router_start(), TAG, "Failed to start event router"); - ESP_RETURN_ON_ERROR(cap_time_sync_service_start(&(cap_time_sync_service_config_t) { - .network_ready = basic_demo_time_network_ready, - .on_sync_success = basic_demo_time_sync_success, - }), - TAG, - "Failed to start time sync service"); + ESP_RETURN_ON_ERROR(cap_time_sync_service_start(&(cap_time_sync_service_config_t){ + .network_ready = basic_demo_time_network_ready, + .on_sync_success = basic_demo_time_sync_success, + }), + TAG, "Failed to start time sync service"); ESP_RETURN_ON_ERROR(cap_scheduler_start(), TAG, "Failed to start scheduler"); ESP_RETURN_ON_ERROR(basic_demo_cli_start(), TAG, "Failed to start CLI"); diff --git a/application/basic_demo/main/app_claw.h b/application/basic_demo/main/app_claw.h index e439f50..6cf8559 100644 --- a/application/basic_demo/main/app_claw.h +++ b/application/basic_demo/main/app_claw.h @@ -13,6 +13,7 @@ extern "C" { #endif esp_err_t app_claw_start(const basic_demo_settings_t *settings); +extern const char *basic_demo_fatfs_base_path; #ifdef __cplusplus } diff --git a/application/basic_demo/main/basic_demo_lua_modules.c b/application/basic_demo/main/basic_demo_lua_modules.c index 5c65a7f..448e949 100644 --- a/application/basic_demo/main/basic_demo_lua_modules.c +++ b/application/basic_demo/main/basic_demo_lua_modules.c @@ -25,6 +25,8 @@ #include "lua_module_camera.h" #endif +extern const char *basic_demo_fatfs_base_path; + esp_err_t basic_demo_lua_modules_register(void) { esp_err_t err; @@ -34,7 +36,7 @@ esp_err_t basic_demo_lua_modules_register(void) return err; } - err = lua_module_storage_register(); + err = lua_module_storage_register(basic_demo_fatfs_base_path); if (err != ESP_OK) { return err; } diff --git a/application/basic_demo/main/idf_component.yml b/application/basic_demo/main/idf_component.yml index 3b577ea..0a0f348 100644 --- a/application/basic_demo/main/idf_component.yml +++ b/application/basic_demo/main/idf_component.yml @@ -48,11 +48,11 @@ dependencies: cap_router_mgr: path: ../../../components/claw_capabilities/cap_router_mgr - cap_session_mng: - path: ../../../components/claw_capabilities/cap_session_mng + cap_session_mgr: + path: ../../../components/claw_capabilities/cap_session_mgr - cap_skill: - path: ../../../components/claw_capabilities/cap_skill + cap_skill_mgr: + path: ../../../components/claw_capabilities/cap_skill_mgr cap_scheduler: path: ../../../components/claw_capabilities/cap_scheduler diff --git a/application/basic_demo/main/main.c b/application/basic_demo/main/main.c index c2ecda7..0267d8c 100644 --- a/application/basic_demo/main/main.c +++ b/application/basic_demo/main/main.c @@ -18,7 +18,7 @@ static const char *TAG = "basic_demo"; static basic_demo_settings_t s_settings = {0}; -const char *basic_demo_fatfs_base_path = "/fatfs/data"; +const char *basic_demo_fatfs_base_path = "/fatfs"; #define BASIC_DEMO_FATFS_PARTITION_LABEL "storage" #define BASIC_DEMO_ENABLE_MEM_LOG (0) diff --git a/components/claw_capabilities/cap_files/src/cap_files.c b/components/claw_capabilities/cap_files/src/cap_files.c index b24613b..838c0a9 100644 --- a/components/claw_capabilities/cap_files/src/cap_files.c +++ b/components/claw_capabilities/cap_files/src/cap_files.c @@ -20,10 +20,9 @@ static const char *TAG = "cap_files"; -#define CAP_FILES_DEFAULT_BASE_DIR "/fatfs/data" -#define CAP_FILES_MAX_FILE_SIZE (32 * 1024) +#define CAP_FILES_MAX_FILE_SIZE (32 * 1024) -static char s_files_base_dir[128] = CAP_FILES_DEFAULT_BASE_DIR; +static char s_files_base_dir[128] = {0}; static bool cap_files_path_is_valid(const char *path) { @@ -32,6 +31,9 @@ static bool cap_files_path_is_valid(const char *path) if (!path || !path[0]) { return false; } + if (s_files_base_dir[0] == '\0') { + return false; + } if (strstr(path, "..") != NULL) { return false; @@ -52,6 +54,9 @@ static esp_err_t cap_files_resolve_path(const char *path, char *resolved, size_t if (!path || !path[0] || !resolved || resolved_size == 0) { return ESP_ERR_INVALID_ARG; } + if (s_files_base_dir[0] == '\0') { + return ESP_ERR_INVALID_STATE; + } if (path[0] == '/') { if (!cap_files_path_is_valid(path)) { @@ -601,6 +606,9 @@ static const claw_cap_group_t s_files_group = { esp_err_t cap_files_register_group(void) { + if (s_files_base_dir[0] == '\0') { + return ESP_ERR_INVALID_STATE; + } if (claw_cap_group_exists(s_files_group.group_id)) { return ESP_OK; } diff --git a/components/claw_capabilities/cap_im_feishu/skills/cap_im_feishu.md b/components/claw_capabilities/cap_im_feishu/skills/cap_im_feishu.md index 8b40fa7..36afa5f 100644 --- a/components/claw_capabilities/cap_im_feishu/skills/cap_im_feishu.md +++ b/components/claw_capabilities/cap_im_feishu/skills/cap_im_feishu.md @@ -31,7 +31,7 @@ Use this skill when the user wants to interact through Feishu, especially to rep - `path` must be a real local filesystem path on the device. - If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`. - Do not pass remote URLs directly to Feishu send capabilities. -- In this demo app, inbound Feishu attachments are typically saved under `/fatfs/data/inbox`. +- In this demo app, inbound Feishu attachments are typically saved under `/inbox`. ## Recommended workflow 1. Determine whether the user wants text, an image, or a generic file. @@ -61,7 +61,7 @@ Send an image: ```json { "chat_id": "oc_xxx123456", - "path": "/fatfs/data/inbox/capture.jpg", + "path": "/inbox/capture.jpg", "caption": "Here is the image." } ``` @@ -70,7 +70,7 @@ Send a file: ```json { "chat_id": "oc_xxx123456", - "path": "/fatfs/data/reports/status.json", + "path": "/reports/status.json", "caption": "Latest report." } ``` diff --git a/components/claw_capabilities/cap_im_feishu/src/cmd_cap_im_feishu.c b/components/claw_capabilities/cap_im_feishu/src/cmd_cap_im_feishu.c index 2af0771..d225454 100644 --- a/components/claw_capabilities/cap_im_feishu/src/cmd_cap_im_feishu.c +++ b/components/claw_capabilities/cap_im_feishu/src/cmd_cap_im_feishu.c @@ -188,8 +188,8 @@ void register_cap_im_feishu(void) " feishu --start\n" " feishu --stop\n" " feishu --send-text ou_xxx --text \"hello\"\n" - " feishu --send-image ou_xxx --path /fatfs/data/inbox/pic.jpg --caption \"look\"\n" - " feishu --send-file ou_xxx --path /fatfs/data/docs/readme.txt\n", + " feishu --send-image ou_xxx --path /inbox/pic.jpg --caption \"look\"\n" + " feishu --send-file ou_xxx --path /docs/readme.txt\n", .func = feishu_func, .argtable = &feishu_args, }; diff --git a/components/claw_capabilities/cap_im_qq/skills/cap_im_qq.md b/components/claw_capabilities/cap_im_qq/skills/cap_im_qq.md index c566002..c7cab3a 100644 --- a/components/claw_capabilities/cap_im_qq/skills/cap_im_qq.md +++ b/components/claw_capabilities/cap_im_qq/skills/cap_im_qq.md @@ -31,7 +31,7 @@ Use this skill when the user wants to interact through QQ Bot channels, especial - `path` must be a real local filesystem path on the device. - If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`. - Do not attempt to send remote URLs directly. Download or locate the file on local storage first. -- In this demo app, inbound QQ attachments are typically saved under `/fatfs/data/inbox`. +- In this demo app, inbound QQ attachments are typically saved under `/inbox`. ## Recommended workflow 1. Determine whether the user wants text, an image, or a generic file. @@ -61,7 +61,7 @@ Send an image: ```json { "chat_id": "c2c:1234567890", - "path": "/fatfs/data/inbox/capture.jpg", + "path": "/inbox/capture.jpg", "caption": "Here is the image." } ``` @@ -70,7 +70,7 @@ Send a file: ```json { "chat_id": "c2c:abcdefg123456", - "path": "/fatfs/data/reports/status.json", + "path": "/reports/status.json", "caption": "Latest report." } ``` diff --git a/components/claw_capabilities/cap_im_tg/skills/cap_im_tg.md b/components/claw_capabilities/cap_im_tg/skills/cap_im_tg.md index fa5d306..4f442b9 100644 --- a/components/claw_capabilities/cap_im_tg/skills/cap_im_tg.md +++ b/components/claw_capabilities/cap_im_tg/skills/cap_im_tg.md @@ -30,7 +30,7 @@ Use this skill when the user wants to interact through Telegram, especially to r - `path` must be a real local filesystem path on the device. - If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`. - Do not pass remote URLs directly to Telegram send capabilities. -- In this demo app, inbound Telegram attachments are typically saved under `/fatfs/data/inbox`. +- In this demo app, inbound Telegram attachments are typically saved under `/inbox`. ## Recommended workflow 1. Determine whether the user wants text, an image, or a generic file. @@ -60,7 +60,7 @@ Send an image: ```json { "chat_id": "123456789", - "path": "/fatfs/data/inbox/capture.jpg", + "path": "/inbox/capture.jpg", "caption": "Here is the image." } ``` @@ -69,7 +69,7 @@ Send a file: ```json { "chat_id": "123456789", - "path": "/fatfs/data/reports/status.json", + "path": "/reports/status.json", "caption": "Latest report." } ``` diff --git a/components/claw_capabilities/cap_im_wechat/skills/cap_im_wechat.md b/components/claw_capabilities/cap_im_wechat/skills/cap_im_wechat.md index 64f6b54..e06f14f 100644 --- a/components/claw_capabilities/cap_im_wechat/skills/cap_im_wechat.md +++ b/components/claw_capabilities/cap_im_wechat/skills/cap_im_wechat.md @@ -29,7 +29,7 @@ Use this skill when the user wants to interact through WeChat, especially to sen - `path` must be a real local filesystem path on the device. - If the exact image path is unknown, inspect storage first with file capabilities such as `list_dir`. - Do not pass remote URLs directly to WeChat send capabilities. -- In this demo app, inbound WeChat media is typically saved under `/fatfs/data/inbox`. +- In this demo app, inbound WeChat media is typically saved under `/inbox`. ## Recommended workflow 1. Confirm that the target channel is WeChat. @@ -52,7 +52,7 @@ Send an image: ```json { "chat_id": "wxid_abc123", - "path": "/fatfs/data/inbox/capture.jpg", + "path": "/inbox/capture.jpg", "caption": "Here is the image." } ``` diff --git a/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c b/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c index 972afdd..091747c 100644 --- a/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c +++ b/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c @@ -55,7 +55,6 @@ static const char *TAG = "cap_im_wechat"; #define CAP_IM_WECHAT_ROUTE_TAG_SIZE 64 #define CAP_IM_WECHAT_CLIENT_ID_SIZE 48 #define CAP_IM_WECHAT_BODY_BUF_SIZE 1536 -#define CAP_IM_WECHAT_STORAGE_BASE "/fatfs/data" #define CAP_IM_WECHAT_DEFAULT_BASE_URL "https://ilinkai.weixin.qq.com" #define CAP_IM_WECHAT_DEFAULT_CDN_BASE_URL "https://novac2c.cdn.weixin.qq.com/c2c" #define CAP_IM_WECHAT_DEFAULT_APP_ID "bot" diff --git a/components/claw_capabilities/cap_llm_inspect/skills/cap_llm_inspect.md b/components/claw_capabilities/cap_llm_inspect/skills/cap_llm_inspect.md index 6c71f02..ef9b4e7 100644 --- a/components/claw_capabilities/cap_llm_inspect/skills/cap_llm_inspect.md +++ b/components/claw_capabilities/cap_llm_inspect/skills/cap_llm_inspect.md @@ -20,12 +20,12 @@ Use this skill when the user wants the device to inspect a local image and descr ## Path guidance - Prefer real local paths already stored on the device. - If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`. -- Common roots in this demo include `/fatfs/data/inbox`, `/fatfs/data`, or other application-managed storage paths. +- Common roots in this demo include `/inbox`, ``, or other application-managed storage paths. ## Example ```json { - "path": "/fatfs/data/inbox/photo.jpg", + "path": "/inbox/photo.jpg", "prompt": "Describe the main objects in this image and mention any visible text." } ``` diff --git a/components/claw_capabilities/cap_llm_inspect/src/cmd_cap_llm_inspect.c b/components/claw_capabilities/cap_llm_inspect/src/cmd_cap_llm_inspect.c index c0a0aa6..3697bf2 100644 --- a/components/claw_capabilities/cap_llm_inspect/src/cmd_cap_llm_inspect.c +++ b/components/claw_capabilities/cap_llm_inspect/src/cmd_cap_llm_inspect.c @@ -84,7 +84,7 @@ void register_cap_llm_inspect(void) .command = "llm_inspect", .help = "Inspect a local image with the configured LLM.\n" "Example:\n" - " llm_inspect --path /fatfs/data/inbox/pic.jpg --prompt \"Describe the screen contents\"\n", + " llm_inspect --path /inbox/pic.jpg --prompt \"Describe the screen contents\"\n", .func = llm_inspect_func, .argtable = &inspect_args, }; diff --git a/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c b/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c index 25b6830..781dfe6 100644 --- a/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c +++ b/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c @@ -633,7 +633,6 @@ static esp_err_t cap_scheduler_load_from_disk_locked(void) return err; } - for (size_t i = 0; i < s_cap_scheduler.max_items; i++) { if (!s_cap_scheduler.entries[i].occupied) { continue; @@ -765,9 +764,15 @@ esp_err_t cap_scheduler_register_group(void) esp_err_t cap_scheduler_init(const cap_scheduler_config_t *config) { + const char *schedules_path = NULL; + const char *state_path = NULL; + if (s_cap_scheduler.initialized) { return ESP_ERR_INVALID_STATE; } + if (!config || !config->schedules_path || !config->schedules_path[0] || !config->state_path || !config->state_path[0]) { + return ESP_ERR_INVALID_ARG; + } if (!s_cap_scheduler.mutex) { s_cap_scheduler.mutex = xSemaphoreCreateRecursiveMutex(); @@ -792,12 +797,10 @@ esp_err_t cap_scheduler_init(const cap_scheduler_config_t *config) s_cap_scheduler.config.task_core = config ? config->task_core : tskNO_AFFINITY; s_cap_scheduler.config.persist_after_fire = true; - strlcpy(s_cap_scheduler.schedules_path, - config && config->schedules_path ? config->schedules_path : CAP_SCHEDULER_DEFAULT_SCHEDULES_PATH, - sizeof(s_cap_scheduler.schedules_path)); - strlcpy(s_cap_scheduler.state_path, - config && config->state_path ? config->state_path : CAP_SCHEDULER_DEFAULT_STATE_PATH, - sizeof(s_cap_scheduler.state_path)); + schedules_path = config->schedules_path; + state_path = config->state_path; + strlcpy(s_cap_scheduler.schedules_path, schedules_path, sizeof(s_cap_scheduler.schedules_path)); + strlcpy(s_cap_scheduler.state_path, state_path, sizeof(s_cap_scheduler.state_path)); strlcpy(s_cap_scheduler.default_timezone, config && config->default_timezone ? config->default_timezone : CAP_SCHEDULER_DEFAULT_TIMEZONE, sizeof(s_cap_scheduler.default_timezone)); diff --git a/components/claw_capabilities/cap_scheduler/src/cap_scheduler_internal.h b/components/claw_capabilities/cap_scheduler/src/cap_scheduler_internal.h index c58e10e..f665d3b 100644 --- a/components/claw_capabilities/cap_scheduler/src/cap_scheduler_internal.h +++ b/components/claw_capabilities/cap_scheduler/src/cap_scheduler_internal.h @@ -15,8 +15,6 @@ #include "freertos/semphr.h" #include "freertos/task.h" -#define CAP_SCHEDULER_DEFAULT_SCHEDULES_PATH "/fatfs/data/scheduler/schedules.json" -#define CAP_SCHEDULER_DEFAULT_STATE_PATH "/fatfs/data/scheduler/scheduler_state.json" #define CAP_SCHEDULER_STATE_TMP_SUFFIX ".tmp" #define CAP_SCHEDULER_STATE_BACKUP_SUFFIX ".bak" #define CAP_SCHEDULER_PATH_BUF_LEN 224 diff --git a/components/claw_capabilities/cap_session_mng/CMakeLists.txt b/components/claw_capabilities/cap_session_mgr/CMakeLists.txt similarity index 81% rename from components/claw_capabilities/cap_session_mng/CMakeLists.txt rename to components/claw_capabilities/cap_session_mgr/CMakeLists.txt index 211b1ba..bb51a53 100644 --- a/components/claw_capabilities/cap_session_mng/CMakeLists.txt +++ b/components/claw_capabilities/cap_session_mgr/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( SRCS - "src/cap_session_mng.c" + "src/cap_session_mgr.c" INCLUDE_DIRS "include" REQUIRES diff --git a/components/claw_capabilities/cap_session_mgr/include/cap_session_mgr.h b/components/claw_capabilities/cap_session_mgr/include/cap_session_mgr.h new file mode 100644 index 0000000..af85fbb --- /dev/null +++ b/components/claw_capabilities/cap_session_mgr/include/cap_session_mgr.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#include "claw_event_router.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t cap_session_mgr_register_group(void); +esp_err_t cap_session_mgr_set_session_root_dir(const char *session_root_dir); +size_t cap_session_mgr_build_session_id(const claw_event_t *event, char *buf, size_t buf_size, void *user_ctx); + +#ifdef __cplusplus +} +#endif diff --git a/components/claw_capabilities/cap_session_mng/src/cap_session_mng.c b/components/claw_capabilities/cap_session_mgr/src/cap_session_mgr.c similarity index 62% rename from components/claw_capabilities/cap_session_mng/src/cap_session_mng.c rename to components/claw_capabilities/cap_session_mgr/src/cap_session_mgr.c index 0f0ff49..96ed7e0 100644 --- a/components/claw_capabilities/cap_session_mng/src/cap_session_mng.c +++ b/components/claw_capabilities/cap_session_mgr/src/cap_session_mgr.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include "cap_session_mng.h" +#include "cap_session_mgr.h" #include #include @@ -18,23 +18,23 @@ #include "esp_log.h" #include "freertos/semphr.h" -static const char *TAG = "cap_session_mng"; +static const char *TAG = "cap_session_mgr"; -#define CAP_SESSION_MNG_MAP_DIRNAME "chat_map" -#define CAP_SESSION_MNG_PATH_SIZE 256 -#define CAP_SESSION_MNG_KEY_SIZE 128 -#define CAP_SESSION_MNG_ID_SIZE 128 +#define CAP_SESSION_MGR_MAP_DIRNAME "chat_map" +#define CAP_SESSION_MGR_PATH_SIZE 256 +#define CAP_SESSION_MGR_KEY_SIZE 128 +#define CAP_SESSION_MGR_ID_SIZE 128 typedef struct { bool configured; char session_root_dir[160]; char mapping_root_dir[192]; SemaphoreHandle_t mutex; -} cap_session_mng_state_t; +} cap_session_mgr_state_t; -static cap_session_mng_state_t s_session_mng = {0}; +static cap_session_mgr_state_t s_session_mgr = {0}; -static bool cap_session_mng_is_chat_event(const claw_event_t *event) +static bool cap_session_mgr_is_chat_event(const claw_event_t *event) { return event && strcmp(event->event_type, "message") == 0 && @@ -42,7 +42,7 @@ static bool cap_session_mng_is_chat_event(const claw_event_t *event) event->chat_id[0] != '\0'; } -static uint32_t cap_session_mng_hash(const char *text) +static uint32_t cap_session_mgr_hash(const char *text) { uint32_t hash = 2166136261u; const unsigned char *ptr = (const unsigned char *)text; @@ -55,7 +55,7 @@ static uint32_t cap_session_mng_hash(const char *text) return hash; } -static void cap_session_mng_sanitize(const char *src, char *dst, size_t dst_size) +static void cap_session_mgr_sanitize(const char *src, char *dst, size_t dst_size) { size_t off = 0; @@ -84,7 +84,7 @@ static void cap_session_mng_sanitize(const char *src, char *dst, size_t dst_size dst[off] = '\0'; } -static esp_err_t cap_session_mng_ensure_dir(const char *path) +static esp_err_t cap_session_mgr_ensure_dir(const char *path) { struct stat st = {0}; @@ -100,10 +100,7 @@ static esp_err_t cap_session_mng_ensure_dir(const char *path) return ESP_FAIL; } -static esp_err_t cap_session_mng_build_chat_key(const char *source_channel, - const char *chat_id, - char *buf, - size_t buf_size) +static esp_err_t cap_session_mgr_build_chat_key(const char *source_channel, const char *chat_id, char *buf, size_t buf_size) { int written; @@ -119,9 +116,7 @@ static esp_err_t cap_session_mng_build_chat_key(const char *source_channel, return ESP_OK; } -static esp_err_t cap_session_mng_build_mapping_path(const char *chat_key, - char *path, - size_t path_size) +static esp_err_t cap_session_mgr_build_mapping_path(const char *chat_key, char *path, size_t path_size) { char safe_key[40]; uint32_t hash; @@ -131,15 +126,15 @@ static esp_err_t cap_session_mng_build_mapping_path(const char *chat_key, return ESP_ERR_INVALID_ARG; } - cap_session_mng_sanitize(chat_key, safe_key, sizeof(safe_key)); + cap_session_mgr_sanitize(chat_key, safe_key, sizeof(safe_key)); if (strlen(safe_key) > 24) { safe_key[24] = '\0'; } - hash = cap_session_mng_hash(chat_key); + hash = cap_session_mgr_hash(chat_key); written = snprintf(path, path_size, "%s/chat_%s_%08" PRIx32 ".json", - s_session_mng.mapping_root_dir, + s_session_mgr.mapping_root_dir, safe_key[0] ? safe_key : "default", hash); if (written < 0 || (size_t)written >= path_size) { @@ -149,15 +144,15 @@ static esp_err_t cap_session_mng_build_mapping_path(const char *chat_key, return ESP_OK; } -static esp_err_t cap_session_mng_write_mapping_locked(const char *chat_key, int version) +static esp_err_t cap_session_mgr_write_mapping_locked(const char *chat_key, int version) { - char path[CAP_SESSION_MNG_PATH_SIZE]; + char path[CAP_SESSION_MGR_PATH_SIZE]; cJSON *root = NULL; char *json = NULL; FILE *file = NULL; esp_err_t err; - err = cap_session_mng_build_mapping_path(chat_key, path, sizeof(path)); + err = cap_session_mgr_build_mapping_path(chat_key, path, sizeof(path)); if (err != ESP_OK) { return err; } @@ -189,9 +184,9 @@ static esp_err_t cap_session_mng_write_mapping_locked(const char *chat_key, int return ESP_OK; } -static esp_err_t cap_session_mng_read_version_locked(const char *chat_key, int *out_version) +static esp_err_t cap_session_mgr_read_version_locked(const char *chat_key, int *out_version) { - char path[CAP_SESSION_MNG_PATH_SIZE]; + char path[CAP_SESSION_MGR_PATH_SIZE]; FILE *file = NULL; long size; char *text = NULL; @@ -204,7 +199,7 @@ static esp_err_t cap_session_mng_read_version_locked(const char *chat_key, int * return ESP_ERR_INVALID_ARG; } - err = cap_session_mng_build_mapping_path(chat_key, path, sizeof(path)); + err = cap_session_mgr_build_mapping_path(chat_key, path, sizeof(path)); if (err != ESP_OK) { return err; } @@ -254,25 +249,25 @@ static esp_err_t cap_session_mng_read_version_locked(const char *chat_key, int * return ESP_OK; } -static esp_err_t cap_session_mng_build_current_session_id_locked(const char *source_channel, +static esp_err_t cap_session_mgr_build_current_session_id_locked(const char *source_channel, const char *chat_id, char *buf, size_t buf_size) { - char chat_key[CAP_SESSION_MNG_KEY_SIZE]; + char chat_key[CAP_SESSION_MGR_KEY_SIZE]; int version = 0; int written; esp_err_t err; - err = cap_session_mng_build_chat_key(source_channel, chat_id, chat_key, sizeof(chat_key)); + err = cap_session_mgr_build_chat_key(source_channel, chat_id, chat_key, sizeof(chat_key)); if (err != ESP_OK) { return err; } - err = cap_session_mng_read_version_locked(chat_key, &version); + err = cap_session_mgr_read_version_locked(chat_key, &version); if (err == ESP_ERR_NOT_FOUND || err == ESP_ERR_INVALID_RESPONSE) { version = 1; - err = cap_session_mng_write_mapping_locked(chat_key, version); + err = cap_session_mgr_write_mapping_locked(chat_key, version); } if (err != ESP_OK) { return err; @@ -286,22 +281,22 @@ static esp_err_t cap_session_mng_build_current_session_id_locked(const char *sou return ESP_OK; } -static esp_err_t cap_session_mng_roll_locked(const char *source_channel, +static esp_err_t cap_session_mgr_roll_locked(const char *source_channel, const char *chat_id, char *new_session_id, size_t new_session_id_size, int *out_version) { - char chat_key[CAP_SESSION_MNG_KEY_SIZE]; + char chat_key[CAP_SESSION_MGR_KEY_SIZE]; int version = 0; esp_err_t err; - err = cap_session_mng_build_chat_key(source_channel, chat_id, chat_key, sizeof(chat_key)); + err = cap_session_mgr_build_chat_key(source_channel, chat_id, chat_key, sizeof(chat_key)); if (err != ESP_OK) { return err; } - err = cap_session_mng_read_version_locked(chat_key, &version); + err = cap_session_mgr_read_version_locked(chat_key, &version); if (err == ESP_ERR_NOT_FOUND || err == ESP_ERR_INVALID_RESPONSE) { version = 0; err = ESP_OK; @@ -311,16 +306,13 @@ static esp_err_t cap_session_mng_roll_locked(const char *source_channel, } version++; - err = cap_session_mng_write_mapping_locked(chat_key, version); + err = cap_session_mgr_write_mapping_locked(chat_key, version); if (err != ESP_OK) { return err; } if (new_session_id && new_session_id_size > 0) { - err = cap_session_mng_build_current_session_id_locked(source_channel, - chat_id, - new_session_id, - new_session_id_size); + err = cap_session_mgr_build_current_session_id_locked(source_channel, chat_id, new_session_id, new_session_id_size); if (err != ESP_OK) { return err; } @@ -332,12 +324,12 @@ static esp_err_t cap_session_mng_roll_locked(const char *source_channel, return ESP_OK; } -static esp_err_t cap_session_mng_roll_execute(const char *input_json, +static esp_err_t cap_session_mgr_roll_execute(const char *input_json, const claw_cap_call_context_t *ctx, char *output, size_t output_size) { - char session_id[CAP_SESSION_MNG_ID_SIZE]; + char session_id[CAP_SESSION_MGR_ID_SIZE]; int version = 0; esp_err_t err; @@ -349,20 +341,16 @@ static esp_err_t cap_session_mng_roll_execute(const char *input_json, } return ESP_ERR_INVALID_ARG; } - if (!s_session_mng.configured || !s_session_mng.mutex) { + if (!s_session_mgr.configured || !s_session_mgr.mutex) { if (output && output_size > 0) { snprintf(output, output_size, "{\"ok\":false,\"error\":\"session manager not configured\"}"); } return ESP_ERR_INVALID_STATE; } - xSemaphoreTakeRecursive(s_session_mng.mutex, portMAX_DELAY); - err = cap_session_mng_roll_locked(ctx->channel, - ctx->chat_id, - session_id, - sizeof(session_id), - &version); - xSemaphoreGiveRecursive(s_session_mng.mutex); + xSemaphoreTakeRecursive(s_session_mgr.mutex, portMAX_DELAY); + err = cap_session_mgr_roll_locked(ctx->channel, ctx->chat_id, session_id, sizeof(session_id), &version); + xSemaphoreGiveRecursive(s_session_mgr.mutex); if (err != ESP_OK) { if (output && output_size > 0) { snprintf(output, output_size, "{\"ok\":false,\"error\":\"%s\"}", esp_err_to_name(err)); @@ -372,16 +360,12 @@ static esp_err_t cap_session_mng_roll_execute(const char *input_json, ESP_LOGI(TAG, "Rolled chat session %s:%s to version %d", ctx->channel, ctx->chat_id, version); if (output && output_size > 0) { - snprintf(output, - output_size, - "{\"ok\":true,\"session_id\":\"%s\",\"version\":%d}", - session_id, - version); + snprintf(output, output_size, "{\"ok\":true,\"session_id\":\"%s\",\"version\":%d}", session_id, version); } return ESP_OK; } -static const claw_cap_descriptor_t s_session_mng_caps[] = { +static const claw_cap_descriptor_t s_session_mgr_caps[] = { { .id = "roll_chat_session", .name = "roll_chat_session", @@ -390,63 +374,60 @@ static const claw_cap_descriptor_t s_session_mng_caps[] = { .kind = CLAW_CAP_KIND_CALLABLE, .cap_flags = CLAW_CAP_FLAG_RESTRICTED, .input_schema_json = "{\"type\":\"object\"}", - .execute = cap_session_mng_roll_execute, + .execute = cap_session_mgr_roll_execute, }, }; -static const claw_cap_group_t s_session_mng_group = { - .group_id = "cap_session_mng", - .plugin_name = "cap_session_mng", +static const claw_cap_group_t s_session_mgr_group = { + .group_id = "cap_session_mgr", + .plugin_name = "cap_session_mgr", .version = "1.0.0", - .descriptors = s_session_mng_caps, - .descriptor_count = sizeof(s_session_mng_caps) / sizeof(s_session_mng_caps[0]), + .descriptors = s_session_mgr_caps, + .descriptor_count = sizeof(s_session_mgr_caps) / sizeof(s_session_mgr_caps[0]), }; -esp_err_t cap_session_mng_register_group(void) +esp_err_t cap_session_mgr_register_group(void) { - return claw_cap_register_group(&s_session_mng_group); + return claw_cap_register_group(&s_session_mgr_group); } -esp_err_t cap_session_mng_set_session_root_dir(const char *session_root_dir) +esp_err_t cap_session_mgr_set_session_root_dir(const char *session_root_dir) { int written; - SemaphoreHandle_t mutex = s_session_mng.mutex; + SemaphoreHandle_t mutex = s_session_mgr.mutex; if (!session_root_dir || !session_root_dir[0]) { return ESP_ERR_INVALID_ARG; } - memset(&s_session_mng, 0, sizeof(s_session_mng)); - s_session_mng.mutex = mutex; - strlcpy(s_session_mng.session_root_dir, session_root_dir, sizeof(s_session_mng.session_root_dir)); - written = snprintf(s_session_mng.mapping_root_dir, - sizeof(s_session_mng.mapping_root_dir), + memset(&s_session_mgr, 0, sizeof(s_session_mgr)); + s_session_mgr.mutex = mutex; + strlcpy(s_session_mgr.session_root_dir, session_root_dir, sizeof(s_session_mgr.session_root_dir)); + written = snprintf(s_session_mgr.mapping_root_dir, + sizeof(s_session_mgr.mapping_root_dir), "%s/%s", session_root_dir, - CAP_SESSION_MNG_MAP_DIRNAME); - if (written < 0 || (size_t)written >= sizeof(s_session_mng.mapping_root_dir)) { + CAP_SESSION_MGR_MAP_DIRNAME); + if (written < 0 || (size_t)written >= sizeof(s_session_mgr.mapping_root_dir)) { return ESP_ERR_INVALID_SIZE; } - if (!s_session_mng.mutex) { - s_session_mng.mutex = xSemaphoreCreateRecursiveMutex(); + if (!s_session_mgr.mutex) { + s_session_mgr.mutex = xSemaphoreCreateRecursiveMutex(); } - if (!s_session_mng.mutex) { + if (!s_session_mgr.mutex) { return ESP_ERR_NO_MEM; } - if (cap_session_mng_ensure_dir(s_session_mng.session_root_dir) != ESP_OK || - cap_session_mng_ensure_dir(s_session_mng.mapping_root_dir) != ESP_OK) { + if (cap_session_mgr_ensure_dir(s_session_mgr.session_root_dir) != ESP_OK || + cap_session_mgr_ensure_dir(s_session_mgr.mapping_root_dir) != ESP_OK) { return ESP_FAIL; } - s_session_mng.configured = true; + s_session_mgr.configured = true; return ESP_OK; } -size_t cap_session_mng_build_session_id(const claw_event_t *event, - char *buf, - size_t buf_size, - void *user_ctx) +size_t cap_session_mgr_build_session_id(const claw_event_t *event, char *buf, size_t buf_size, void *user_ctx) { esp_err_t err; @@ -455,23 +436,20 @@ size_t cap_session_mng_build_session_id(const claw_event_t *event, if (!buf || buf_size == 0 || !event) { return 0; } - if (!cap_session_mng_is_chat_event(event) || !s_session_mng.configured || !s_session_mng.mutex) { - return claw_event_build_session_id(event, buf, buf_size); + if (!cap_session_mgr_is_chat_event(event) || !s_session_mgr.configured || !s_session_mgr.mutex) { + return claw_event_router_build_session_id(event, buf, buf_size); } - xSemaphoreTakeRecursive(s_session_mng.mutex, portMAX_DELAY); - err = cap_session_mng_build_current_session_id_locked(event->source_channel, - event->chat_id, - buf, - buf_size); - xSemaphoreGiveRecursive(s_session_mng.mutex); + xSemaphoreTakeRecursive(s_session_mgr.mutex, portMAX_DELAY); + err = cap_session_mgr_build_current_session_id_locked(event->source_channel, event->chat_id, buf, buf_size); + xSemaphoreGiveRecursive(s_session_mgr.mutex); if (err != ESP_OK) { ESP_LOGW(TAG, "Falling back to default session id for %s:%s: %s", event->source_channel, event->chat_id, esp_err_to_name(err)); - return claw_event_build_session_id(event, buf, buf_size); + return claw_event_router_build_session_id(event, buf, buf_size); } return strlen(buf); diff --git a/components/claw_capabilities/cap_session_mng/include/cap_session_mng.h b/components/claw_capabilities/cap_session_mng/include/cap_session_mng.h deleted file mode 100644 index ad4b29b..0000000 --- a/components/claw_capabilities/cap_session_mng/include/cap_session_mng.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include - -#include "claw_event.h" -#include "esp_err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -esp_err_t cap_session_mng_register_group(void); -esp_err_t cap_session_mng_set_session_root_dir(const char *session_root_dir); -size_t cap_session_mng_build_session_id(const claw_event_t *event, - char *buf, - size_t buf_size, - void *user_ctx); - -#ifdef __cplusplus -} -#endif diff --git a/components/claw_capabilities/cap_skill/CMakeLists.txt b/components/claw_capabilities/cap_skill_mgr/CMakeLists.txt similarity index 85% rename from components/claw_capabilities/cap_skill/CMakeLists.txt rename to components/claw_capabilities/cap_skill_mgr/CMakeLists.txt index a3a2ccb..77de6fb 100644 --- a/components/claw_capabilities/cap_skill/CMakeLists.txt +++ b/components/claw_capabilities/cap_skill_mgr/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( SRCS - "src/cap_skill.c" + "src/cap_skill_mgr.c" "src/cmd_cap_skill.c" INCLUDE_DIRS "include" diff --git a/components/claw_capabilities/cap_skill/include/cap_skill.h b/components/claw_capabilities/cap_skill_mgr/include/cap_skill_mgr.h similarity index 82% rename from components/claw_capabilities/cap_skill/include/cap_skill.h rename to components/claw_capabilities/cap_skill_mgr/include/cap_skill_mgr.h index f2aa92f..a2b303a 100644 --- a/components/claw_capabilities/cap_skill/include/cap_skill.h +++ b/components/claw_capabilities/cap_skill_mgr/include/cap_skill_mgr.h @@ -11,7 +11,7 @@ extern "C" { #endif -esp_err_t cap_skill_register_group(void); +esp_err_t cap_skill_mgr_register_group(void); #ifdef __cplusplus } diff --git a/components/claw_capabilities/cap_skill/include/cmd_cap_skill.h b/components/claw_capabilities/cap_skill_mgr/include/cmd_cap_skill.h similarity index 100% rename from components/claw_capabilities/cap_skill/include/cmd_cap_skill.h rename to components/claw_capabilities/cap_skill_mgr/include/cmd_cap_skill.h diff --git a/components/claw_capabilities/cap_skill/src/cap_skill.c b/components/claw_capabilities/cap_skill_mgr/src/cap_skill_mgr.c similarity index 94% rename from components/claw_capabilities/cap_skill/src/cap_skill.c rename to components/claw_capabilities/cap_skill_mgr/src/cap_skill_mgr.c index d359835..732e77a 100644 --- a/components/claw_capabilities/cap_skill/src/cap_skill.c +++ b/components/claw_capabilities/cap_skill_mgr/src/cap_skill_mgr.c @@ -3,7 +3,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include "cap_skill.h" #include #include @@ -14,6 +13,7 @@ #include "cJSON.h" #include "claw_cap.h" #include "claw_skill.h" +#include "cap_skill_mgr.h" static const char *CAP_SKILL_ACTIVATE = "activate_skill"; static const char *CAP_SKILL_DEACTIVATE = "deactivate_skill"; @@ -21,10 +21,25 @@ static const char *CAP_SKILL_LIST = "list_skill"; static const char *CAP_SKILL_REGISTER = "register_skill"; static const char *CAP_SKILL_UNREGISTER = "unregister_skill"; -#define CAP_SKILL_ROOT_DIR "/fatfs/data/skills" -#define CAP_SKILL_LIST_FILE_PATH CAP_SKILL_ROOT_DIR "/skills_list.json" #define CAP_SKILL_MAX_CATALOG_LEN 16384 -#define CAP_SKILL_MAX_PATH_LEN 256 +#define CAP_SKILL_MAX_PATH_LEN 128 + +static const char *cap_skill_root_dir(void) +{ + return claw_skill_get_skills_root_dir(); +} + +static const char *cap_skill_list_file_path(void) +{ + static char path[CAP_SKILL_MAX_PATH_LEN]; + + const char *root_dir = cap_skill_root_dir(); + if (!root_dir) { + return NULL; + } + snprintf(path, sizeof(path), "%s/skills_list.json", root_dir); + return path; +} static void cap_skill_free_string_array(char **items, size_t count) { @@ -293,7 +308,13 @@ static esp_err_t cap_skill_load_catalog_json(char **out_text, cJSON **out_catalo *out_text = NULL; *out_catalog = NULL; - err = cap_skill_read_file_dup(CAP_SKILL_LIST_FILE_PATH, &catalog_text); + { + const char *catalog_path = cap_skill_list_file_path(); + if (!catalog_path) { + return ESP_ERR_INVALID_STATE; + } + err = cap_skill_read_file_dup(catalog_path, &catalog_text); + } if (err != ESP_OK) { return err; } @@ -330,12 +351,17 @@ static esp_err_t cap_skill_render_catalog(cJSON *catalog, char **out_text) static esp_err_t cap_skill_write_catalog_and_reload(const char *new_text, const char *old_text) { esp_err_t err; + const char *catalog_path = NULL; if (!new_text || !old_text) { return ESP_ERR_INVALID_ARG; } + catalog_path = cap_skill_list_file_path(); + if (!catalog_path) { + return ESP_ERR_INVALID_STATE; + } - err = cap_skill_write_file_text(CAP_SKILL_LIST_FILE_PATH, new_text); + err = cap_skill_write_file_text(catalog_path, new_text); if (err != ESP_OK) { return err; } @@ -345,7 +371,7 @@ static esp_err_t cap_skill_write_catalog_and_reload(const char *new_text, const return ESP_OK; } - if (cap_skill_write_file_text(CAP_SKILL_LIST_FILE_PATH, old_text) == ESP_OK) { + if (cap_skill_write_file_text(catalog_path, old_text) == ESP_OK) { (void)claw_skill_reload_registry(); } @@ -564,11 +590,18 @@ static esp_err_t cap_skill_register_execute(const char *input_json, return ESP_ERR_INVALID_ARG; } - if (snprintf(skill_path, sizeof(skill_path), "%s/%s", CAP_SKILL_ROOT_DIR, file_item->valuestring) >= - (int)sizeof(skill_path)) { - cJSON_Delete(root); - cap_skill_write_error(output, output_size, "file path is too long", skill_id_item->valuestring); - return ESP_ERR_INVALID_SIZE; + { + const char *root_dir = cap_skill_root_dir(); + if (!root_dir) { + cJSON_Delete(root); + cap_skill_write_error(output, output_size, "skill storage is not initialized", skill_id_item->valuestring); + return ESP_ERR_INVALID_STATE; + } + if (snprintf(skill_path, sizeof(skill_path), "%s/%s", root_dir, file_item->valuestring) >= (int)sizeof(skill_path)) { + cJSON_Delete(root); + cap_skill_write_error(output, output_size, "file path is too long", skill_id_item->valuestring); + return ESP_ERR_INVALID_SIZE; + } } if (!cap_skill_file_exists(skill_path)) { cJSON_Delete(root); @@ -788,7 +821,7 @@ static const claw_cap_group_t s_skill_group = { .descriptor_count = sizeof(s_skill_descriptors) / sizeof(s_skill_descriptors[0]), }; -esp_err_t cap_skill_register_group(void) +esp_err_t cap_skill_mgr_register_group(void) { if (claw_cap_group_exists(s_skill_group.group_id)) { return ESP_OK; diff --git a/components/claw_capabilities/cap_skill/src/cmd_cap_skill.c b/components/claw_capabilities/cap_skill_mgr/src/cmd_cap_skill.c similarity index 99% rename from components/claw_capabilities/cap_skill/src/cmd_cap_skill.c rename to components/claw_capabilities/cap_skill_mgr/src/cmd_cap_skill.c index 84fed64..1521825 100644 --- a/components/claw_capabilities/cap_skill/src/cmd_cap_skill.c +++ b/components/claw_capabilities/cap_skill_mgr/src/cmd_cap_skill.c @@ -210,7 +210,7 @@ void register_cap_skill(void) skill_args.catalog = arg_lit0(NULL, "catalog", "Print the skills catalog JSON"); skill_args.register_skill = arg_str0("r", "register", "", "Register one skill in the catalog"); skill_args.unregister_skill = arg_str0("u", "unregister", "", "Remove one skill from the catalog"); - skill_args.file = arg_str0("f", "file", "", "Skill markdown path relative to /fatfs/data/skills"); + skill_args.file = arg_str0("f", "file", "", "Skill markdown path relative to /skills"); skill_args.title = arg_str0("t", "title", "", "Skill title for catalog registration"); skill_args.summary = arg_str0(NULL, "summary", "<summary>", "Skill summary for catalog registration"); skill_args.activate = arg_str0("a", "activate", "<skill_id>", "Activate one skill"); diff --git a/components/claw_modules/claw_event_router/include/claw_event_router.h b/components/claw_modules/claw_event_router/include/claw_event_router.h index 06558d8..5dd5821 100644 --- a/components/claw_modules/claw_event_router/include/claw_event_router.h +++ b/components/claw_modules/claw_event_router/include/claw_event_router.h @@ -18,8 +18,6 @@ extern "C" { #endif -#define CLAW_EVENT_ROUTER_DEFAULT_RULES_PATH "/fatfs/data/automation/automations.json" - typedef size_t (*claw_event_router_session_builder_fn)(const claw_event_t *event, char *buf, size_t buf_size, diff --git a/components/claw_modules/claw_event_router/src/claw_event_router.c b/components/claw_modules/claw_event_router/src/claw_event_router.c index 45be651..d1693a6 100644 --- a/components/claw_modules/claw_event_router/src/claw_event_router.c +++ b/components/claw_modules/claw_event_router/src/claw_event_router.c @@ -67,7 +67,6 @@ typedef struct { } claw_event_router_runtime_t; static claw_event_router_runtime_t s_runtime = { - .rules_path = CLAW_EVENT_ROUTER_DEFAULT_RULES_PATH, .max_rules = CLAW_EVENT_ROUTER_DEFAULT_MAX_RULES, .max_actions_per_rule = CLAW_EVENT_ROUTER_DEFAULT_MAX_ACTIONS, .cap_output_size = CLAW_EVENT_ROUTER_DEFAULT_OUTPUT_SIZE, @@ -1913,6 +1912,9 @@ esp_err_t claw_event_router_init(const claw_event_router_config_t *config) if (s_runtime.initialized) { return ESP_ERR_INVALID_STATE; } + if (!config || !config->rules_path || !config->rules_path[0]) { + return ESP_ERR_INVALID_ARG; + } if (!s_runtime.mutex) { s_runtime.mutex = xSemaphoreCreateRecursiveMutex(); @@ -1926,9 +1928,7 @@ esp_err_t claw_event_router_init(const claw_event_router_config_t *config) if (config) { s_runtime.config = *config; } - if (config && config->rules_path && config->rules_path[0]) { - strlcpy(s_runtime.rules_path, config->rules_path, sizeof(s_runtime.rules_path)); - } + strlcpy(s_runtime.rules_path, config->rules_path, sizeof(s_runtime.rules_path)); if (config && config->max_rules > 0) { s_runtime.max_rules = config->max_rules; } diff --git a/components/claw_modules/claw_event_router/test_apps/event_router_cli_test/main/main.c b/components/claw_modules/claw_event_router/test_apps/event_router_cli_test/main/main.c index 52521de..da0c89d 100644 --- a/components/claw_modules/claw_event_router/test_apps/event_router_cli_test/main/main.c +++ b/components/claw_modules/claw_event_router/test_apps/event_router_cli_test/main/main.c @@ -27,7 +27,7 @@ static const char *TAG = "event_router_test"; -#define TEST_FATFS_BASE_PATH "/fatfs/data" +#define TEST_FATFS_BASE_PATH "/tmp" #define TEST_FATFS_PARTITION_LABEL "storage" #define TEST_AUTOMATION_DIR TEST_FATFS_BASE_PATH "/auto" #define TEST_RULES_PATH TEST_AUTOMATION_DIR "/rules" diff --git a/components/claw_modules/claw_skill/include/claw_skill.h b/components/claw_modules/claw_skill/include/claw_skill.h index a1cbc7d..c645741 100644 --- a/components/claw_modules/claw_skill/include/claw_skill.h +++ b/components/claw_modules/claw_skill/include/claw_skill.h @@ -24,6 +24,7 @@ typedef struct { esp_err_t claw_skill_init(const claw_skill_config_t *config); esp_err_t claw_skill_reload_registry(void); +const char *claw_skill_get_skills_root_dir(void); /* Renders the skill catalog used by the prompt layer. */ esp_err_t claw_skill_read_skills_list(char *buf, size_t size); diff --git a/components/claw_modules/claw_skill/src/claw_skill.c b/components/claw_modules/claw_skill/src/claw_skill.c index 1041286..d2ca928 100644 --- a/components/claw_modules/claw_skill/src/claw_skill.c +++ b/components/claw_modules/claw_skill/src/claw_skill.c @@ -860,6 +860,15 @@ esp_err_t claw_skill_reload_registry(void) return err; } +const char *claw_skill_get_skills_root_dir(void) +{ + if (!s_skill.initialized || !s_skill.skills_root_dir[0]) { + return NULL; + } + + return s_skill.skills_root_dir; +} + esp_err_t claw_skill_read_skills_list(char *buf, size_t size) { return claw_skill_render_skills_list(buf, size); diff --git a/components/lua_modules/lua_module_audio/lua_module_audio.c b/components/lua_modules/lua_module_audio/lua_module_audio.c index 35e9d3a..c3e243d 100644 --- a/components/lua_modules/lua_module_audio/lua_module_audio.c +++ b/components/lua_modules/lua_module_audio/lua_module_audio.c @@ -22,11 +22,10 @@ static const char *TAG = "lua_audio"; /* -------------------------------------------------------------------------- * Audio constants * -------------------------------------------------------------------------- */ -#define AUDIO_CHUNK_BYTES 512 -#define AUDIO_DEFAULT_VOL 80 -#define AUDIO_DEFAULT_GAIN_DB 30.0f -#define AUDIO_BASE_PATH "/fatfs/" -#define AUDIO_HANDLE_METATABLE "lua_audio_handle" +#define AUDIO_CHUNK_BYTES 512 +#define AUDIO_DEFAULT_VOL 80 +#define AUDIO_DEFAULT_GAIN_DB 30.0f +#define AUDIO_HANDLE_METATABLE "lua_audio_handle" typedef enum { AUDIO_HANDLE_INPUT = 0, @@ -160,14 +159,10 @@ static esp_err_t wav_parse(FILE *f, audio_wav_info_t *info) * -------------------------------------------------------------------------- */ static bool audio_path_valid(const char *path, const char *ext) { - size_t base_len = strlen(AUDIO_BASE_PATH); size_t ext_len = strlen(ext); size_t len; - if (!path || strstr(path, "..")) { - return false; - } - if (strncmp(path, AUDIO_BASE_PATH, base_len) != 0) { + if (!path || !path[0] || strstr(path, "..")) { return false; } len = strlen(path); @@ -375,6 +370,7 @@ static int lua_audio_close(lua_State *L) /* -------------------------------------------------------------------------- * audio.play_wav(output_handle, path) -> nil + * path must be a .wav file and must not contain "..". * -------------------------------------------------------------------------- */ static int lua_audio_play_wav(lua_State *L) { @@ -385,7 +381,7 @@ static int lua_audio_play_wav(lua_State *L) audio_wav_info_t info = {0}; if (!audio_path_valid(path, ".wav")) { - return luaL_error(L, "audio play_wav: path must be a .wav file under %s", AUDIO_BASE_PATH); + return luaL_error(L, "audio play_wav: path must be a .wav file and must not contain '..'"); } f = fopen(path, "rb"); @@ -438,6 +434,7 @@ cleanup: /* -------------------------------------------------------------------------- * audio.record_wav(input_handle, path, duration_ms) -> { path, duration_ms, bytes } + * path must be a .wav file and must not contain "..". * -------------------------------------------------------------------------- */ static int lua_audio_record_wav(lua_State *L) { @@ -450,7 +447,7 @@ static int lua_audio_record_wav(lua_State *L) uint8_t wav_hdr[44]; if (!audio_path_valid(path, ".wav")) { - return luaL_error(L, "audio record_wav: path must be a .wav file under %s", AUDIO_BASE_PATH); + return luaL_error(L, "audio record_wav: path must be a .wav file and must not contain '..'"); } if (duration_ms == 0) { return luaL_error(L, "audio record_wav: duration_ms must be positive"); diff --git a/components/lua_modules/lua_module_audio/skills/lua_module_audio.md b/components/lua_modules/lua_module_audio/skills/lua_module_audio.md index 77d9e0e..e93dd42 100644 --- a/components/lua_modules/lua_module_audio/skills/lua_module_audio.md +++ b/components/lua_modules/lua_module_audio/skills/lua_module_audio.md @@ -6,8 +6,8 @@ This skill describes how to correctly use audio when writing Lua scripts. - Import it with `local audio = require("audio")` - Call `audio.new_input(codec_dev_handle, sample_rate, channels, bits_per_sample [, gain_db])` to create an input handle - Call `audio.new_output(codec_dev_handle, sample_rate, channels, bits_per_sample [, volume])` to create an output handle -- Call `audio.play_wav(output_handle, path)` to play a WAV file under `/fatfs/data/` -- Call `audio.record_wav(input_handle, path, duration_ms)` to record audio to a WAV file under `/fatfs/data/` +- Call `audio.play_wav(output_handle, path)` to play a WAV file under the current storage root +- Call `audio.record_wav(input_handle, path, duration_ms)` to record audio to a WAV file under the current storage root - Call `audio.loopback(input_handle, output_handle [, duration_ms])` to route input to output for monitoring - Call `audio.set_volume(output_handle, pct)`, `audio.get_volume(output_handle)`, `audio.set_mute(output_handle, enabled)`, or `audio.set_gain(input_handle, db)` to adjust levels - Call `audio.mic_read_level(input_handle [, duration_ms])` to read microphone level statistics such as `rms` and `peak` @@ -23,6 +23,7 @@ local output_codec, rate, channels, bits = local output = audio.new_output(output_codec, rate, channels, bits) audio.set_volume(output, 60) -audio.play_wav(output, "/fatfs/data/test.wav") +local storage = require("storage") +audio.play_wav(output, storage.join_path(storage.get_root_dir(), "test.wav")) audio.close(output) ``` diff --git a/components/lua_modules/lua_module_camera/skills/lua_module_camera.md b/components/lua_modules/lua_module_camera/skills/lua_module_camera.md index 89419ec..db2adbe 100644 --- a/components/lua_modules/lua_module_camera/skills/lua_module_camera.md +++ b/components/lua_modules/lua_module_camera/skills/lua_module_camera.md @@ -8,7 +8,7 @@ This skill describes how to correctly use camera when writing Lua scripts. - Call `camera.info()` to get stream information such as `width`, `height`, and `pixel_format` - Call `camera.get_frame([timeout_ms])` to borrow one raw frame - Call `camera.release_frame(frame)` or `frame:release()` after using a borrowed frame -- Call `camera.capture(save_path [, timeout_ms])` to capture a frame to a `.jpg` or `.jpeg` path under `/fatfs/data/` +- Call `camera.capture(save_path [, timeout_ms])` to capture a frame to a `.jpg` or `.jpeg` path under the current storage root - Call `camera.close()` when the camera is no longer needed ## Frame lifecycle @@ -43,7 +43,8 @@ local frame_info = frame:info() print(frame_info.width, frame_info.height, frame_info.pixel_format, frame:bytes()) frame:release() -local capture = camera.capture("/fatfs/data/capture.jpg", 3000) +local storage = require("storage") +local capture = camera.capture(storage.join_path(storage.get_root_dir(), "capture.jpg"), 3000) print(capture.path, capture.bytes) camera.close() ``` diff --git a/components/lua_modules/lua_module_camera/src/lua_module_camera.c b/components/lua_modules/lua_module_camera/src/lua_module_camera.c index 41608f9..6beed17 100644 --- a/components/lua_modules/lua_module_camera/src/lua_module_camera.c +++ b/components/lua_modules/lua_module_camera/src/lua_module_camera.c @@ -18,7 +18,6 @@ #include "lua_module_camera_service.h" #define LUA_MODULE_CAMERA_NAME "camera" -#define LUA_MODULE_CAMERA_BASE_DIR "/fatfs/data" #define LUA_MODULE_CAMERA_FRAME_MT "camera.frame" typedef struct { @@ -185,14 +184,7 @@ static bool lua_module_camera_has_suffix(const char *path, const char *suffix) static bool lua_module_camera_save_path_is_valid(const char *path) { - size_t base_len; - - if (path == NULL) { - return false; - } - - base_len = strlen(LUA_MODULE_CAMERA_BASE_DIR); - if (strncmp(path, LUA_MODULE_CAMERA_BASE_DIR, base_len) != 0 || path[base_len] != '/') { + if (path == NULL || path[0] == '\0') { return false; } if (strstr(path, "..") != NULL) { @@ -248,7 +240,7 @@ static int lua_module_camera_info(lua_State *L) /* camera.capture(save_path [, timeout_ms]) * Captures a JPEG frame and writes it to save_path. - * save_path must be a .jpg/.jpeg file under /fatfs/data/. + * save_path must be a .jpg/.jpeg file and must not contain "..". * Returns a table with: path, bytes, width, height, pixel_format, pixel_format_raw, * frame_bytes, timestamp_us. */ static int lua_module_camera_capture(lua_State *L) @@ -262,8 +254,7 @@ static int lua_module_camera_capture(lua_State *L) esp_err_t err; if (!lua_module_camera_save_path_is_valid(path)) { - return luaL_error(L, "save path must be a .jpg/.jpeg file under %s", - LUA_MODULE_CAMERA_BASE_DIR); + return luaL_error(L, "save path must be a .jpg/.jpeg file and must not contain '..'"); } if (timeout_ms < 0) { return luaL_error(L, "timeout_ms must be non-negative"); diff --git a/components/lua_modules/lua_module_display/skills/lua_module_display.md b/components/lua_modules/lua_module_display/skills/lua_module_display.md index 2f8e171..bc8de2c 100644 --- a/components/lua_modules/lua_module_display/skills/lua_module_display.md +++ b/components/lua_modules/lua_module_display/skills/lua_module_display.md @@ -44,7 +44,7 @@ pcall(display.deinit) - Colors are almost always passed as three integers: `r, g, b`. - Text drawing only supports ASCII text. - For Chinese or other Unicode text, render an image and draw it with `draw_png_file(...)` or `draw_jpeg_file(...)`. -- Image file paths must be absolute paths like `/fatfs/data/pic.jpg`. +- Image file paths must be absolute paths under the current storage root, for example `storage.join_path(storage.get_root_dir(), "pic.jpg")`. - Paths containing `..` are rejected. - Supported image file extensions are `.jpg`, `.jpeg`, and `.png`. diff --git a/components/lua_modules/lua_module_storage/include/lua_module_storage.h b/components/lua_modules/lua_module_storage/include/lua_module_storage.h index 1386362..6603797 100644 --- a/components/lua_modules/lua_module_storage/include/lua_module_storage.h +++ b/components/lua_modules/lua_module_storage/include/lua_module_storage.h @@ -13,7 +13,7 @@ extern "C" { #endif int luaopen_storage(lua_State *L); -esp_err_t lua_module_storage_register(void); +esp_err_t lua_module_storage_register(const char *base_path); #ifdef __cplusplus } diff --git a/components/lua_modules/lua_module_storage/skills/lua_module_storage.md b/components/lua_modules/lua_module_storage/skills/lua_module_storage.md index 3dd74f6..776488d 100644 --- a/components/lua_modules/lua_module_storage/skills/lua_module_storage.md +++ b/components/lua_modules/lua_module_storage/skills/lua_module_storage.md @@ -19,7 +19,7 @@ This skill describes how to correctly use storage when writing Lua scripts. ## Path joining - Prefer `storage.join_path(...)` whenever building a path from `storage.get_root_dir()` and child names. - Pass each path component as a separate string argument, for example `storage.join_path(root, "logs", "today.txt")`. -- `join_path` removes duplicate separators between components, so `storage.join_path("/fatfs/data/", "/demo/", "test.txt")` returns `/fatfs/data/demo/test.txt`. +- `join_path` removes duplicate separators between components, so `storage.join_path(storage.get_root_dir(), "/demo/", "test.txt")` returns `<storage_root>/demo/test.txt`. - Empty string components are ignored, so optional subdirectories can be passed directly when they may be empty. - The first component decides whether the result is absolute. Use `storage.get_root_dir()` as the first component for filesystem paths in this demo. - Do not put multiple logical components in one string when they can be separate arguments; `storage.join_path(root, "demo", filename)` is easier to audit than `storage.join_path(root, "demo/" .. filename)`. diff --git a/components/lua_modules/lua_module_storage/src/lua_module_storage.c b/components/lua_modules/lua_module_storage/src/lua_module_storage.c index 121a0bd..ab5b2d9 100644 --- a/components/lua_modules/lua_module_storage/src/lua_module_storage.c +++ b/components/lua_modules/lua_module_storage/src/lua_module_storage.c @@ -17,15 +17,21 @@ #include "esp_vfs_fat.h" #include "lauxlib.h" -#define LUA_MODULE_STORAGE_DEFAULT_ROOT_DIR "/fatfs/data" +static char *s_storage_base_path; -// The basic demo defines this symbol in main.c; keep a weak fallback so this -// Lua module can still link in apps that do not override the storage root. -const char *basic_demo_fatfs_base_path __attribute__((weak)) = LUA_MODULE_STORAGE_DEFAULT_ROOT_DIR; +static const char *lua_module_storage_base_path(void) +{ + return s_storage_base_path; +} static int lua_module_storage_get_root_dir(lua_State *L) { - lua_pushstring(L, basic_demo_fatfs_base_path); + const char *base_path = lua_module_storage_base_path(); + + if (!base_path || !base_path[0]) { + return luaL_error(L, "storage root is not configured"); + } + lua_pushstring(L, base_path); return 1; } @@ -285,9 +291,15 @@ static int lua_module_storage_rename(lua_State *L) static int lua_module_storage_get_free_space(lua_State *L) { + const char *base_path = lua_module_storage_base_path(); uint64_t total = 0; uint64_t free_bytes = 0; - esp_err_t err = esp_vfs_fat_info(basic_demo_fatfs_base_path, &total, &free_bytes); + + if (!base_path || !base_path[0]) { + return luaL_error(L, "storage root is not configured"); + } + + esp_err_t err = esp_vfs_fat_info(base_path, &total, &free_bytes); if (err != ESP_OK) { return luaL_error(L, "failed to query storage free space: %s", esp_err_to_name(err)); @@ -331,7 +343,20 @@ int luaopen_storage(lua_State *L) return 1; } -esp_err_t lua_module_storage_register(void) +esp_err_t lua_module_storage_register(const char *base_path) { + char *base_path_copy = NULL; + + if (!base_path || !base_path[0]) { + return ESP_ERR_INVALID_ARG; + } + + base_path_copy = strdup(base_path); + if (!base_path_copy) { + return ESP_ERR_NO_MEM; + } + + free(s_storage_base_path); + s_storage_base_path = base_path_copy; return cap_lua_register_module("storage", luaopen_storage); }