664 lines
22 KiB
C
664 lines
22 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "trigger_manager.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "agent.h"
|
|
#include "cjson/cJSON.h"
|
|
#include "debug.h"
|
|
#include "nostr_handler.h"
|
|
|
|
static int clamp_enabled(int enabled) {
|
|
return enabled ? 1 : 0;
|
|
}
|
|
|
|
static int ensure_capacity(trigger_manager_t* mgr, int needed) {
|
|
if (!mgr || needed <= 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (mgr->capacity >= needed) {
|
|
return 0;
|
|
}
|
|
|
|
int new_cap = mgr->capacity > 0 ? mgr->capacity : TRIGGER_DEFAULT_MAX_ACTIVE;
|
|
while (new_cap < needed) {
|
|
if (new_cap > 1024) {
|
|
return -1;
|
|
}
|
|
new_cap *= 2;
|
|
}
|
|
|
|
active_trigger_t* grown = (active_trigger_t*)realloc(mgr->triggers, (size_t)new_cap * sizeof(active_trigger_t));
|
|
if (!grown) {
|
|
return -1;
|
|
}
|
|
|
|
if (new_cap > mgr->capacity) {
|
|
memset(&grown[mgr->capacity], 0, (size_t)(new_cap - mgr->capacity) * sizeof(active_trigger_t));
|
|
}
|
|
|
|
mgr->triggers = grown;
|
|
mgr->capacity = new_cap;
|
|
return 0;
|
|
}
|
|
|
|
static int find_trigger_index_locked(trigger_manager_t* mgr, const char* skill_slug) {
|
|
if (!mgr || !skill_slug || skill_slug[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
for (int i = 0; i < mgr->count; i++) {
|
|
if (strncmp(mgr->triggers[i].skill_slug, skill_slug, TRIGGER_SKILL_SLUG_MAX) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static cJSON* find_tag_value_string(cJSON* tags, const char* key) {
|
|
if (!tags || !key || !cJSON_IsArray(tags)) return NULL;
|
|
|
|
int n = cJSON_GetArraySize(tags);
|
|
for (int i = 0; i < n; i++) {
|
|
cJSON* tag = cJSON_GetArrayItem(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) && cJSON_IsString(v) &&
|
|
k->valuestring && v->valuestring && strcmp(k->valuestring, key) == 0) {
|
|
return v;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int parse_address_tag(const char* addr, int* out_kind, char out_pubkey[65], char out_slug[65]) {
|
|
if (!addr || !out_kind || !out_pubkey || !out_slug) {
|
|
return -1;
|
|
}
|
|
|
|
const char* p1 = strchr(addr, ':');
|
|
if (!p1) return -1;
|
|
const char* p2 = strchr(p1 + 1, ':');
|
|
if (!p2) return -1;
|
|
|
|
char kind_buf[16] = {0};
|
|
size_t kind_len = (size_t)(p1 - addr);
|
|
if (kind_len == 0 || kind_len >= sizeof(kind_buf)) return -1;
|
|
memcpy(kind_buf, addr, kind_len);
|
|
|
|
int kind = atoi(kind_buf);
|
|
if (kind != 31123 && kind != 31124) return -1;
|
|
|
|
size_t pub_len = (size_t)(p2 - (p1 + 1));
|
|
if (pub_len != 64U) return -1;
|
|
memcpy(out_pubkey, p1 + 1, 64U);
|
|
out_pubkey[64] = '\0';
|
|
|
|
size_t slug_len = strlen(p2 + 1);
|
|
if (slug_len == 0 || slug_len >= 65U) return -1;
|
|
memcpy(out_slug, p2 + 1, slug_len + 1U);
|
|
|
|
*out_kind = kind;
|
|
return 0;
|
|
}
|
|
|
|
static char* build_template_output(const active_trigger_t* t, cJSON* event, const char* relay_url) {
|
|
(void)relay_url;
|
|
|
|
if (!t || !event) return NULL;
|
|
|
|
cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content");
|
|
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
|
cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id");
|
|
cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
|
|
|
|
const char* content_s = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : "";
|
|
const char* pubkey_s = (pubkey && cJSON_IsString(pubkey) && pubkey->valuestring) ? pubkey->valuestring : "unknown";
|
|
const char* id_s = (id && cJSON_IsString(id) && id->valuestring) ? id->valuestring : "";
|
|
int kind_i = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1;
|
|
|
|
const char* tmpl = t->skill_content;
|
|
if (!tmpl || tmpl[0] == '\0') {
|
|
return NULL;
|
|
}
|
|
|
|
char out[TRIGGER_SKILL_CONTENT_MAX + 256];
|
|
snprintf(out,
|
|
sizeof(out),
|
|
"%s\n\n[event id=%s kind=%d pubkey=%s]\n%s",
|
|
tmpl,
|
|
id_s,
|
|
kind_i,
|
|
pubkey_s,
|
|
content_s);
|
|
|
|
return strdup(out);
|
|
}
|
|
|
|
static void execute_template_action(trigger_manager_t* mgr,
|
|
const active_trigger_t* t,
|
|
cJSON* event,
|
|
const char* relay_url) {
|
|
if (!mgr || !mgr->cfg || !t || !event) {
|
|
return;
|
|
}
|
|
|
|
char* rendered = build_template_output(t, event, relay_url);
|
|
if (!rendered) {
|
|
return;
|
|
}
|
|
|
|
if (strncmp(rendered, "DM admin:", 9) == 0) {
|
|
const char* body = rendered + 9;
|
|
while (*body == ' ') body++;
|
|
(void)nostr_handler_send_dm_auto(mgr->cfg->admin.pubkey, body);
|
|
} else if (strncmp(rendered, "POST:", 5) == 0) {
|
|
const char* body = rendered + 5;
|
|
while (*body == ' ') body++;
|
|
(void)nostr_handler_publish_kind_event(1, body, NULL, NULL);
|
|
} else if (strncmp(rendered, "LOG:", 4) == 0) {
|
|
const char* body = rendered + 4;
|
|
while (*body == ' ') body++;
|
|
DEBUG_INFO("[didactyl] trigger template log (%s): %s", t->skill_slug, body);
|
|
} else {
|
|
(void)nostr_handler_send_dm_auto(mgr->cfg->admin.pubkey, rendered);
|
|
}
|
|
|
|
free(rendered);
|
|
}
|
|
|
|
static void execute_llm_action(const active_trigger_t* t, cJSON* event, const char* relay_url) {
|
|
if (!t || !event) {
|
|
return;
|
|
}
|
|
agent_on_trigger(t->skill_slug, t->skill_content, event, relay_url);
|
|
}
|
|
|
|
static int maybe_fire_trigger_locked(trigger_manager_t* mgr, int index, cJSON* event, const char* relay_url) {
|
|
active_trigger_t* t = &mgr->triggers[index];
|
|
if (!t->enabled) {
|
|
return 0;
|
|
}
|
|
|
|
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
|
time_t created_ts = (created_at && cJSON_IsNumber(created_at)) ? (time_t)created_at->valuedouble : 0;
|
|
if (created_ts > 0 && created_ts <= t->last_seen_created_at) {
|
|
return 0;
|
|
}
|
|
|
|
time_t now = time(NULL);
|
|
int cooldown = mgr->cfg->triggers.cooldown_seconds;
|
|
if (cooldown < 0) cooldown = 0;
|
|
if (cooldown > 0 && t->last_fired > 0 && (now - t->last_fired) < cooldown) {
|
|
return 0;
|
|
}
|
|
|
|
t->last_fired = now;
|
|
if (created_ts > t->last_seen_created_at) {
|
|
t->last_seen_created_at = created_ts;
|
|
}
|
|
|
|
active_trigger_t trigger_copy = *t;
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
|
|
if (trigger_copy.action_type == TRIGGER_ACTION_TEMPLATE) {
|
|
execute_template_action(mgr, &trigger_copy, event, relay_url);
|
|
} else {
|
|
execute_llm_action(&trigger_copy, event, relay_url);
|
|
}
|
|
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
return 1;
|
|
}
|
|
|
|
int trigger_manager_init(trigger_manager_t* mgr, didactyl_config_t* cfg) {
|
|
if (!mgr || !cfg) {
|
|
return -1;
|
|
}
|
|
|
|
memset(mgr, 0, sizeof(*mgr));
|
|
mgr->cfg = cfg;
|
|
|
|
if (pthread_mutex_init(&mgr->mutex, NULL) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
int initial_cap = cfg->triggers.max_active > 0 ? cfg->triggers.max_active : TRIGGER_DEFAULT_MAX_ACTIVE;
|
|
mgr->capacity = initial_cap;
|
|
mgr->triggers = (active_trigger_t*)calloc((size_t)mgr->capacity, sizeof(active_trigger_t));
|
|
if (!mgr->triggers) {
|
|
pthread_mutex_destroy(&mgr->mutex);
|
|
memset(mgr, 0, sizeof(*mgr));
|
|
return -1;
|
|
}
|
|
|
|
mgr->last_poll_at = time(NULL);
|
|
|
|
DEBUG_INFO("[didactyl] trigger manager initialized (capacity=%d)", mgr->capacity);
|
|
return 0;
|
|
}
|
|
|
|
int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
|
if (!mgr || !mgr->cfg) {
|
|
return -1;
|
|
}
|
|
|
|
char* adoption_json = nostr_handler_get_self_events_by_kind_json(10123);
|
|
if (!adoption_json) {
|
|
return 0;
|
|
}
|
|
|
|
cJSON* adoption_events = cJSON_Parse(adoption_json);
|
|
free(adoption_json);
|
|
if (!adoption_events || !cJSON_IsArray(adoption_events) || cJSON_GetArraySize(adoption_events) <= 0) {
|
|
cJSON_Delete(adoption_events);
|
|
return 0;
|
|
}
|
|
|
|
cJSON* list_event = cJSON_GetArrayItem(adoption_events, 0);
|
|
cJSON* list_tags = list_event ? cJSON_GetObjectItemCaseSensitive(list_event, "tags") : NULL;
|
|
if (!list_tags || !cJSON_IsArray(list_tags)) {
|
|
cJSON_Delete(adoption_events);
|
|
return 0;
|
|
}
|
|
|
|
int loaded = 0;
|
|
int tn = cJSON_GetArraySize(list_tags);
|
|
for (int i = 0; i < tn; i++) {
|
|
cJSON* tag = cJSON_GetArrayItem(list_tags, i);
|
|
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) continue;
|
|
|
|
cJSON* key = cJSON_GetArrayItem(tag, 0);
|
|
cJSON* val = cJSON_GetArrayItem(tag, 1);
|
|
if (!key || !val || !cJSON_IsString(key) || !cJSON_IsString(val) ||
|
|
!key->valuestring || !val->valuestring || strcmp(key->valuestring, "a") != 0) {
|
|
continue;
|
|
}
|
|
|
|
int kind = 0;
|
|
char pubkey[65] = {0};
|
|
char slug[65] = {0};
|
|
if (parse_address_tag(val->valuestring, &kind, pubkey, slug) != 0) {
|
|
continue;
|
|
}
|
|
|
|
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);
|
|
if (skill_json) {
|
|
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;
|
|
}
|
|
|
|
cJSON* skill_event = cJSON_GetArrayItem(skill_events, 0);
|
|
cJSON* content = skill_event ? cJSON_GetObjectItemCaseSensitive(skill_event, "content") : NULL;
|
|
cJSON* tags = skill_event ? cJSON_GetObjectItemCaseSensitive(skill_event, "tags") : NULL;
|
|
if (!content || !cJSON_IsString(content) || !content->valuestring || !tags || !cJSON_IsArray(tags)) {
|
|
cJSON_Delete(skill_events);
|
|
continue;
|
|
}
|
|
|
|
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* 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 (trigger_s && strcmp(trigger_s, "nostr-subscription") == 0 && filter_s && filter_s[0] != '\0') {
|
|
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, content->valuestring, filter_s, at, is_enabled) == 0) {
|
|
loaded++;
|
|
}
|
|
}
|
|
|
|
cJSON_Delete(skill_events);
|
|
}
|
|
|
|
cJSON_Delete(adoption_events);
|
|
DEBUG_INFO("[didactyl] trigger manager loaded %d trigger(s) from skills", loaded);
|
|
return 0;
|
|
}
|
|
|
|
int trigger_manager_add(trigger_manager_t* mgr,
|
|
const char* skill_slug,
|
|
const char* content,
|
|
const char* filter_json,
|
|
trigger_action_type_t action_type,
|
|
int enabled) {
|
|
if (!mgr || !skill_slug || skill_slug[0] == '\0' || !content || !filter_json) {
|
|
return -1;
|
|
}
|
|
|
|
if (action_type != TRIGGER_ACTION_LLM && action_type != TRIGGER_ACTION_TEMPLATE) {
|
|
return -1;
|
|
}
|
|
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
|
|
int existing = find_trigger_index_locked(mgr, skill_slug);
|
|
if (existing >= 0) {
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
return trigger_manager_update(mgr, skill_slug, content, filter_json, action_type, enabled);
|
|
}
|
|
|
|
int max_active = mgr->cfg ? mgr->cfg->triggers.max_active : TRIGGER_DEFAULT_MAX_ACTIVE;
|
|
if (max_active < 1) max_active = TRIGGER_DEFAULT_MAX_ACTIVE;
|
|
if (mgr->count >= max_active) {
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
DEBUG_WARN("[didactyl] trigger add rejected: max_active reached (%d)", max_active);
|
|
return -1;
|
|
}
|
|
|
|
if (ensure_capacity(mgr, mgr->count + 1) != 0) {
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
return -1;
|
|
}
|
|
|
|
active_trigger_t* t = &mgr->triggers[mgr->count];
|
|
memset(t, 0, sizeof(*t));
|
|
snprintf(t->skill_slug, sizeof(t->skill_slug), "%s", skill_slug);
|
|
snprintf(t->skill_content, sizeof(t->skill_content), "%s", content);
|
|
snprintf(t->filter_json, sizeof(t->filter_json), "%s", filter_json);
|
|
t->action_type = action_type;
|
|
t->enabled = clamp_enabled(enabled);
|
|
t->last_fired = 0;
|
|
t->last_seen_created_at = 0;
|
|
|
|
mgr->count++;
|
|
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
|
|
DEBUG_INFO("[didactyl] trigger added slug=%s action=%d enabled=%d", skill_slug, (int)action_type, clamp_enabled(enabled));
|
|
return 0;
|
|
}
|
|
|
|
int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_slug) {
|
|
if (!mgr || !skill_slug || skill_slug[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
|
|
int idx = find_trigger_index_locked(mgr, skill_slug);
|
|
if (idx < 0) {
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (idx < mgr->count - 1) {
|
|
memmove(&mgr->triggers[idx],
|
|
&mgr->triggers[idx + 1],
|
|
(size_t)(mgr->count - idx - 1) * sizeof(active_trigger_t));
|
|
}
|
|
|
|
mgr->count--;
|
|
memset(&mgr->triggers[mgr->count], 0, sizeof(active_trigger_t));
|
|
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
|
|
DEBUG_INFO("[didactyl] trigger removed slug=%s", skill_slug);
|
|
return 0;
|
|
}
|
|
|
|
int trigger_manager_update(trigger_manager_t* mgr,
|
|
const char* skill_slug,
|
|
const char* content,
|
|
const char* filter_json,
|
|
trigger_action_type_t action_type,
|
|
int enabled) {
|
|
if (!mgr || !skill_slug || skill_slug[0] == '\0' || !content || !filter_json) {
|
|
return -1;
|
|
}
|
|
|
|
if (action_type != TRIGGER_ACTION_LLM && action_type != TRIGGER_ACTION_TEMPLATE) {
|
|
return -1;
|
|
}
|
|
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
|
|
int idx = find_trigger_index_locked(mgr, skill_slug);
|
|
if (idx < 0) {
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
return trigger_manager_add(mgr, skill_slug, content, filter_json, action_type, enabled);
|
|
}
|
|
|
|
active_trigger_t* t = &mgr->triggers[idx];
|
|
snprintf(t->skill_content, sizeof(t->skill_content), "%s", content);
|
|
snprintf(t->filter_json, sizeof(t->filter_json), "%s", filter_json);
|
|
t->action_type = action_type;
|
|
t->enabled = clamp_enabled(enabled);
|
|
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
|
|
DEBUG_INFO("[didactyl] trigger updated slug=%s action=%d enabled=%d", skill_slug, (int)action_type, clamp_enabled(enabled));
|
|
return 0;
|
|
}
|
|
|
|
int trigger_manager_active_count(trigger_manager_t* mgr) {
|
|
if (!mgr) {
|
|
return 0;
|
|
}
|
|
|
|
int active = 0;
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
for (int i = 0; i < mgr->count; i++) {
|
|
if (mgr->triggers[i].enabled) {
|
|
active++;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
|
|
return active;
|
|
}
|
|
|
|
int trigger_manager_poll(trigger_manager_t* mgr) {
|
|
if (!mgr || !mgr->cfg || !mgr->cfg->triggers.enabled) {
|
|
return 0;
|
|
}
|
|
|
|
time_t now = time(NULL);
|
|
if (mgr->last_poll_at > 0 && (now - mgr->last_poll_at) < 10) {
|
|
return 0;
|
|
}
|
|
mgr->last_poll_at = now;
|
|
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
int count = mgr->count;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
active_trigger_t snapshot = mgr->triggers[i];
|
|
if (!snapshot.enabled || snapshot.filter_json[0] == '\0') {
|
|
continue;
|
|
}
|
|
|
|
cJSON* filter = cJSON_Parse(snapshot.filter_json);
|
|
if (!filter || !cJSON_IsObject(filter)) {
|
|
cJSON_Delete(filter);
|
|
continue;
|
|
}
|
|
|
|
time_t since = snapshot.last_seen_created_at > 0 ? snapshot.last_seen_created_at + 1 : now - 30;
|
|
if (since < 0) since = 0;
|
|
cJSON_AddNumberToObject(filter, "since", (double)since);
|
|
cJSON_AddNumberToObject(filter, "limit", 8);
|
|
|
|
char* events_json = nostr_handler_query_json(filter, 1200);
|
|
cJSON_Delete(filter);
|
|
if (!events_json) {
|
|
continue;
|
|
}
|
|
|
|
cJSON* events = cJSON_Parse(events_json);
|
|
free(events_json);
|
|
if (!events || !cJSON_IsArray(events)) {
|
|
cJSON_Delete(events);
|
|
continue;
|
|
}
|
|
|
|
int n = cJSON_GetArraySize(events);
|
|
for (int e = 0; e < n; e++) {
|
|
cJSON* ev = cJSON_GetArrayItem(events, e);
|
|
if (!ev || !cJSON_IsObject(ev)) continue;
|
|
|
|
int idx = find_trigger_index_locked(mgr, snapshot.skill_slug);
|
|
if (idx < 0) {
|
|
continue;
|
|
}
|
|
|
|
(void)maybe_fire_trigger_locked(mgr, idx, ev, NULL);
|
|
}
|
|
|
|
cJSON_Delete(events);
|
|
}
|
|
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
return 0;
|
|
}
|
|
|
|
char* trigger_manager_status_json(trigger_manager_t* mgr) {
|
|
if (!mgr) {
|
|
cJSON* err = cJSON_CreateObject();
|
|
if (!err) {
|
|
return NULL;
|
|
}
|
|
cJSON_AddBoolToObject(err, "success", 0);
|
|
cJSON_AddStringToObject(err, "error", "trigger manager unavailable");
|
|
char* out = cJSON_PrintUnformatted(err);
|
|
cJSON_Delete(err);
|
|
return out;
|
|
}
|
|
|
|
cJSON* root = cJSON_CreateObject();
|
|
cJSON* arr = cJSON_CreateArray();
|
|
if (!root || !arr) {
|
|
cJSON_Delete(root);
|
|
cJSON_Delete(arr);
|
|
return NULL;
|
|
}
|
|
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
|
|
cJSON_AddBoolToObject(root, "success", 1);
|
|
cJSON_AddNumberToObject(root, "count", mgr->count);
|
|
|
|
int active = 0;
|
|
for (int i = 0; i < mgr->count; i++) {
|
|
active_trigger_t* t = &mgr->triggers[i];
|
|
if (t->enabled) {
|
|
active++;
|
|
}
|
|
|
|
cJSON* item = cJSON_CreateObject();
|
|
if (!item) {
|
|
continue;
|
|
}
|
|
|
|
cJSON_AddStringToObject(item, "skill_slug", t->skill_slug);
|
|
cJSON_AddStringToObject(item, "filter_json", t->filter_json);
|
|
cJSON_AddStringToObject(item, "action", t->action_type == TRIGGER_ACTION_TEMPLATE ? "template" : "llm");
|
|
cJSON_AddBoolToObject(item, "enabled", t->enabled ? 1 : 0);
|
|
cJSON_AddNumberToObject(item, "last_fired", (double)t->last_fired);
|
|
cJSON_AddNumberToObject(item, "last_seen_created_at", (double)t->last_seen_created_at);
|
|
cJSON_AddItemToArray(arr, item);
|
|
}
|
|
|
|
cJSON_AddNumberToObject(root, "active", active);
|
|
cJSON_AddItemToObject(root, "triggers", arr);
|
|
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
|
|
char* out = cJSON_PrintUnformatted(root);
|
|
cJSON_Delete(root);
|
|
return out;
|
|
}
|
|
|
|
void trigger_manager_cleanup(trigger_manager_t* mgr) {
|
|
if (!mgr) {
|
|
return;
|
|
}
|
|
|
|
pthread_mutex_lock(&mgr->mutex);
|
|
free(mgr->triggers);
|
|
mgr->triggers = NULL;
|
|
mgr->count = 0;
|
|
mgr->capacity = 0;
|
|
mgr->cfg = NULL;
|
|
mgr->last_poll_at = 0;
|
|
pthread_mutex_unlock(&mgr->mutex);
|
|
|
|
pthread_mutex_destroy(&mgr->mutex);
|
|
memset(mgr, 0, sizeof(*mgr));
|
|
|
|
DEBUG_INFO("[didactyl] trigger manager cleaned up");
|
|
}
|