feat(core): Modified the message notification mechanism

This commit is contained in:
lvhaiyu
2026-04-17 20:39:51 +08:00
committed by loop
parent bf10c1420b
commit 1d94b95be5
6 changed files with 206 additions and 50 deletions
+29 -29
View File
@@ -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)
@@ -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.",
+22
View File
@@ -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
@@ -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);
@@ -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);
@@ -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;
}