diff --git a/README.md b/README.md index 717fa92..fd98dd6 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,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.56 +## Current Status — v0.0.57 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.56 — Load triggers deterministically from startup events before EOSE +> Last release update: v0.0.57 — Register triggers live from self-skill websocket events - 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/context.log.md b/context.log.md index 91b19c5..5576c2f 100644 --- a/context.log.md +++ b/context.log.md @@ -1,3 +1,46 @@ +```text +Context Log - not seen by model +timestamp=2026-03-09 16:30:42 +phase=llm_trigger +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-opus-4.6 +context_bytes=383 +approx_tokens=95 +``` + +Triggering event JSON: +{"created_at":1773088242,"content":"Good day everyone.","tags":[],"kind":1,"pubkey":"8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e","id":"b431825da03f7e7d608cefccd01d32fb9b0a0034d6df8d37e28f3c218dbf95d1","sig":"ba3ca4addb8a7cc008a4355544a39e469bf9f2bc94c3aa69e9021875158d60e3d9324b65c60fe4d75ca6ab12ef6bc27bbd659b2e4a3a61f8366675fda08019ad"} + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-09 16:30:27 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-opus-4.6 +context_bytes=683 +approx_tokens=170 +``` + +slash=/trigger_list +result_json={"success":true,"count":1,"active":1,"triggers":[{"skill_slug":"cheerleader","filter_json":"{\"kinds\":[1],\"authors\":[\"8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e\"]}","action":"llm","enabled":true,"subscribed":true,"last_fired":0,"last_seen_created_at":0}]} +result_markdown=- **success:** true +- **count:** 1 +- **active:** 1 +- **triggers:** + - + - **skill_slug:** cheerleader + - **filter_json:** {"kinds":[1],"authors":["8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e"]} + - **action:** llm + - **enabled:** true + - **subscribed:** true + - **last_fired:** 0 + - **last_seen_created_at:** 0 + + +--- + ```text Context Log - not seen by model timestamp=2026-03-09 16:12:06 diff --git a/src/main.c b/src/main.c index ceb6562..4eabe19 100644 --- a/src/main.c +++ b/src/main.c @@ -262,6 +262,7 @@ int main(int argc, char** argv) { return 1; } agent_set_trigger_manager(&trigger_manager); + nostr_handler_set_trigger_manager(&trigger_manager); deferred_trigger_load_ctx_t deferred_trigger_ctx = { .trigger_manager = &trigger_manager, diff --git a/src/main.h b/src/main.h index 000d228..acd978a 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 56 -#define DIDACTYL_VERSION "v0.0.56" +#define DIDACTYL_VERSION_PATCH 57 +#define DIDACTYL_VERSION "v0.0.57" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/nostr_handler.c b/src/nostr_handler.c index e56f1d1..2d3cab6 100644 --- a/src/nostr_handler.c +++ b/src/nostr_handler.c @@ -11,6 +11,7 @@ #include "../../nostr_core_lib/cjson/cJSON.h" #include "../../nostr_core_lib/nostr_core/nostr_core.h" #include "debug.h" +#include "trigger_manager.h" #define NIP17_MAX_RELAYS 32 #define NIP17_MAX_GIFT_WRAPS 8 @@ -51,6 +52,8 @@ static pthread_mutex_t g_self_skill_mutex = PTHREAD_MUTEX_INITIALIZER; static nostr_self_skill_eose_cb_t g_self_skill_eose_cb = NULL; static void* g_self_skill_eose_user_data = NULL; +static struct trigger_manager* g_trigger_manager = NULL; + static char g_seen_dm_ids[DM_DEDUP_CACHE_SIZE][65]; static int g_seen_dm_count = 0; static int g_seen_dm_next = 0; @@ -269,6 +272,7 @@ static void upsert_kind1_note(time_t created_at, const char* content); static int startup_self_kind1_exists(void); static void load_startup_display_name(void); static void build_startup_kind1_content(char* out, size_t out_size, const char* fallback); +static void register_trigger_from_self_skill_event(cJSON* event); static void on_self_skill_event(cJSON* event, const char* relay_url, void* user_data); static void dm_history_clear_locked(void); static void dm_history_remember(const char* peer_pubkey_hex, const char* content, int incoming, time_t created_at); @@ -445,6 +449,72 @@ static const char* find_tag_value_local(cJSON* tags, const char* key) { return NULL; } +static int parse_enabled_flag_local(const char* enabled_s) { + if (!enabled_s || enabled_s[0] == '\0') { + return 1; + } + return (strcmp(enabled_s, "false") == 0 || strcmp(enabled_s, "0") == 0) ? 0 : 1; +} + +static void register_trigger_from_self_skill_event(cJSON* event) { + if (!event || !g_cfg || !g_trigger_manager) { + return; + } + + cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind"); + cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey"); + cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content"); + cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags"); + + if (!kind || !cJSON_IsNumber(kind) || + !pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring || + !content || !cJSON_IsString(content) || !content->valuestring || + !tags || !cJSON_IsArray(tags)) { + return; + } + + int kind_val = (int)kind->valuedouble; + if (kind_val != 31123 && kind_val != 31124) { + return; + } + + if (strcmp(pubkey->valuestring, g_cfg->keys.public_key_hex) != 0) { + return; + } + + const char* slug = find_tag_value_local(tags, "d"); + const char* trigger = find_tag_value_local(tags, "trigger"); + const char* filter = find_tag_value_local(tags, "filter"); + const char* action = find_tag_value_local(tags, "action"); + const char* enabled = find_tag_value_local(tags, "enabled"); + + if (!slug || slug[0] == '\0' || + !trigger || strcmp(trigger, "nostr-subscription") != 0 || + !filter || filter[0] == '\0') { + return; + } + + trigger_action_type_t action_type = (action && strcmp(action, "template") == 0) + ? TRIGGER_ACTION_TEMPLATE + : TRIGGER_ACTION_LLM; + int is_enabled = parse_enabled_flag_local(enabled); + + int rc = trigger_manager_add((trigger_manager_t*)g_trigger_manager, + slug, + content->valuestring, + filter, + action_type, + is_enabled); + if (rc == 0) { + DEBUG_INFO("[didactyl] live self-skill trigger registered slug=%s action=%s enabled=%d", + slug, + action_type == TRIGGER_ACTION_TEMPLATE ? "template" : "llm", + is_enabled); + } else { + DEBUG_WARN("[didactyl] live self-skill trigger register failed slug=%s", slug); + } +} + static void self_skill_cache_upsert_event_locked(cJSON* event) { if (!event || !cJSON_IsObject(event)) { return; @@ -1165,6 +1235,8 @@ static void on_self_skill_event(cJSON* event, const char* relay_url, void* user_ return; } + register_trigger_from_self_skill_event(event); + pthread_mutex_lock(&g_self_skill_mutex); self_skill_cache_upsert_event_locked(event); pthread_mutex_unlock(&g_self_skill_mutex); @@ -1333,6 +1405,10 @@ int nostr_handler_subscribe_admin_context(void) { return rc; } +void nostr_handler_set_trigger_manager(struct trigger_manager* trigger_manager) { + g_trigger_manager = trigger_manager; +} + int nostr_handler_subscribe_self_skills(void) { if (!g_cfg || !g_pool) { return -1; diff --git a/src/nostr_handler.h b/src/nostr_handler.h index 1d4ecc3..05485e3 100644 --- a/src/nostr_handler.h +++ b/src/nostr_handler.h @@ -5,6 +5,8 @@ #include "cjson/cJSON.h" #include "../nostr_core_lib/nostr_core/nostr_core.h" +struct trigger_manager; + typedef enum { DIDACTYL_SENDER_ADMIN = 1, DIDACTYL_SENDER_WOT = 2, @@ -31,6 +33,7 @@ typedef struct { typedef void (*nostr_self_skill_eose_cb_t)(int event_count, void* user_data); int nostr_handler_init(didactyl_config_t* config); +void nostr_handler_set_trigger_manager(struct trigger_manager* trigger_manager); 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);