From ffed72c9fdfe7edaf763032ce818f285108aade6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 9 Mar 2026 16:29:17 -0400 Subject: [PATCH] v0.0.56 - Load triggers deterministically from startup events before EOSE --- README.md | 4 +-- src/main.c | 6 ++++ src/main.h | 4 +-- src/trigger_manager.c | 71 +++++++++++++++++++++++++++++++++++++++++++ src/trigger_manager.h | 1 + 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 892cb2d..717fa92 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.55 +## Current Status — v0.0.56 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.55 — Defer trigger loading until self-skill EOSE and add trigger subscription debug instrumentation +> Last release update: v0.0.56 — Load triggers deterministically from startup events before EOSE - 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/main.c b/src/main.c index 4244d45..ceb6562 100644 --- a/src/main.c +++ b/src/main.c @@ -271,6 +271,12 @@ int main(int argc, char** argv) { DEBUG_INFO("[didactyl] startup phase: deferred trigger load will run after self-skill EOSE"); + DEBUG_INFO("[didactyl] startup phase: startup-config trigger load begin"); + if (trigger_manager_load_from_startup_events(&trigger_manager) != 0) { + DEBUG_WARN("[didactyl] startup phase: startup-config trigger load failed (continuing)"); + } + DEBUG_INFO("[didactyl] startup phase: startup-config trigger load end"); + DEBUG_INFO("[didactyl] startup phase: subscribe admin context begin"); if (nostr_handler_subscribe_admin_context() != 0) { DEBUG_WARN("[didactyl] startup phase: subscribe admin context failed (continuing)"); diff --git a/src/main.h b/src/main.h index 3c53ff2..000d228 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 55 -#define DIDACTYL_VERSION "v0.0.55" +#define DIDACTYL_VERSION_PATCH 56 +#define DIDACTYL_VERSION "v0.0.56" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/trigger_manager.c b/src/trigger_manager.c index 76b3424..75a0c1f 100644 --- a/src/trigger_manager.c +++ b/src/trigger_manager.c @@ -495,6 +495,77 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) { return 0; } +int trigger_manager_load_from_startup_events(trigger_manager_t* mgr) { + if (!mgr || !mgr->cfg) { + return -1; + } + + int loaded = 0; + int considered = 0; + + for (int i = 0; i < mgr->cfg->startup_event_count; i++) { + startup_event_t* ev = &mgr->cfg->startup_events[i]; + if (!ev) { + continue; + } + + if (ev->kind != 31123 && ev->kind != 31124) { + continue; + } + + considered++; + + if (!ev->content || ev->content[0] == '\0' || !ev->tags_json || ev->tags_json[0] == '\0') { + continue; + } + + cJSON* tags = cJSON_Parse(ev->tags_json); + if (!tags || !cJSON_IsArray(tags)) { + cJSON_Delete(tags); + DEBUG_WARN("[didactyl] startup trigger skip: invalid tags_json for startup event index=%d", i); + continue; + } + + cJSON* d = find_tag_value_string(tags, "d"); + cJSON* trigger = find_tag_value_string(tags, "trigger"); + cJSON* filter = find_tag_value_string(tags, "filter"); + cJSON* action = find_tag_value_string(tags, "action"); + cJSON* enabled = find_tag_value_string(tags, "enabled"); + + const char* slug = (d && cJSON_IsString(d) && d->valuestring) ? d->valuestring : NULL; + const char* trigger_s = (trigger && cJSON_IsString(trigger) && trigger->valuestring) ? trigger->valuestring : NULL; + const char* filter_s = (filter && cJSON_IsString(filter) && filter->valuestring) ? filter->valuestring : NULL; + const char* action_s = (action && cJSON_IsString(action) && action->valuestring) ? action->valuestring : "llm"; + const char* enabled_s = (enabled && cJSON_IsString(enabled) && enabled->valuestring) ? enabled->valuestring : "true"; + + if (!slug || slug[0] == '\0') { + cJSON_Delete(tags); + DEBUG_WARN("[didactyl] startup trigger skip: missing d tag for startup event index=%d", i); + continue; + } + + if (!trigger_s || strcmp(trigger_s, "nostr-subscription") != 0 || !filter_s || filter_s[0] == '\0') { + cJSON_Delete(tags); + continue; + } + + trigger_action_type_t at = (strcmp(action_s, "template") == 0) ? TRIGGER_ACTION_TEMPLATE : TRIGGER_ACTION_LLM; + int is_enabled = (strcmp(enabled_s, "false") == 0 || strcmp(enabled_s, "0") == 0) ? 0 : 1; + + if (trigger_manager_add(mgr, slug, ev->content, filter_s, at, is_enabled) == 0) { + loaded++; + DEBUG_INFO("[didactyl] startup trigger registered slug=%s action=%s enabled=%d", slug, at == TRIGGER_ACTION_TEMPLATE ? "template" : "llm", is_enabled); + } else { + DEBUG_WARN("[didactyl] startup trigger failed slug=%s", slug); + } + + cJSON_Delete(tags); + } + + DEBUG_INFO("[didactyl] trigger manager loaded %d trigger(s) from startup config (considered=%d)", loaded, considered); + return 0; +} + int trigger_manager_add(trigger_manager_t* mgr, const char* skill_slug, const char* content, diff --git a/src/trigger_manager.h b/src/trigger_manager.h index 787612d..09dafeb 100644 --- a/src/trigger_manager.h +++ b/src/trigger_manager.h @@ -40,6 +40,7 @@ typedef struct trigger_manager { int trigger_manager_init(trigger_manager_t* mgr, didactyl_config_t* cfg); int trigger_manager_load_from_skills(trigger_manager_t* mgr); +int trigger_manager_load_from_startup_events(trigger_manager_t* mgr); int trigger_manager_add(trigger_manager_t* mgr, const char* skill_slug, const char* content,