From 656d251a2a190d5bcf639bb4221627f2081641f8 Mon Sep 17 00:00:00 2001 From: Didactyl User Date: Mon, 6 Apr 2026 06:08:25 -0400 Subject: [PATCH] v0.2.26 - Add immediate adopted-skill cache invalidation on skill events plus new skill_refresh tool and /skill_refresh support --- README.md | 4 ++-- src/agent.c | 12 ++++++++++++ src/agent.h | 1 + src/main.h | 4 ++-- src/nostr_handler.c | 3 +++ src/tools/tool_skill.c | 16 ++++++++++++++++ src/tools/tools_dispatch.c | 3 +++ src/tools/tools_internal.h | 1 + src/tools/tools_schema.c | 15 +++++++++++++++ 9 files changed, 55 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 57a1586..40550f5 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers. -## Current Status — v0.2.25 +## Current Status — v0.2.26 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.2.25 — Fix private skill interoperability: NIP-44 encode/decode for kind 31124 with runtime tag normalization +> Last release update: v0.2.26 — Add immediate adopted-skill cache invalidation on skill events plus new skill_refresh tool and /skill_refresh support - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected diff --git a/src/agent.c b/src/agent.c index da7b5e2..f8aacbe 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1579,6 +1579,12 @@ static void clear_adopted_skills_cache_locked(void) { g_adopted_skills_count = 0; } +void agent_invalidate_adopted_skills_cache(void) { + pthread_mutex_lock(&g_adopted_skills_mutex); + g_adopted_skills_last_refresh_at = 0; + pthread_mutex_unlock(&g_adopted_skills_mutex); +} + static int refresh_adopted_skills_cache_if_needed(void) { if (!g_cfg) { return -1; @@ -2165,7 +2171,10 @@ int agent_build_admin_messages_json(const char* current_user_message, (sender_tier == DIDACTYL_SENDER_WOT ? "wot" : "stranger")); cJSON_AddNumberToObject(dm_event, "created_at", (double)time(NULL)); + const char* prev_current_user_message = g_tools_ctx.template_current_user_message; + g_tools_ctx.template_current_user_message = current_user_message ? current_user_message : ""; char* composed_context = build_context_from_triggers(TRIGGER_TYPE_DM, NULL, dm_event, "api"); + g_tools_ctx.template_current_user_message = prev_current_user_message; cJSON_Delete(dm_event); if (!composed_context) { composed_context = strdup("You are an AI agent. Respond to the message."); @@ -2256,7 +2265,10 @@ void agent_on_message(const char* sender_pubkey_hex, (tier == DIDACTYL_SENDER_WOT ? "wot" : "stranger")); cJSON_AddNumberToObject(dm_event, "created_at", (double)time(NULL)); + const char* prev_current_user_message = g_tools_ctx.template_current_user_message; + g_tools_ctx.template_current_user_message = message ? message : ""; char* dm_context = build_context_from_triggers(TRIGGER_TYPE_DM, NULL, dm_event, "dm"); + g_tools_ctx.template_current_user_message = prev_current_user_message; cJSON_Delete(dm_event); if (!dm_context) { dm_context = strdup("You are an AI agent. Respond to the message."); diff --git a/src/agent.h b/src/agent.h index cbc172f..17d6af1 100644 --- a/src/agent.h +++ b/src/agent.h @@ -25,6 +25,7 @@ tools_context_t* agent_tools_context(void); const char* agent_classify_message_part(cJSON* msg, int idx); int agent_set_context_debug_mode(const char* mode); void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload); +void agent_invalidate_adopted_skills_cache(void); void agent_cleanup(void); #endif \ No newline at end of file diff --git a/src/main.h b/src/main.h index 1659bbd..d860ba8 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ // Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define DIDACTYL_VERSION_MAJOR 0 #define DIDACTYL_VERSION_MINOR 2 -#define DIDACTYL_VERSION_PATCH 25 -#define DIDACTYL_VERSION "v0.2.25" +#define DIDACTYL_VERSION_PATCH 26 +#define DIDACTYL_VERSION "v0.2.26" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/nostr_handler.c b/src/nostr_handler.c index 84a6008..a8c740b 100644 --- a/src/nostr_handler.c +++ b/src/nostr_handler.c @@ -14,6 +14,7 @@ #include "trigger_manager.h" #include "nostr_block_list.h" #include "cashu_wallet.h" +#include "agent.h" #define NIP17_MAX_RELAYS 32 #define NIP17_MAX_GIFT_WRAPS 8 @@ -2445,6 +2446,8 @@ static void on_self_skill_event(cJSON* event, const char* relay_url, void* user_ pthread_mutex_lock(&g_self_skill_mutex); self_skill_cache_upsert_event_locked(event); pthread_mutex_unlock(&g_self_skill_mutex); + + agent_invalidate_adopted_skills_cache(); } static void on_wallet_event(cJSON* event, const char* relay_url, void* user_data) { diff --git a/src/tools/tool_skill.c b/src/tools/tool_skill.c index f370e16..4fba663 100644 --- a/src/tools/tool_skill.c +++ b/src/tools/tool_skill.c @@ -11,6 +11,7 @@ #include "../debug.h" #include "../nostr_handler.h" #include "../trigger_manager.h" +#include "../agent.h" static char* json_error_local(const char* msg) { cJSON* root = cJSON_CreateObject(); @@ -1769,6 +1770,21 @@ char* execute_skill_edit(tools_context_t* ctx, const char* args_json) { return json; } +char* execute_skill_refresh(tools_context_t* ctx, const char* args_json) { + (void)args_json; + if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); + + agent_invalidate_adopted_skills_cache(); + + cJSON* out = cJSON_CreateObject(); + if (!out) return NULL; + cJSON_AddBoolToObject(out, "success", 1); + cJSON_AddStringToObject(out, "message", "adopted skills cache invalidated; next context build will reload skills"); + char* json = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + return json; +} + char* execute_skill_search(const char* args_json) { cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); diff --git a/src/tools/tools_dispatch.c b/src/tools/tools_dispatch.c index f1bcd7c..8344f6f 100644 --- a/src/tools/tools_dispatch.c +++ b/src/tools/tools_dispatch.c @@ -152,6 +152,9 @@ char* tools_execute_legacy(tools_context_t* ctx, const char* tool_name, const ch if (strcmp(tool_name, "skill_edit") == 0) { return execute_skill_edit(ctx, args_json); } + if (strcmp(tool_name, "skill_refresh") == 0) { + return execute_skill_refresh(ctx, args_json); + } if (strcmp(tool_name, "trigger_list") == 0) { return execute_trigger_list(ctx, args_json); } diff --git a/src/tools/tools_internal.h b/src/tools/tools_internal.h index 44f481a..848c905 100644 --- a/src/tools/tools_internal.h +++ b/src/tools/tools_internal.h @@ -63,6 +63,7 @@ char* execute_skill_set(tools_context_t* ctx, const char* args_json); char* execute_skill_adopt(tools_context_t* ctx, const char* args_json); char* execute_skill_remove(tools_context_t* ctx, const char* args_json); char* execute_skill_edit(tools_context_t* ctx, const char* args_json); +char* execute_skill_refresh(tools_context_t* ctx, const char* args_json); char* execute_skill_search(const char* args_json); char* execute_memory_save(tools_context_t* ctx, const char* args_json); diff --git a/src/tools/tools_schema.c b/src/tools/tools_schema.c index bf095c8..dfee0f1 100644 --- a/src/tools/tools_schema.c +++ b/src/tools/tools_schema.c @@ -1077,6 +1077,21 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) { cJSON_AddItemToObject(t25b, "function", t25b_fn); cJSON_AddItemToArray(tools, t25b); + cJSON* t25c = cJSON_CreateObject(); + cJSON* t25c_fn = cJSON_CreateObject(); + cJSON* t25c_params = cJSON_CreateObject(); + cJSON* t25c_props = cJSON_CreateObject(); + + cJSON_AddStringToObject(t25c, "type", "function"); + cJSON_AddStringToObject(t25c_fn, "name", "skill_refresh"); + cJSON_AddStringToObject(t25c_fn, "description", "Invalidate adopted skills cache so next context build reloads latest skill events from cache/relays"); + cJSON_AddStringToObject(t25c_params, "type", "object"); + cJSON_AddItemToObject(t25c_params, "properties", t25c_props); + + cJSON_AddItemToObject(t25c_fn, "parameters", t25c_params); + cJSON_AddItemToObject(t25c, "function", t25c_fn); + cJSON_AddItemToArray(tools, t25c); + cJSON* t26 = cJSON_CreateObject(); cJSON* t26_fn = cJSON_CreateObject(); cJSON* t26_params = cJSON_CreateObject();