mirror of
https://github.com/m5stack/ESP-Claw.git
synced 2026-05-20 11:51:49 -07:00
feat(core): Modified the message notification mechanism
This commit is contained in:
@@ -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.",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user