diff --git a/README.md b/README.md index ae6bed5..6bce966 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,11 @@ Agents learn capabilities through skills — Nostr events that any agent can di 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.0.42 +## Current Status — v0.0.43 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.42 — Add self-skill subscription cache and make skill_list read from cache +> Last release update: v0.0.43 — Unify self-authored event cache for skills and adoption list - 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 ed1b962..22f7838 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1632,25 +1632,7 @@ static int refresh_adopted_skills_cache_if_needed(void) { memset(tmp, 0, sizeof(tmp)); int tmp_count = 0; - cJSON* adopt_filter = cJSON_CreateObject(); - cJSON* kinds = cJSON_CreateArray(); - cJSON* authors = cJSON_CreateArray(); - if (!adopt_filter || !kinds || !authors) { - cJSON_Delete(adopt_filter); - cJSON_Delete(kinds); - cJSON_Delete(authors); - return -1; - } - - cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123)); - cJSON_AddItemToObject(adopt_filter, "kinds", kinds); - cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex)); - cJSON_AddItemToObject(adopt_filter, "authors", authors); - cJSON_AddNumberToObject(adopt_filter, "limit", 1); - - char* adoption_json = nostr_handler_query_json(adopt_filter, 2000); - cJSON_Delete(adopt_filter); - + char* adoption_json = nostr_handler_get_self_events_by_kind_json(10123); cJSON* adoption_events = adoption_json ? cJSON_Parse(adoption_json) : NULL; free(adoption_json); @@ -1680,34 +1662,65 @@ static int refresh_adopted_skills_cache_if_needed(void) { continue; } - cJSON* skill_filter = cJSON_CreateObject(); - cJSON* sk_kinds = cJSON_CreateArray(); - cJSON* sk_authors = cJSON_CreateArray(); - cJSON* d_values = cJSON_CreateArray(); - if (!skill_filter || !sk_kinds || !sk_authors || !d_values) { + cJSON* skill_events = NULL; + if (strcmp(skill_author, g_cfg->keys.public_key_hex) == 0) { + char* skill_json = nostr_handler_get_self_events_by_kind_json(skill_kind); + if (skill_json) { + cJSON* all_events = cJSON_Parse(skill_json); + free(skill_json); + if (all_events && cJSON_IsArray(all_events)) { + skill_events = cJSON_CreateArray(); + if (skill_events) { + int all_n = cJSON_GetArraySize(all_events); + for (int ai = 0; ai < all_n; ai++) { + cJSON* ev = cJSON_GetArrayItem(all_events, ai); + cJSON* ev_pubkey = ev ? cJSON_GetObjectItemCaseSensitive(ev, "pubkey") : NULL; + cJSON* ev_tags = ev ? cJSON_GetObjectItemCaseSensitive(ev, "tags") : NULL; + cJSON* ev_d = find_tag_value_string_local(ev_tags, "d"); + if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring || + strcmp(ev_pubkey->valuestring, skill_author) != 0 || + !ev_d || !cJSON_IsString(ev_d) || !ev_d->valuestring || + strcmp(ev_d->valuestring, skill_slug) != 0) { + continue; + } + cJSON* dup = cJSON_Duplicate(ev, 1); + if (dup) { + cJSON_AddItemToArray(skill_events, dup); + } + } + } + } + cJSON_Delete(all_events); + } + } else { + cJSON* skill_filter = cJSON_CreateObject(); + cJSON* sk_kinds = cJSON_CreateArray(); + cJSON* sk_authors = cJSON_CreateArray(); + cJSON* d_values = cJSON_CreateArray(); + if (!skill_filter || !sk_kinds || !sk_authors || !d_values) { + cJSON_Delete(skill_filter); + cJSON_Delete(sk_kinds); + cJSON_Delete(sk_authors); + cJSON_Delete(d_values); + continue; + } + + cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(skill_kind)); + cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds); + cJSON_AddItemToArray(sk_authors, cJSON_CreateString(skill_author)); + cJSON_AddItemToObject(skill_filter, "authors", sk_authors); + cJSON_AddItemToArray(d_values, cJSON_CreateString(skill_slug)); + cJSON_AddItemToObject(skill_filter, "#d", d_values); + cJSON_AddNumberToObject(skill_filter, "limit", 1); + + char* skill_json = nostr_handler_query_json(skill_filter, 2000); cJSON_Delete(skill_filter); - cJSON_Delete(sk_kinds); - cJSON_Delete(sk_authors); - cJSON_Delete(d_values); - continue; + if (skill_json) { + skill_events = cJSON_Parse(skill_json); + free(skill_json); + } } - cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(skill_kind)); - cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds); - cJSON_AddItemToArray(sk_authors, cJSON_CreateString(skill_author)); - cJSON_AddItemToObject(skill_filter, "authors", sk_authors); - cJSON_AddItemToArray(d_values, cJSON_CreateString(skill_slug)); - cJSON_AddItemToObject(skill_filter, "#d", d_values); - cJSON_AddNumberToObject(skill_filter, "limit", 1); - - char* skill_json = nostr_handler_query_json(skill_filter, 2000); - cJSON_Delete(skill_filter); - if (!skill_json) { - continue; - } - - cJSON* skill_events = cJSON_Parse(skill_json); - free(skill_json); if (!skill_events || !cJSON_IsArray(skill_events) || cJSON_GetArraySize(skill_events) <= 0) { cJSON_Delete(skill_events); continue; diff --git a/src/main.h b/src/main.h index 2c28f18..7f3c3d9 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 0 -#define DIDACTYL_VERSION_PATCH 42 -#define DIDACTYL_VERSION "v0.0.42" +#define DIDACTYL_VERSION_PATCH 43 +#define DIDACTYL_VERSION "v0.0.43" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/nostr_handler.c b/src/nostr_handler.c index 6b12320..5b4bd99 100644 --- a/src/nostr_handler.c +++ b/src/nostr_handler.c @@ -401,7 +401,7 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) { } int kind_val = (int)kind->valuedouble; - if (kind_val != 31123 && kind_val != 31124) { + if (kind_val != 31123 && kind_val != 31124 && kind_val != 10123) { return; } @@ -410,7 +410,7 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) { } const char* d_tag = find_tag_value_local(tags, "d"); - if (!d_tag || d_tag[0] == '\0') { + if ((kind_val == 31123 || kind_val == 31124) && (!d_tag || d_tag[0] == '\0')) { return; } @@ -440,13 +440,16 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) { continue; } - const char* ex_d = find_tag_value_local(ex_tags, "d"); - if (!ex_d || strcmp(ex_d, d_tag) != 0) { + int ex_kind_val = (int)ex_kind->valuedouble; + if (ex_kind_val != kind_val || strcmp(ex_pubkey->valuestring, pubkey->valuestring) != 0) { continue; } - if ((int)ex_kind->valuedouble != kind_val || strcmp(ex_pubkey->valuestring, pubkey->valuestring) != 0) { - continue; + if (kind_val == 31123 || kind_val == 31124) { + const char* ex_d = find_tag_value_local(ex_tags, "d"); + if (!ex_d || !d_tag || strcmp(ex_d, d_tag) != 0) { + continue; + } } cJSON* ex_created_at = cJSON_GetObjectItemCaseSensitive(existing, "created_at"); @@ -1267,6 +1270,7 @@ int nostr_handler_subscribe_self_skills(void) { cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31123)); cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31124)); + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123)); cJSON_AddItemToObject(filter, "kinds", kinds); cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex)); cJSON_AddItemToObject(filter, "authors", authors); @@ -1291,7 +1295,7 @@ int nostr_handler_subscribe_self_skills(void) { return -1; } - DEBUG_INFO("[didactyl] self-skill subscriptions active for pubkey %.16s...", g_cfg->keys.public_key_hex); + DEBUG_INFO("[didactyl] self-event subscriptions active for pubkey %.16s...", g_cfg->keys.public_key_hex); return 0; } @@ -1591,6 +1595,12 @@ static int publish_kind_event_to_relays(int kind, relay_count, sent); + if (sent > 0) { + pthread_mutex_lock(&g_self_skill_mutex); + self_skill_cache_upsert_event_locked(event); + pthread_mutex_unlock(&g_self_skill_mutex); + } + cJSON_Delete(event); return sent > 0 ? 0 : -1; } @@ -1782,22 +1792,64 @@ const char* nostr_handler_get_system_context(void) { return g_system_context; } -char* nostr_handler_get_self_skill_events_json(void) { - cJSON* dup = NULL; +char* nostr_handler_get_self_events_by_kind_json(int kind) { + cJSON* out = cJSON_CreateArray(); + if (!out) { + return NULL; + } pthread_mutex_lock(&g_self_skill_mutex); if (g_self_skill_events && cJSON_IsArray(g_self_skill_events)) { - dup = cJSON_Duplicate(g_self_skill_events, 1); + int n = cJSON_GetArraySize(g_self_skill_events); + for (int i = 0; i < n; i++) { + cJSON* ev = cJSON_GetArrayItem(g_self_skill_events, i); + cJSON* ev_kind = ev ? cJSON_GetObjectItemCaseSensitive(ev, "kind") : NULL; + if (!ev_kind || !cJSON_IsNumber(ev_kind) || (int)ev_kind->valuedouble != kind) { + continue; + } + cJSON* dup = cJSON_Duplicate(ev, 1); + if (dup) { + cJSON_AddItemToArray(out, dup); + } + } } pthread_mutex_unlock(&g_self_skill_mutex); - if (!dup) { - return strdup("[]"); + char* json = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + return json ? json : strdup("[]"); +} + +char* nostr_handler_get_self_skill_events_json(void) { + cJSON* out = cJSON_CreateArray(); + if (!out) { + return NULL; } - char* out = cJSON_PrintUnformatted(dup); - cJSON_Delete(dup); - return out; + pthread_mutex_lock(&g_self_skill_mutex); + if (g_self_skill_events && cJSON_IsArray(g_self_skill_events)) { + int n = cJSON_GetArraySize(g_self_skill_events); + for (int i = 0; i < n; i++) { + cJSON* ev = cJSON_GetArrayItem(g_self_skill_events, i); + cJSON* ev_kind = ev ? cJSON_GetObjectItemCaseSensitive(ev, "kind") : NULL; + if (!ev_kind || !cJSON_IsNumber(ev_kind)) { + continue; + } + int k = (int)ev_kind->valuedouble; + if (k != 31123 && k != 31124) { + continue; + } + cJSON* dup = cJSON_Duplicate(ev, 1); + if (dup) { + cJSON_AddItemToArray(out, dup); + } + } + } + pthread_mutex_unlock(&g_self_skill_mutex); + + char* json = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + return json ? json : strdup("[]"); } const char* nostr_handler_get_startup_display_name(void) { diff --git a/src/nostr_handler.h b/src/nostr_handler.h index 8e9df82..2152ad9 100644 --- a/src/nostr_handler.h +++ b/src/nostr_handler.h @@ -30,6 +30,7 @@ typedef struct { int nostr_handler_init(didactyl_config_t* config); int nostr_handler_subscribe_admin_context(void); int nostr_handler_subscribe_self_skills(void); +char* nostr_handler_get_self_events_by_kind_json(int kind); int nostr_handler_subscribe_dms(dm_callback_t callback, void* user_data); int nostr_handler_send_dm(const char* recipient_pubkey_hex, const char* message); int nostr_handler_send_dm_auto(const char* recipient_pubkey_hex, const char* message); diff --git a/src/tools.c b/src/tools.c index 47e68e0..5046363 100644 --- a/src/tools.c +++ b/src/tools.c @@ -893,24 +893,7 @@ static int fetch_adoption_list_tags(tools_context_t* ctx, cJSON** out_tags, char *out_tags = NULL; *out_content = NULL; - cJSON* filter = cJSON_CreateObject(); - cJSON* kinds = cJSON_CreateArray(); - cJSON* authors = cJSON_CreateArray(); - if (!filter || !kinds || !authors) { - cJSON_Delete(filter); - cJSON_Delete(kinds); - cJSON_Delete(authors); - return -1; - } - - cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123)); - cJSON_AddItemToObject(filter, "kinds", kinds); - cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex)); - cJSON_AddItemToObject(filter, "authors", authors); - cJSON_AddNumberToObject(filter, "limit", 1); - - char* events_json = nostr_handler_query_json(filter, 8000); - cJSON_Delete(filter); + char* events_json = nostr_handler_get_self_events_by_kind_json(10123); cJSON* tags = cJSON_CreateArray(); char* content = strdup(""); diff --git a/src/trigger_manager.c b/src/trigger_manager.c index 1d04197..cb7c60b 100644 --- a/src/trigger_manager.c +++ b/src/trigger_manager.c @@ -252,24 +252,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) { return -1; } - cJSON* adopt_filter = cJSON_CreateObject(); - cJSON* kinds = cJSON_CreateArray(); - cJSON* authors = cJSON_CreateArray(); - if (!adopt_filter || !kinds || !authors) { - cJSON_Delete(adopt_filter); - cJSON_Delete(kinds); - cJSON_Delete(authors); - return -1; - } - - cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123)); - cJSON_AddItemToObject(adopt_filter, "kinds", kinds); - cJSON_AddItemToArray(authors, cJSON_CreateString(mgr->cfg->keys.public_key_hex)); - cJSON_AddItemToObject(adopt_filter, "authors", authors); - cJSON_AddNumberToObject(adopt_filter, "limit", 1); - - char* adoption_json = nostr_handler_query_json(adopt_filter, 2000); - cJSON_Delete(adopt_filter); + char* adoption_json = nostr_handler_get_self_events_by_kind_json(10123); if (!adoption_json) { return 0; } @@ -308,34 +291,65 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) { continue; } - cJSON* skill_filter = cJSON_CreateObject(); - cJSON* sk_kinds = cJSON_CreateArray(); - cJSON* sk_authors = cJSON_CreateArray(); - cJSON* d_values = cJSON_CreateArray(); - if (!skill_filter || !sk_kinds || !sk_authors || !d_values) { + cJSON* skill_events = NULL; + if (strcmp(pubkey, mgr->cfg->keys.public_key_hex) == 0) { + char* skill_json = nostr_handler_get_self_events_by_kind_json(kind); + if (skill_json) { + cJSON* all_events = cJSON_Parse(skill_json); + free(skill_json); + if (all_events && cJSON_IsArray(all_events)) { + skill_events = cJSON_CreateArray(); + if (skill_events) { + int all_n = cJSON_GetArraySize(all_events); + for (int ai = 0; ai < all_n; ai++) { + cJSON* ev = cJSON_GetArrayItem(all_events, ai); + cJSON* ev_pubkey = ev ? cJSON_GetObjectItemCaseSensitive(ev, "pubkey") : NULL; + cJSON* ev_tags = ev ? cJSON_GetObjectItemCaseSensitive(ev, "tags") : NULL; + cJSON* ev_d = find_tag_value_string(ev_tags, "d"); + if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring || + strcmp(ev_pubkey->valuestring, pubkey) != 0 || + !ev_d || !cJSON_IsString(ev_d) || !ev_d->valuestring || + strcmp(ev_d->valuestring, slug) != 0) { + continue; + } + cJSON* dup = cJSON_Duplicate(ev, 1); + if (dup) { + cJSON_AddItemToArray(skill_events, dup); + } + } + } + } + cJSON_Delete(all_events); + } + } else { + cJSON* skill_filter = cJSON_CreateObject(); + cJSON* sk_kinds = cJSON_CreateArray(); + cJSON* sk_authors = cJSON_CreateArray(); + cJSON* d_values = cJSON_CreateArray(); + if (!skill_filter || !sk_kinds || !sk_authors || !d_values) { + cJSON_Delete(skill_filter); + cJSON_Delete(sk_kinds); + cJSON_Delete(sk_authors); + cJSON_Delete(d_values); + continue; + } + + cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(kind)); + cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds); + cJSON_AddItemToArray(sk_authors, cJSON_CreateString(pubkey)); + cJSON_AddItemToObject(skill_filter, "authors", sk_authors); + cJSON_AddItemToArray(d_values, cJSON_CreateString(slug)); + cJSON_AddItemToObject(skill_filter, "#d", d_values); + cJSON_AddNumberToObject(skill_filter, "limit", 1); + + char* skill_json = nostr_handler_query_json(skill_filter, 2000); cJSON_Delete(skill_filter); - cJSON_Delete(sk_kinds); - cJSON_Delete(sk_authors); - cJSON_Delete(d_values); - continue; + if (skill_json) { + skill_events = cJSON_Parse(skill_json); + free(skill_json); + } } - cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(kind)); - cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds); - cJSON_AddItemToArray(sk_authors, cJSON_CreateString(pubkey)); - cJSON_AddItemToObject(skill_filter, "authors", sk_authors); - cJSON_AddItemToArray(d_values, cJSON_CreateString(slug)); - cJSON_AddItemToObject(skill_filter, "#d", d_values); - cJSON_AddNumberToObject(skill_filter, "limit", 1); - - char* skill_json = nostr_handler_query_json(skill_filter, 2000); - cJSON_Delete(skill_filter); - if (!skill_json) { - continue; - } - - cJSON* skill_events = cJSON_Parse(skill_json); - free(skill_json); if (!skill_events || !cJSON_IsArray(skill_events) || cJSON_GetArraySize(skill_events) <= 0) { cJSON_Delete(skill_events); continue;