v0.0.65 - Filter skill_list by 10123 adoption tags, gate live trigger registration on adoption, and allow underscores in skill d-tags

This commit is contained in:
Your Name
2026-03-10 10:40:17 -04:00
parent 464c3d473c
commit 5566e7809c
5 changed files with 176 additions and 7 deletions
+2 -2
View File
@@ -55,11 +55,11 @@ Skills support context modes (`inject`, `full`, `override`) and per-skill LLM fa
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.64
## Current Status — v0.0.65
**Active build — this project is barely working. Experiment at your own risk.**
> Last release update: v0.0.64 — Just keeping things fresh
> Last release update: v0.0.65 — Filter skill_list by 10123 adoption tags, gate live trigger registration on adoption, and allow underscores in skill d-tags
- Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected
+51
View File
@@ -246,6 +246,57 @@
]
},
// Kind 31124: Private triggered skill — webhook-echo
// Example webhook trigger that emits a fixed DM response.
{
"kind": 31124,
"content": "When this webhook trigger fires, DM the admin with exactly: WEBHOOK_SOURCE_POC_OK",
"tags": [
["d", "webhook-echo"],
["app", "didactyl"],
["scope", "private"],
["description", "PoC webhook trigger skill"],
["trigger", "webhook"],
["filter", "{}"],
["action", "llm"],
["enabled", "true"]
]
},
// Kind 31124: Private triggered skill — cron_poc
// Example cron trigger that runs every minute.
{
"kind": 31124,
"content": "DM the administrator with the current time, and tell him that your cron skill triggered.",
"tags": [
["d", "cron_poc"],
["app", "didactyl"],
["scope", "private"],
["description", "PoC cron trigger skill"],
["trigger", "cron"],
["filter", "*/1 * * * *"],
["action", "template"],
["enabled", "true"]
]
},
// Kind 31124: Private triggered skill — chain_from_webhook_poc
// Example chain trigger that runs after webhook-echo completes.
{
"kind": 31124,
"content": "LOG: chain_from_webhook_poc fired",
"tags": [
["d", "chain_from_webhook_poc"],
["app", "didactyl"],
["scope", "private"],
["description", "PoC chain trigger skill"],
["trigger", "chain"],
["filter", "webhook-echo"],
["action", "template"],
["enabled", "true"]
]
},
// Kind 10123: Skill adoption list
// References which public skills this agent has adopted.
{
+2 -2
View File
@@ -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 64
#define DIDACTYL_VERSION "v0.0.64"
#define DIDACTYL_VERSION_PATCH 65
#define DIDACTYL_VERSION "v0.0.65"
// Agent metadata
#define DIDACTYL_NAME "Didactyl"
+57
View File
@@ -456,6 +456,58 @@ static int parse_enabled_flag_local(const char* enabled_s) {
return (strcmp(enabled_s, "false") == 0 || strcmp(enabled_s, "0") == 0) ? 0 : 1;
}
static int self_skill_is_adopted_local(int kind, const char* pubkey_hex, const char* d_tag) {
if ((kind != 31123 && kind != 31124) || !pubkey_hex || !d_tag || d_tag[0] == '\0') {
return 0;
}
char addr[256];
snprintf(addr, sizeof(addr), "%d:%s:%s", kind, pubkey_hex, d_tag);
int adopted = 0;
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 && !adopted; i++) {
cJSON* ev = cJSON_GetArrayItem(g_self_skill_events, i);
cJSON* ev_kind = ev ? cJSON_GetObjectItemCaseSensitive(ev, "kind") : NULL;
cJSON* ev_pubkey = ev ? cJSON_GetObjectItemCaseSensitive(ev, "pubkey") : NULL;
cJSON* ev_tags = ev ? cJSON_GetObjectItemCaseSensitive(ev, "tags") : NULL;
if (!ev_kind || !cJSON_IsNumber(ev_kind) || (int)ev_kind->valuedouble != 10123 ||
!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
strcmp(ev_pubkey->valuestring, pubkey_hex) != 0 ||
!ev_tags || !cJSON_IsArray(ev_tags)) {
continue;
}
int tag_count = cJSON_GetArraySize(ev_tags);
for (int ti = 0; ti < tag_count; ti++) {
cJSON* tag = cJSON_GetArrayItem(ev_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) {
continue;
}
if (strcmp(k->valuestring, "a") == 0 && strcmp(v->valuestring, addr) == 0) {
adopted = 1;
break;
}
}
}
}
pthread_mutex_unlock(&g_self_skill_mutex);
return adopted;
}
static void register_trigger_from_self_skill_event(cJSON* event) {
if (!event || !g_cfg || !g_trigger_manager) {
return;
@@ -499,6 +551,11 @@ static void register_trigger_from_self_skill_event(cJSON* event) {
return;
}
if (!self_skill_is_adopted_local(kind_val, pubkey->valuestring, d_tag)) {
DEBUG_INFO("[didactyl] live self-skill trigger ignored (not adopted) d_tag=%s", d_tag);
return;
}
if (action && strcmp(action, "template") == 0) {
DEBUG_WARN("[didactyl] live self-skill trigger action template is deprecated; forcing llm for d_tag=%s", d_tag);
}
+64 -3
View File
@@ -885,7 +885,7 @@ static int validate_skill_d_tag(const char* d_tag) {
prev_dash = 1;
continue;
}
if (!islower(c) && !isdigit(c)) return 0;
if (!islower(c) && !isdigit(c) && c != '_') return 0;
prev_dash = 0;
}
@@ -978,6 +978,37 @@ static int publish_adoption_list(const char* content, cJSON* tags, nostr_publish
return nostr_handler_publish_kind_event(10123, content ? content : "", tags, out_result);
}
static int adoption_tags_contains_address(cJSON* adoption_tags,
int kind,
const char* pubkey,
const char* d_tag) {
if (!adoption_tags || !cJSON_IsArray(adoption_tags) || !pubkey || !d_tag || d_tag[0] == '\0') {
return 0;
}
char addr[256];
snprintf(addr, sizeof(addr), "%d:%s:%s", kind, pubkey, d_tag);
int tag_count = cJSON_GetArraySize(adoption_tags);
for (int i = 0; i < tag_count; i++) {
cJSON* tag = cJSON_GetArrayItem(adoption_tags, i);
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) {
continue;
}
if (strcmp(k->valuestring, "a") == 0 && strcmp(v->valuestring, addr) == 0) {
return 1;
}
}
return 0;
}
static cJSON* extract_skill_summary(cJSON* event) {
if (!event || !cJSON_IsObject(event)) return NULL;
@@ -3751,13 +3782,25 @@ static char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
}
cJSON_Delete(args);
cJSON* adoption_tags = NULL;
char* adoption_content = NULL;
if (fetch_adoption_list_tags(ctx, &adoption_tags, &adoption_content) != 0) {
return json_error("skill_list failed to load adoption list");
}
char* events_json = nostr_handler_get_self_skill_events_json();
if (!events_json) return json_error("skill_list cache unavailable");
if (!events_json) {
cJSON_Delete(adoption_tags);
free(adoption_content);
return json_error("skill_list cache unavailable");
}
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (!events || !cJSON_IsArray(events)) {
cJSON_Delete(events);
cJSON_Delete(adoption_tags);
free(adoption_content);
return json_error("skill_list cache returned invalid JSON");
}
@@ -3767,6 +3810,8 @@ static char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
cJSON_Delete(out);
cJSON_Delete(skills);
cJSON_Delete(events);
cJSON_Delete(adoption_tags);
free(adoption_content);
return NULL;
}
@@ -3776,14 +3821,28 @@ static char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
if (!ev || !cJSON_IsObject(ev)) continue;
cJSON* kind = cJSON_GetObjectItemCaseSensitive(ev, "kind");
if (!kind || !cJSON_IsNumber(kind)) {
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
cJSON* tags = cJSON_GetObjectItemCaseSensitive(ev, "tags");
if (!kind || !cJSON_IsNumber(kind) ||
!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring ||
!tags || !cJSON_IsArray(tags)) {
continue;
}
int kind_val = (int)kind->valuedouble;
if ((kind_val == 31123 && !include_public) || (kind_val == 31124 && !include_private)) {
continue;
}
cJSON* d = find_tag_value_string(tags, "d");
if (!d || !cJSON_IsString(d) || !d->valuestring || d->valuestring[0] == '\0') {
continue;
}
if (!adoption_tags_contains_address(adoption_tags, kind_val, pubkey->valuestring, d->valuestring)) {
continue;
}
cJSON* summary = extract_skill_summary(ev);
if (summary) cJSON_AddItemToArray(skills, summary);
}
@@ -3796,6 +3855,8 @@ static char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(events);
cJSON_Delete(adoption_tags);
free(adoption_content);
return json;
}