v0.2.45 - Expand skill refresh to include adopted authors and clean up skill_list output

This commit is contained in:
Didactyl User
2026-04-16 18:06:20 -04:00
parent c877ca646e
commit 2a091aabf7
4 changed files with 249 additions and 47 deletions

View File

@@ -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. 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.44 ## Current Status — v0.2.45
**Active build — this project is barely working. Experiment at your own risk.** **Active build — this project is barely working. Experiment at your own risk.**
> Last release update: v0.2.44Fix adoption list selection to self-authored latest event in agent and skill tool > Last release update: v0.2.45Expand skill refresh to include adopted authors and clean up skill_list output
- Connects to configured relays with auto-reconnect and relay state transition logging - Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected - Publishes configured startup events per relay as each relay becomes connected

View File

@@ -12,8 +12,8 @@
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros // Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define DIDACTYL_VERSION_MAJOR 0 #define DIDACTYL_VERSION_MAJOR 0
#define DIDACTYL_VERSION_MINOR 2 #define DIDACTYL_VERSION_MINOR 2
#define DIDACTYL_VERSION_PATCH 44 #define DIDACTYL_VERSION_PATCH 45
#define DIDACTYL_VERSION "v0.2.44" #define DIDACTYL_VERSION "v0.2.45"
// Agent metadata // Agent metadata
#define DIDACTYL_NAME "Didactyl" #define DIDACTYL_NAME "Didactyl"

View File

@@ -1535,11 +1535,13 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) {
int is_agent_author = (strcmp(pubkey->valuestring, g_cfg->keys.public_key_hex) == 0); int is_agent_author = (strcmp(pubkey->valuestring, g_cfg->keys.public_key_hex) == 0);
int is_admin_author = (g_cfg->admin.pubkey[0] != '\0' && strcmp(pubkey->valuestring, g_cfg->admin.pubkey) == 0); int is_admin_author = (g_cfg->admin.pubkey[0] != '\0' && strcmp(pubkey->valuestring, g_cfg->admin.pubkey) == 0);
if (!is_agent_author && !is_admin_author) { if (kind_val == 10123) {
return; if (!is_agent_author && !is_admin_author) {
return;
}
} }
if (is_admin_author && kind_val != 31123) { if (is_admin_author && kind_val == 31124) {
return; return;
} }
@@ -3736,6 +3738,114 @@ int nostr_handler_refresh_self_skill_cache_from_relays(int timeout_ms) {
return -1; return -1;
} }
cJSON* adopted_authors = cJSON_CreateObject();
if (adopted_authors) {
cJSON* latest_self_adoption = NULL;
double latest_self_adoption_created = -1.0;
int n0 = cJSON_GetArraySize(events);
for (int i = 0; i < n0; i++) {
cJSON* ev = cJSON_GetArrayItem(events, i);
if (!ev || !cJSON_IsObject(ev)) {
continue;
}
cJSON* kind = cJSON_GetObjectItemCaseSensitive(ev, "kind");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
if (!kind || !cJSON_IsNumber(kind) || (int)kind->valuedouble != 10123 ||
!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring ||
strcmp(pubkey->valuestring, g_cfg->keys.public_key_hex) != 0) {
continue;
}
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
if (!latest_self_adoption || created > latest_self_adoption_created) {
latest_self_adoption = ev;
latest_self_adoption_created = created;
}
}
if (latest_self_adoption) {
cJSON* tags = cJSON_GetObjectItemCaseSensitive(latest_self_adoption, "tags");
if (tags && cJSON_IsArray(tags)) {
int tn = cJSON_GetArraySize(tags);
for (int ti = 0; ti < tn; ti++) {
cJSON* tag = cJSON_GetArrayItem(tags, ti);
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
continue;
}
cJSON* k = cJSON_GetArrayItem(tag, 0);
cJSON* v = cJSON_GetArrayItem(tag, 1);
if (!k || !v || !cJSON_IsString(k) || !k->valuestring ||
!cJSON_IsString(v) || !v->valuestring || strcmp(k->valuestring, "a") != 0) {
continue;
}
int adopted_kind = 0;
char adopted_pubkey[65] = {0};
char adopted_d_tag[65] = {0};
if (parse_skill_address_local(v->valuestring, &adopted_kind, adopted_pubkey, adopted_d_tag) != 0) {
continue;
}
if (adopted_pubkey[0] == '\0') {
continue;
}
if (!cJSON_GetObjectItemCaseSensitive(adopted_authors, adopted_pubkey)) {
cJSON_AddBoolToObject(adopted_authors, adopted_pubkey, 1);
}
}
}
}
int adopted_author_count = 0;
cJSON* adopted_it = NULL;
cJSON_ArrayForEach(adopted_it, adopted_authors) {
adopted_author_count++;
}
if (adopted_author_count > 0) {
cJSON* adopted_filter = cJSON_CreateObject();
cJSON* adopted_kinds = cJSON_CreateArray();
cJSON* adopted_author_arr = cJSON_CreateArray();
if (adopted_filter && adopted_kinds && adopted_author_arr) {
cJSON_AddItemToArray(adopted_kinds, cJSON_CreateNumber(31123));
cJSON_AddItemToArray(adopted_kinds, cJSON_CreateNumber(31124));
cJSON_AddItemToObject(adopted_filter, "kinds", adopted_kinds);
cJSON_ArrayForEach(adopted_it, adopted_authors) {
if (adopted_it->string && adopted_it->string[0] != '\0') {
cJSON_AddItemToArray(adopted_author_arr, cJSON_CreateString(adopted_it->string));
}
}
cJSON_AddItemToObject(adopted_filter, "authors", adopted_author_arr);
cJSON_AddNumberToObject(adopted_filter, "limit", 1000);
char* adopted_json = nostr_handler_query_json(adopted_filter, effective_timeout_ms);
if (adopted_json) {
cJSON* adopted_events = cJSON_Parse(adopted_json);
free(adopted_json);
if (adopted_events && cJSON_IsArray(adopted_events)) {
int an = cJSON_GetArraySize(adopted_events);
for (int ai = 0; ai < an; ai++) {
cJSON* ev = cJSON_GetArrayItem(adopted_events, ai);
cJSON* dup = ev ? cJSON_Duplicate(ev, 1) : NULL;
if (dup) {
cJSON_AddItemToArray(events, dup);
}
}
}
cJSON_Delete(adopted_events);
}
} else {
cJSON_Delete(adopted_filter);
cJSON_Delete(adopted_kinds);
cJSON_Delete(adopted_author_arr);
}
cJSON_Delete(adopted_filter);
}
}
cJSON_Delete(adopted_authors);
int merged = 0; int merged = 0;
pthread_mutex_lock(&g_self_skill_mutex); pthread_mutex_lock(&g_self_skill_mutex);
int n = cJSON_GetArraySize(events); int n = cJSON_GetArraySize(events);

View File

@@ -3,6 +3,8 @@
#include "tools_internal.h" #include "tools_internal.h"
#include <ctype.h> #include <ctype.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -131,6 +133,52 @@ static cJSON* parse_args_local(const char* args_json) {
return args; return args;
} }
static int append_textf_skill_list_local(char** out, size_t* cap, size_t* used, const char* fmt, ...) {
if (!out || !cap || !used || !fmt) {
return -1;
}
va_list ap;
va_start(ap, fmt);
va_list ap2;
va_copy(ap2, ap);
int needed = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (needed < 0) {
va_end(ap2);
return -1;
}
size_t need = (size_t)needed;
if (*used + need + 1U > *cap) {
size_t new_cap = (*cap > 0U) ? *cap : 256U;
while (*used + need + 1U > new_cap) {
if (new_cap > (SIZE_MAX / 2U)) {
va_end(ap2);
return -1;
}
new_cap *= 2U;
}
char* grown = (char*)realloc(*out, new_cap);
if (!grown) {
va_end(ap2);
return -1;
}
*out = grown;
*cap = new_cap;
}
int wrote = vsnprintf((*out) + (*used), *cap - *used, fmt, ap2);
va_end(ap2);
if (wrote < 0) {
return -1;
}
*used += (size_t)wrote;
return 0;
}
static int is_hex_string_len_local(const char* s, size_t expected_len) { static int is_hex_string_len_local(const char* s, size_t expected_len) {
if (!s || strlen(s) != expected_len) return 0; if (!s || strlen(s) != expected_len) return 0;
for (size_t i = 0; i < expected_len; i++) { for (size_t i = 0; i < expected_len; i++) {
@@ -1192,11 +1240,9 @@ char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
} }
cJSON* out = cJSON_CreateObject(); cJSON* out = cJSON_CreateObject();
cJSON* summary_rows = cJSON_CreateArray();
cJSON* skills = cJSON_CreateArray(); cJSON* skills = cJSON_CreateArray();
if (!out || !summary_rows || !skills) { if (!out || !skills) {
cJSON_Delete(out); cJSON_Delete(out);
cJSON_Delete(summary_rows);
cJSON_Delete(skills); cJSON_Delete(skills);
cJSON_Delete(events); cJSON_Delete(events);
cJSON_Delete(adoption_tags); cJSON_Delete(adoption_tags);
@@ -1204,7 +1250,21 @@ char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
return NULL; return NULL;
} }
size_t rendered_cap = 512U;
size_t rendered_used = 0U;
char* rendered_lines = (char*)malloc(rendered_cap);
if (!rendered_lines) {
cJSON_Delete(out);
cJSON_Delete(skills);
cJSON_Delete(events);
cJSON_Delete(adoption_tags);
free(adoption_content);
return NULL;
}
rendered_lines[0] = '\0';
int adopted_count = 0; int adopted_count = 0;
int skill_count = 0;
int event_count = cJSON_GetArraySize(events); int event_count = cJSON_GetArraySize(events);
for (int i = 0; i < event_count; i++) { for (int i = 0; i < event_count; i++) {
cJSON* ev = cJSON_GetArrayItem(events, i); cJSON* ev = cJSON_GetArrayItem(events, i);
@@ -1236,59 +1296,91 @@ char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
if (adopted_filter >= 0 && is_adopted != adopted_filter) { if (adopted_filter >= 0 && is_adopted != adopted_filter) {
continue; continue;
} }
const char* owner = skill_owner_label_local(ctx->cfg, pubkey->valuestring);
cJSON* scope_tag = find_tag_value_string_local(tags, "scope");
cJSON* desc_tag = find_tag_value_string_local(tags, "description");
const char* scope_out = (scope_tag && cJSON_IsString(scope_tag) && scope_tag->valuestring)
? scope_tag->valuestring
: ((kind_val == 31124) ? "private" : "public");
const char* desc_out = (desc_tag && cJSON_IsString(desc_tag) && desc_tag->valuestring)
? desc_tag->valuestring
: "";
cJSON* row = cJSON_CreateObject();
if (!row) {
continue;
}
cJSON_AddStringToObject(row, "d_tag", d->valuestring);
cJSON_AddStringToObject(row, "owner", owner);
cJSON_AddStringToObject(row, "scope", scope_out);
cJSON_AddNumberToObject(row, "kind", kind_val);
cJSON_AddBoolToObject(row, "adopted", is_adopted ? 1 : 0);
if (desc_out[0] != '\0') {
cJSON_AddStringToObject(row, "description", desc_out);
}
cJSON_AddItemToArray(skills, row);
if (is_adopted) { if (is_adopted) {
adopted_count++; adopted_count++;
} }
skill_count++;
const char* owner = skill_owner_label_local(ctx->cfg, pubkey->valuestring); if (append_textf_skill_list_local(&rendered_lines,
&rendered_cap,
cJSON* detail = extract_skill_summary_local(ev); &rendered_used,
if (detail) { "- %s | owner=%s | scope=%s | adopted=%s%s%s\n",
cJSON_AddStringToObject(detail, "owner", owner); d->valuestring,
cJSON_AddBoolToObject(detail, "adopted", is_adopted ? 1 : 0); owner,
cJSON_AddItemToArray(skills, detail); scope_out,
} is_adopted ? "yes" : "no",
desc_out[0] ? " | description=" : "",
cJSON* row = cJSON_CreateObject(); desc_out) != 0) {
if (row) { free(rendered_lines);
cJSON_AddStringToObject(row, "d_tag", d->valuestring); cJSON_Delete(out);
cJSON_AddStringToObject(row, "owner", owner); cJSON_Delete(skills);
cJSON_AddBoolToObject(row, "adopted", is_adopted ? 1 : 0); cJSON_Delete(events);
cJSON_AddItemToArray(summary_rows, row); cJSON_Delete(adoption_tags);
free(adoption_content);
return NULL;
} }
} }
cJSON* skills_pretty = cJSON_CreateArray(); if (skill_count == 0) {
if (!skills_pretty) { if (append_textf_skill_list_local(&rendered_lines, &rendered_cap, &rendered_used, "- (none)\n") != 0) {
free(rendered_lines);
cJSON_Delete(out);
cJSON_Delete(skills);
cJSON_Delete(events);
cJSON_Delete(adoption_tags);
free(adoption_content);
return NULL;
}
}
char header[128];
snprintf(header, sizeof(header), "Skills: %d total (%d adopted)\n", skill_count, adopted_count);
size_t header_len = strlen(header);
char* rendered = (char*)malloc(header_len + rendered_used + 1U);
if (!rendered) {
free(rendered_lines);
cJSON_Delete(out); cJSON_Delete(out);
cJSON_Delete(skills);
cJSON_Delete(events); cJSON_Delete(events);
cJSON_Delete(adoption_tags); cJSON_Delete(adoption_tags);
free(adoption_content); free(adoption_content);
return NULL; return NULL;
} }
memcpy(rendered, header, header_len);
int skill_count = cJSON_GetArraySize(skills); memcpy(rendered + header_len, rendered_lines, rendered_used + 1U);
for (int i = 0; i < skill_count; i++) { free(rendered_lines);
cJSON* skill_item = cJSON_GetArrayItem(skills, i);
if (!skill_item) continue;
char* skill_json = cJSON_PrintUnformatted(skill_item);
if (!skill_json) continue;
cJSON* block = cJSON_CreateString(skill_json);
free(skill_json);
if (block) {
cJSON_AddItemToArray(skills_pretty, block);
}
}
cJSON_AddBoolToObject(out, "success", 1); cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddBoolToObject(out, "fallback_used", 0); cJSON_AddNumberToObject(out, "count", skill_count);
cJSON_AddStringToObject(out, "skills_separator", "\n\n\n");
cJSON_AddItemToObject(out, "summary", summary_rows);
cJSON_AddItemToObject(out, "skills", skills);
cJSON_AddItemToObject(out, "skills_pretty", skills_pretty);
cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(skills));
cJSON_AddNumberToObject(out, "adopted_count", adopted_count); cJSON_AddNumberToObject(out, "adopted_count", adopted_count);
cJSON_AddItemToObject(out, "skills", skills);
cJSON_AddStringToObject(out, "rendered", rendered);
free(rendered);
char* json = cJSON_PrintUnformatted(out); char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out); cJSON_Delete(out);