diff --git a/application/basic_demo/CMakeLists.txt b/application/basic_demo/CMakeLists.txt index 8780070..73766dc 100644 --- a/application/basic_demo/CMakeLists.txt +++ b/application/basic_demo/CMakeLists.txt @@ -8,38 +8,38 @@ endif() find_program(GIT_EXECUTABLE git REQUIRED) -# if(EXISTS "${BASIC_DEMO_ESP_IDF_PATCH}") -# execute_process( -# COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${BASIC_DEMO_ESP_IDF_PATCH}" -# WORKING_DIRECTORY "$ENV{IDF_PATH}" -# RESULT_VARIABLE BASIC_DEMO_PATCH_ALREADY_APPLIED -# OUTPUT_QUIET -# ERROR_QUIET -# ) +if(EXISTS "${BASIC_DEMO_ESP_IDF_PATCH}") + execute_process( + COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${BASIC_DEMO_ESP_IDF_PATCH}" + WORKING_DIRECTORY "$ENV{IDF_PATH}" + RESULT_VARIABLE BASIC_DEMO_PATCH_ALREADY_APPLIED + OUTPUT_QUIET + ERROR_QUIET + ) -# if(BASIC_DEMO_PATCH_ALREADY_APPLIED EQUAL 0) -# message(STATUS "ESP-IDF patch already applied: ${BASIC_DEMO_ESP_IDF_PATCH}") -# else() -# execute_process( -# COMMAND ${GIT_EXECUTABLE} apply "${BASIC_DEMO_ESP_IDF_PATCH}" -# WORKING_DIRECTORY "$ENV{IDF_PATH}" -# RESULT_VARIABLE BASIC_DEMO_PATCH_APPLY_RESULT -# OUTPUT_VARIABLE BASIC_DEMO_PATCH_APPLY_STDOUT -# ERROR_VARIABLE BASIC_DEMO_PATCH_APPLY_STDERR -# ) + if(BASIC_DEMO_PATCH_ALREADY_APPLIED EQUAL 0) + message(STATUS "ESP-IDF patch already applied: ${BASIC_DEMO_ESP_IDF_PATCH}") + else() + execute_process( + COMMAND ${GIT_EXECUTABLE} apply "${BASIC_DEMO_ESP_IDF_PATCH}" + WORKING_DIRECTORY "$ENV{IDF_PATH}" + RESULT_VARIABLE BASIC_DEMO_PATCH_APPLY_RESULT + OUTPUT_VARIABLE BASIC_DEMO_PATCH_APPLY_STDOUT + ERROR_VARIABLE BASIC_DEMO_PATCH_APPLY_STDERR + ) -# if(NOT BASIC_DEMO_PATCH_APPLY_RESULT EQUAL 0) -# message(FATAL_ERROR -# "Failed to apply ESP-IDF patch: ${BASIC_DEMO_ESP_IDF_PATCH}\n" -# "stdout:\n${BASIC_DEMO_PATCH_APPLY_STDOUT}\n" -# "stderr:\n${BASIC_DEMO_PATCH_APPLY_STDERR}") -# endif() + if(NOT BASIC_DEMO_PATCH_APPLY_RESULT EQUAL 0) + message(FATAL_ERROR + "Failed to apply ESP-IDF patch: ${BASIC_DEMO_ESP_IDF_PATCH}\n" + "stdout:\n${BASIC_DEMO_PATCH_APPLY_STDOUT}\n" + "stderr:\n${BASIC_DEMO_PATCH_APPLY_STDERR}") + endif() -# message(STATUS "Applied ESP-IDF patch: ${BASIC_DEMO_ESP_IDF_PATCH}") -# endif() -# else() -# message(FATAL_ERROR "ESP-IDF patch file not found: ${BASIC_DEMO_ESP_IDF_PATCH}") -# endif() + message(STATUS "Applied ESP-IDF patch: ${BASIC_DEMO_ESP_IDF_PATCH}") + endif() +else() + message(FATAL_ERROR "ESP-IDF patch file not found: ${BASIC_DEMO_ESP_IDF_PATCH}") +endif() include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(basic_demo) diff --git a/application/basic_demo/fatfs_image/router_rules/router_rules.json b/application/basic_demo/fatfs_image/router_rules/router_rules.json index 0a01f46..6a5734b 100644 --- a/application/basic_demo/fatfs_image/router_rules/router_rules.json +++ b/application/basic_demo/fatfs_image/router_rules/router_rules.json @@ -66,7 +66,7 @@ "input": { "channel": "{{event.source_channel}}", "chat_id": "{{event.chat_id}}", - "message": "ESP-Claw is working" + "message": "🦞 ESP-Claw is snapping on it..." } } ] @@ -111,6 +111,27 @@ } ] }, + { + "id": "agent_stage_im_notify", + "description": "Forward agent stage updates to the originating IM chat.", + "enabled": true, + "consume_on_match": true, + "ack": "agent stage notified via {{event.source_channel}}", + "match": { + "source_cap": "claw_core", + "event_type": "agent_stage" + }, + "actions": [ + { + "type": "send_message", + "input": { + "channel": "{{event.source_channel}}", + "chat_id": "{{event.chat_id}}", + "message": "{{event.text}}" + } + } + ] + }, { "id": "agent_response_send_message", "description": "Deliver final agent responses to the original IM chat.", diff --git a/components/claw_modules/claw_core/Kconfig b/components/claw_modules/claw_core/Kconfig new file mode 100644 index 0000000..c7a8911 --- /dev/null +++ b/components/claw_modules/claw_core/Kconfig @@ -0,0 +1,22 @@ +menu "ESP-Claw Core" + +choice CLAW_CORE_STAGE_VERBOSITY + prompt "Agent stage notification verbosity" + default CLAW_CORE_STAGE_VERBOSITY_SIMPLE + help + Controls how much detail is forwarded to IM channels during agent execution. + +config CLAW_CORE_STAGE_VERBOSITY_SIMPLE + bool "Simple" + help + Send only essential updates: tool names being invoked. + +config CLAW_CORE_STAGE_VERBOSITY_VERBOSE + bool "Verbose" + help + Include tool arguments preview and iteration round in addition to tool + names. Useful for debugging and power users. + +endchoice + +endmenu diff --git a/components/claw_modules/claw_core/src/claw_core.c b/components/claw_modules/claw_core/src/claw_core.c index dbfb37d..3a4b8d4 100644 --- a/components/claw_modules/claw_core/src/claw_core.c +++ b/components/claw_modules/claw_core/src/claw_core.c @@ -189,7 +189,7 @@ static void log_tool_call_names(uint32_t request_id, const claw_core_llm_respons off += (size_t)written; } - ESP_LOGI(TAG, "llm_tool_calls request=%" PRIu32 " count=%u names=%s%s", + ESP_LOGD(TAG, "llm_tool_calls request=%" PRIu32 " count=%u names=%s%s", request_id, (unsigned)response->tool_call_count, buf, @@ -443,6 +443,90 @@ static void publish_response_event_if_requested(const claw_core_request_item_t * free(payload_json); } +static void publish_stage_event(const claw_core_request_t *request, const char *text) +{ + claw_event_t event = {0}; + const char *channel; + const char *chat_id; + int64_t now_ms; + + if (!request || !text || !text[0]) { + return; + } + channel = (request->target_channel && request->target_channel[0]) ? + request->target_channel : request->source_channel; + chat_id = (request->target_chat_id && request->target_chat_id[0]) ? + request->target_chat_id : request->source_chat_id; + if (!channel || !channel[0] || !chat_id || !chat_id[0]) { + return; + } + + now_ms = claw_core_now_ms(); + snprintf(event.event_id, sizeof(event.event_id), + "stage-%" PRIu32 "-%" PRId64, request->request_id, now_ms); + strlcpy(event.source_cap, "claw_core", sizeof(event.source_cap)); + strlcpy(event.event_type, "agent_stage", sizeof(event.event_type)); + strlcpy(event.source_channel, channel, sizeof(event.source_channel)); + strlcpy(event.chat_id, chat_id, sizeof(event.chat_id)); + strlcpy(event.content_type, "text", sizeof(event.content_type)); + event.text = (char *)text; + event.timestamp_ms = now_ms; + esp_err_t pub_err = claw_event_router_publish(&event); + if (pub_err != ESP_OK) { + ESP_LOGW(TAG, "request=%" PRIu32 " failed to publish stage event: %s", + request->request_id, esp_err_to_name(pub_err)); + } +} + +static void publish_stage_tool_calls(const claw_core_request_t *request, + const claw_core_llm_response_t *response, + uint32_t iteration) +{ +#if CONFIG_CLAW_CORE_STAGE_VERBOSITY_VERBOSE + char buf[256]; + size_t off = 0; + size_t i; + int written; + + if (!response || response->tool_call_count == 0) { + return; + } + + if (iteration > 0) { + written = snprintf(buf, sizeof(buf), "🦞 [Round %" PRIu32 "] Snap: ", iteration + 1); + } else { + written = snprintf(buf, sizeof(buf), "🦞 Snap: "); + } + if (written < 0 || (size_t)written >= sizeof(buf)) { + return; + } + off = (size_t)written; + + for (i = 0; i < response->tool_call_count; i++) { + const char *name = response->tool_calls[i].name ? response->tool_calls[i].name : "?"; + const char *args = response->tool_calls[i].arguments_json; + if (args && args[0]) { + written = snprintf(buf + off, sizeof(buf) - off, "%s%s(%.40s%s)", + i == 0 ? "" : ", ", name, args, + strlen(args) > 40 ? "..." : ""); + } else { + written = snprintf(buf + off, sizeof(buf) - off, "%s%s", + i == 0 ? "" : ", ", name); + } + if (written < 0 || (size_t)written >= sizeof(buf) - off) { + break; + } + off += (size_t)written; + } + + publish_stage_event(request, buf); +#else + (void)request; + (void)response; + (void)iteration; +#endif +} + static esp_err_t append_user_message(cJSON *messages, const char *text) { cJSON *user_msg = NULL; @@ -549,7 +633,11 @@ static char *build_current_turn_prompt(const claw_core_request_t *request) return NULL; } - total_len = 256; + static const char *k_behavior_note = + "The agent result will be automatically sent to the user. " + "so it is generally not need to activate cap_im_xx to return messages.\n"; + + total_len = 256 + strlen(k_behavior_note); total_len += request->source_cap ? strlen(request->source_cap) : 0; total_len += request->source_channel ? strlen(request->source_channel) : 0; total_len += request->source_chat_id ? strlen(request->source_chat_id) : 0; @@ -569,13 +657,16 @@ static char *build_current_turn_prompt(const claw_core_request_t *request) "- source_channel: %s\n" "- source_chat_id: %s\n" "- source_sender_id: %s\n" - "- source_message_id: %s\n", + "- source_message_id: %s\n" + "\n## Behavior Notes\n" + "%s", request->request_id, request->source_cap ? request->source_cap : "(unknown)", request->source_channel ? request->source_channel : "(unknown)", request->source_chat_id ? request->source_chat_id : "(unknown)", request->source_sender_id ? request->source_sender_id : "(unknown)", - request->source_message_id ? request->source_message_id : "(none)"); + request->source_message_id ? request->source_message_id : "(none)", + k_behavior_note); return text; } @@ -787,15 +878,26 @@ static esp_err_t build_iteration_context(const claw_core_request_item_t *request continue; } if (err != ESP_OK) { + ESP_LOGW(TAG, + "context provider collect failed request=%" PRIu32 + " provider=%s err=%s", + request->view.request_id, + provider->name, + esp_err_to_name(err)); goto cleanup; } if (!context.content || !context.content[0]) { + ESP_LOGW(TAG, + "context provider returned empty content request=%" PRIu32 + " provider=%s", + request->view.request_id, + provider->name); free(context.content); err = ESP_FAIL; goto cleanup; } context_len = strlen(context.content); - ESP_LOGI(TAG, + ESP_LOGD(TAG, "context_loaded request=%" PRIu32 " provider=%s context_kind=%s context_len=%u", request->view.request_id, provider->name, @@ -934,9 +1036,6 @@ static void claw_core_task(void *arg) &llm_response, &response.view.error_message); if (err != ESP_OK) { - ESP_LOGE(TAG, "request=%"PRIu32" LLM chat failed: %s", - request.view.request_id, - response.view.error_message ? response.view.error_message : esp_err_to_name(err)); goto finish_request; } @@ -949,6 +1048,7 @@ static void claw_core_task(void *arg) } log_tool_call_names(request.view.request_id, &llm_response); + publish_stage_tool_calls(&request.view, &llm_response, iteration); err = append_assistant_tool_calls(runtime_messages, &llm_response); if (err != ESP_OK) { @@ -999,6 +1099,11 @@ static void claw_core_task(void *arg) } finish_request: + if (err != ESP_OK) { + ESP_LOGE(TAG, "request=%" PRIu32 " failed: %s", + request.view.request_id, + response.view.error_message ? response.view.error_message : esp_err_to_name(err)); + } publish_response_event_if_requested(&request, &response); if (request.view.flags & CLAW_CORE_REQUEST_FLAG_SKIP_RESPONSE_QUEUE) { free_response_item(&response); diff --git a/components/claw_modules/claw_core/src/llm/claw_llm_http_transport.c b/components/claw_modules/claw_core/src/llm/claw_llm_http_transport.c index 8812e97..7b20a5c 100644 --- a/components/claw_modules/claw_core/src/llm/claw_llm_http_transport.c +++ b/components/claw_modules/claw_core/src/llm/claw_llm_http_transport.c @@ -203,6 +203,7 @@ esp_err_t claw_llm_http_post_json(const claw_llm_http_json_request_t *request, err = response_buffer_init(&buffer); if (err != ESP_OK) { *out_error_message = dup_printf("Out of memory allocating HTTP buffer"); + ESP_LOGE(TAG, "OOM allocating HTTP response buffer"); return err; } @@ -217,6 +218,7 @@ esp_err_t claw_llm_http_post_json(const claw_llm_http_json_request_t *request, client = esp_http_client_init(&config); if (!client) { *out_error_message = dup_printf("Failed to create HTTP client"); + ESP_LOGE(TAG, "Failed to create HTTP client for %s", request->url); err = ESP_FAIL; goto cleanup; } @@ -241,7 +243,7 @@ esp_err_t claw_llm_http_post_json(const claw_llm_http_json_request_t *request, } esp_http_client_set_post_field(client, request->body, (int)strlen(request->body)); - ESP_LOGI(TAG, "POST %s", request->url); + ESP_LOGD(TAG, "POST %s", request->url); err = esp_http_client_perform(client); if (err != ESP_OK) { *out_error_message = dup_printf("HTTP request failed: %s", esp_err_to_name(err)); @@ -250,7 +252,7 @@ esp_err_t claw_llm_http_post_json(const claw_llm_http_json_request_t *request, } status_code = esp_http_client_get_status_code(client); - ESP_LOGI(TAG, "HTTP status=%d", status_code); + ESP_LOGD(TAG, "HTTP status=%d", status_code); if (status_code != 200) { err = ESP_FAIL; *out_error_message = parse_error_message_body(buffer.data, status_code); 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 d1693a6..3199f0b 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 @@ -1501,7 +1501,7 @@ static esp_err_t claw_event_router_execute_send_message_action( channel = cJSON_GetStringValue(cJSON_GetObjectItem(rendered_input, "channel")); chat_id = cJSON_GetStringValue(cJSON_GetObjectItem(rendered_input, "chat_id")); message = cJSON_GetStringValue(cJSON_GetObjectItem(rendered_input, "message")); - ESP_LOGI(TAG, + ESP_LOGD(TAG, "send_message rendered channel=%s chat_id=%s message_len=%u", channel ? channel : "(null)", chat_id ? chat_id : "(null)", @@ -1514,11 +1514,16 @@ static esp_err_t claw_event_router_execute_send_message_action( } if (!message || !message[0]) { message = claw_event_router_get_ctx_string(ctx, "last", "output"); - ESP_LOGI(TAG, + ESP_LOGD(TAG, "send_message fallback last.output message_len=%u", (unsigned int)(message ? strlen(message) : 0)); } if (!message || !message[0]) { + ESP_LOGW(TAG, + "send_message dropped: empty message rule=%s channel=%s chat_id=%s", + rule ? rule->id : "-", + channel ? channel : "(null)", + chat_id ? chat_id : "(null)"); cJSON_Delete(rendered_input); return ESP_ERR_INVALID_ARG; } @@ -1537,7 +1542,7 @@ static esp_err_t claw_event_router_execute_send_message_action( cJSON_Delete(rendered_input); return err; } - ESP_LOGI(TAG, "send_message resolved cap=%s", cap_name); + ESP_LOGD(TAG, "send_message resolved cap=%s", cap_name); payload_root = cJSON_CreateObject(); if (!payload_root) { @@ -1679,7 +1684,7 @@ static esp_err_t claw_event_router_execute_action(const claw_event_router_rule_t cJSON *ctx, claw_event_router_result_t *result) { - ESP_LOGI(TAG, + ESP_LOGD(TAG, "event=%s rule=%s action=%s start", event ? event->event_id : "-", rule ? rule->id : "-", @@ -1827,12 +1832,13 @@ static esp_err_t claw_event_router_process_event(const claw_event_t *event, for (size_t j = 0; j < rule->action_count; j++) { rule_err = claw_event_router_execute_action(rule, &rule->actions[j], event, ctx, &local); - ESP_LOGI(TAG, - "event=%s rule=%s action=%s done err=%s", - event->event_id, - rule->id, - claw_event_router_action_kind_to_string(rule->actions[j].kind), - esp_err_to_name(rule_err)); + ESP_LOG_LEVEL(rule_err == ESP_OK ? ESP_LOG_DEBUG : ESP_LOG_WARN, + TAG, + "event=%s rule=%s action=%s done err=%s", + event->event_id, + rule->id, + claw_event_router_action_kind_to_string(rule->actions[j].kind), + esp_err_to_name(rule_err)); if (rule_err != ESP_OK && !rule->actions[j].fail_open) { break; }