v0.0.60 - Remove forced final trigger DM, add skill_edit tool, and complete d_tag terminology migration
This commit is contained in:
@@ -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.59
|
||||
## Current Status — v0.0.60
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.0.59 — Use tool-capable multi-turn execution for triggered skills
|
||||
> Last release update: v0.0.60 — Remove forced final trigger DM, add skill_edit tool, and complete d_tag terminology migration
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
|
||||
6216
context.log.md
6216
context.log.md
File diff suppressed because it is too large
Load Diff
@@ -477,8 +477,8 @@ The following endpoints are planned but not yet implemented:
|
||||
| GET | `/api/events/soul` | Fetch agent soul event |
|
||||
| PUT | `/api/events/soul` | Update soul content |
|
||||
| GET | `/api/events/skills` | List published skills |
|
||||
| GET | `/api/events/skills/:slug` | Fetch skill by slug |
|
||||
| PUT | `/api/events/skills/:slug` | Update skill |
|
||||
| GET | `/api/events/skills/:d_tag` | Fetch skill by d_tag |
|
||||
| PUT | `/api/events/skills/:d_tag` | Update skill |
|
||||
| GET | `/api/events/adoption` | Fetch adoption list |
|
||||
| GET | `/api/events/profile` | Fetch agent profile |
|
||||
| PUT | `/api/events/profile` | Update agent profile |
|
||||
|
||||
@@ -75,9 +75,9 @@ All endpoints return JSON. All mutations use POST/PUT/DELETE. All reads use GET.
|
||||
| GET | `/api/events/soul` | Fetch the agent soul event (kind 31120, d=soul) |
|
||||
| PUT | `/api/events/soul` | Update soul content, republish to relays |
|
||||
| GET | `/api/events/skills` | List all published skills (kind 31123/31124 by own pubkey) |
|
||||
| GET | `/api/events/skills/:slug` | Fetch a single skill by slug |
|
||||
| PUT | `/api/events/skills/:slug` | Update skill content/tags, republish |
|
||||
| DELETE | `/api/events/skills/:slug` | Remove skill from adoption list |
|
||||
| GET | `/api/events/skills/:d_tag` | Fetch a single skill by d_tag |
|
||||
| PUT | `/api/events/skills/:d_tag` | Update skill content/tags, republish |
|
||||
| DELETE | `/api/events/skills/:d_tag` | Remove skill from adoption list |
|
||||
| GET | `/api/events/adoption` | Fetch kind 10123 adoption list |
|
||||
| GET | `/api/events/startup` | List startup events from config |
|
||||
| GET | `/api/events/profile` | Fetch agent kind 0 profile |
|
||||
|
||||
@@ -53,7 +53,7 @@ These are not yet implemented but are on the roadmap:
|
||||
| GET | `/api/events/soul` | Agent soul/system prompt event |
|
||||
| PUT | `/api/events/soul` | Update soul content |
|
||||
| GET | `/api/events/skills` | List skills |
|
||||
| GET/PUT | `/api/events/skills/:slug` | Read/update individual skills |
|
||||
| GET/PUT | `/api/events/skills/:d_tag` | Read/update individual skills |
|
||||
| GET | `/api/events/profile` | Agent Nostr profile |
|
||||
| GET | `/api/tools` | List all tool schemas |
|
||||
| POST | `/api/tools/:name/execute` | Execute a tool directly |
|
||||
|
||||
@@ -21,7 +21,7 @@ The entire triggered-skill infrastructure is **already built and functional**:
|
||||
|
||||
### Gap Found
|
||||
|
||||
The `skill_create` tool **schema** (what the LLM sees) only exposes 5 parameters: `slug`, `content`, `scope`, `description`, `auto_adopt`. The execution function already handles `trigger`, `filter`, `action`, and `enabled` — but these are **not declared in the tool schema**, so the LLM will never pass them.
|
||||
The `skill_create` tool **schema** (what the LLM sees) only exposes 5 parameters: `d_tag`, `content`, `scope`, `description`, `auto_adopt`. The execution function already handles `trigger`, `filter`, `action`, and `enabled` — but these are **not declared in the tool schema**, so the LLM will never pass them.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ The agent selects the profile matching the active model, falling back to `defaul
|
||||
│ - skill_remove (remove) │
|
||||
│ │
|
||||
│ Structure per skill: │
|
||||
│ - slug (string) │
|
||||
│ - d_tag (string) │
|
||||
│ - description (string) │
|
||||
│ - content (string, full) │
|
||||
│ - scope (public/private) │
|
||||
|
||||
@@ -34,7 +34,7 @@ NIP: NIP-23
|
||||
Event Kind: 30023
|
||||
Format: The content field must be markdown text...
|
||||
Required Tags:
|
||||
- d: Addressable identifier slug...
|
||||
- d: Addressable identifier d_tag...
|
||||
- title: Human-readable article title
|
||||
- published_at: Unix timestamp as string...
|
||||
Procedure:
|
||||
|
||||
@@ -12,12 +12,12 @@ Skills are Nostr events. The tools are thin orchestration wrappers over the exis
|
||||
|
||||
| Kind | Purpose | Replaceable? | Key tag |
|
||||
|---|---|---|---|
|
||||
| `31123` | Public skill definition | Yes (d-tag) | `d=<slug>` |
|
||||
| `31124` | Private skill definition | Yes (d-tag) | `d=<slug>` |
|
||||
| `31123` | Public skill definition | Yes (d-tag) | `d=<d_tag>` |
|
||||
| `31124` | Private skill definition | Yes (d-tag) | `d=<d_tag>` |
|
||||
| `10123` | Public skill adoption list | Yes (replaceable) | `a` refs to 31123 events |
|
||||
|
||||
All skill events carry these standard tags:
|
||||
- `["d", "<slug>"]` — unique identifier within the author's pubkey
|
||||
- `["d", "<d_tag>"]` — unique identifier within the author's pubkey
|
||||
- `["app", "didactyl"]` — app namespace
|
||||
- `["scope", "public"]` or `["scope", "private"]`
|
||||
|
||||
@@ -38,37 +38,37 @@ All skill events carry these standard tags:
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": { "type": "string", "description": "Unique skill identifier (lowercase, hyphens allowed)" },
|
||||
"d_tag": { "type": "string", "description": "Unique skill identifier (lowercase, hyphens allowed)" },
|
||||
"content": { "type": "string", "description": "Skill body — markdown instructions or structured JSON" },
|
||||
"scope": { "type": "string", "description": "public (kind 31123) or private (kind 31124). Default: public" },
|
||||
"description": { "type": "string", "description": "Short one-line description for the skill" },
|
||||
"auto_adopt": { "type": "boolean", "description": "Automatically add to adoption list (kind 10123). Default: true" }
|
||||
},
|
||||
"required": ["slug", "content"]
|
||||
"required": ["d_tag", "content"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution logic (`execute_skill_create`):**
|
||||
|
||||
1. Validate `slug` — must be non-empty, lowercase alphanumeric + hyphens, no spaces
|
||||
1. Validate `d_tag` — must be non-empty, lowercase alphanumeric + hyphens, no spaces
|
||||
2. Determine kind: `31123` if scope is `"public"` or absent; `31124` if `"private"`
|
||||
3. Build tags array:
|
||||
- `["d", slug]`
|
||||
- `["d", d_tag]`
|
||||
- `["app", "didactyl"]`
|
||||
- `["scope", scope]`
|
||||
- `["description", description]` if provided
|
||||
4. Call `nostr_handler_publish_kind_event(kind, content, tags, &result)`
|
||||
5. If `auto_adopt` is true (default), update the kind `10123` adoption list:
|
||||
- Query existing `10123` event for own pubkey (same pattern as `execute_nostr_list_manage`)
|
||||
- Add `["a", "31123:<own_pubkey>:<slug>"]` tag if not already present
|
||||
- Add `["a", "31123:<own_pubkey>:<d_tag>"]` tag if not already present
|
||||
- Republish the updated `10123` event
|
||||
6. Return JSON with `success`, `event_id`, `naddr_uri`, `slug`, `adopted`
|
||||
6. Return JSON with `success`, `event_id`, `naddr_uri`, `d_tag`, `adopted`
|
||||
|
||||
**Key design decisions:**
|
||||
- Auto-adopt defaults to `true` — creating a skill you don't adopt is unusual
|
||||
- Private skills (31124) are NOT added to the public adoption list (10123)
|
||||
- Republishing with the same slug replaces the previous version (replaceable event)
|
||||
- Republishing with the same d_tag replaces the previous version (replaceable event)
|
||||
|
||||
---
|
||||
|
||||
@@ -99,7 +99,7 @@ All skill events carry these standard tags:
|
||||
- Private only: `{"kinds": [31124], "authors": [own_pubkey]}`
|
||||
2. Call `nostr_handler_query_json(filter, 8000)`
|
||||
3. Parse results, extract for each event:
|
||||
- `slug` (from d-tag)
|
||||
- `d_tag` (from d-tag)
|
||||
- `kind`
|
||||
- `scope` (from scope tag)
|
||||
- `description` (from description tag, if present)
|
||||
@@ -123,21 +123,21 @@ All skill events carry these standard tags:
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pubkey": { "type": "string", "description": "Hex pubkey of the skill author" },
|
||||
"slug": { "type": "string", "description": "Skill slug (d-tag value)" },
|
||||
"d_tag": { "type": "string", "description": "Skill d_tag (d-tag value)" },
|
||||
"kind": { "type": "integer", "description": "Skill kind (31123 or 31124). Default: 31123" }
|
||||
},
|
||||
"required": ["pubkey", "slug"]
|
||||
"required": ["pubkey", "d_tag"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution logic (`execute_skill_adopt`):**
|
||||
|
||||
1. Validate pubkey (64-char hex) and slug (non-empty)
|
||||
1. Validate pubkey (64-char hex) and d_tag (non-empty)
|
||||
2. Default kind to 31123 if not provided
|
||||
3. Build the `a`-tag value: `"<kind>:<pubkey>:<slug>"`
|
||||
3. Build the `a`-tag value: `"<kind>:<pubkey>:<d_tag>"`
|
||||
4. Query existing kind `10123` event for own pubkey
|
||||
5. Check if `["a", "<kind>:<pubkey>:<slug>"]` already exists — if so, return success with `already_adopted: true`
|
||||
5. Check if `["a", "<kind>:<pubkey>:<d_tag>"]` already exists — if so, return success with `already_adopted: true`
|
||||
6. Add the tag, republish `10123`
|
||||
7. Return JSON with `success`, `adopted_address`, `event_id`
|
||||
|
||||
@@ -157,10 +157,10 @@ All skill events carry these standard tags:
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pubkey": { "type": "string", "description": "Hex pubkey of the skill author. Defaults to own pubkey." },
|
||||
"slug": { "type": "string", "description": "Skill slug to remove" },
|
||||
"d_tag": { "type": "string", "description": "Skill d_tag to remove" },
|
||||
"kind": { "type": "integer", "description": "Skill kind (31123 or 31124). Default: 31123" }
|
||||
},
|
||||
"required": ["slug"]
|
||||
"required": ["d_tag"]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -169,7 +169,7 @@ All skill events carry these standard tags:
|
||||
|
||||
1. Default pubkey to own pubkey if not provided
|
||||
2. Default kind to 31123
|
||||
3. Build the `a`-tag value: `"<kind>:<pubkey>:<slug>"`
|
||||
3. Build the `a`-tag value: `"<kind>:<pubkey>:<d_tag>"`
|
||||
4. Query existing kind `10123` event for own pubkey
|
||||
5. Find and remove matching `["a", ...]` tag
|
||||
6. Republish `10123`
|
||||
@@ -211,7 +211,7 @@ All skill events carry these standard tags:
|
||||
- Return skill summaries
|
||||
3. If `query` is provided (keyword search):
|
||||
- Query `{"kinds": [31123]}` with limit
|
||||
- Filter results client-side by matching `query` against slug, description tag, or content
|
||||
- Filter results client-side by matching `query` against d_tag, description tag, or content
|
||||
- Return matching skill summaries
|
||||
4. Default (no params): return own adopted skills from `10123`
|
||||
|
||||
@@ -236,7 +236,7 @@ This is essentially the same pattern already used in `execute_nostr_list_manage(
|
||||
### Shared helper: skill summary extraction
|
||||
|
||||
```c
|
||||
// Extract slug, kind, scope, description, created_at from a skill event JSON
|
||||
// Extract d_tag, kind, scope, description, created_at from a skill event JSON
|
||||
static cJSON* extract_skill_summary(cJSON* event);
|
||||
```
|
||||
|
||||
@@ -269,7 +269,7 @@ flowchart TD
|
||||
1. **Schema registration** — Add 5 new tool definitions in `tools_build_openai_schema_json()` (t22–t26)
|
||||
2. **Execution functions** — Add 5 new `execute_skill_*()` static functions
|
||||
3. **Dispatch** — Add 5 new `strcmp` branches in `tools_execute()`
|
||||
4. **Shared helpers** — Add `fetch_adoption_list_tags()`, `publish_adoption_list()`, `extract_skill_summary()`, and `validate_skill_slug()`
|
||||
4. **Shared helpers** — Add `fetch_adoption_list_tags()`, `publish_adoption_list()`, `extract_skill_summary()`, and `validate_skill_d_tag()`
|
||||
|
||||
### `README.md`
|
||||
|
||||
@@ -284,19 +284,19 @@ flowchart TD
|
||||
|
||||
## Slug Validation Rules
|
||||
|
||||
A valid skill slug must:
|
||||
A valid skill d_tag must:
|
||||
- Be 1–64 characters
|
||||
- Contain only lowercase letters, digits, and hyphens
|
||||
- Not start or end with a hyphen
|
||||
- Not contain consecutive hyphens
|
||||
|
||||
```c
|
||||
static int validate_skill_slug(const char* slug) {
|
||||
if (!slug || slug[0] == '\0' || strlen(slug) > 64) return 0;
|
||||
if (slug[0] == '-') return 0;
|
||||
static int validate_skill_d_tag(const char* d_tag) {
|
||||
if (!d_tag || d_tag[0] == '\0' || strlen(d_tag) > 64) return 0;
|
||||
if (d_tag[0] == '-') return 0;
|
||||
int prev_dash = 0;
|
||||
for (size_t i = 0; slug[i]; i++) {
|
||||
char c = slug[i];
|
||||
for (size_t i = 0; d_tag[i]; i++) {
|
||||
char c = d_tag[i];
|
||||
if (c == '-') {
|
||||
if (prev_dash) return 0;
|
||||
prev_dash = 1;
|
||||
@@ -306,7 +306,7 @@ static int validate_skill_slug(const char* slug) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (slug[strlen(slug) - 1] == '-') return 0;
|
||||
if (d_tag[strlen(d_tag) - 1] == '-') return 0;
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
@@ -315,7 +315,7 @@ static int validate_skill_slug(const char* slug) {
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **Shared helpers** — `validate_skill_slug`, `fetch_adoption_list_tags`, `publish_adoption_list`, `extract_skill_summary`
|
||||
1. **Shared helpers** — `validate_skill_d_tag`, `fetch_adoption_list_tags`, `publish_adoption_list`, `extract_skill_summary`
|
||||
2. **`skill_create`** — most important tool, enables the agent to author skills
|
||||
3. **`skill_list`** — lets the agent see what it has published
|
||||
4. **`skill_adopt`** — adopt skills from other authors
|
||||
|
||||
@@ -19,7 +19,7 @@ A skill is a Nostr event (kind `31123` for public, `31124` for private) that def
|
||||
- **Parameters** like temperature and max_tokens
|
||||
- A **description** for human display
|
||||
|
||||
Skills are replaceable events keyed by their `d` tag (the slug). Publishing a new event with the same `d` tag replaces the previous version.
|
||||
Skills are replaceable events keyed by their `d` tag (the d_tag). Publishing a new event with the same `d` tag replaces the previous version.
|
||||
|
||||
### Skill Event Structure
|
||||
|
||||
@@ -206,7 +206,7 @@ To find skills by a specific author:
|
||||
1. Receive the event from the relay
|
||||
2. Parse `event.content` as JSON to get the skill definition object
|
||||
3. Extract `description`, `template`, `temperature`, `max_tokens`
|
||||
4. Extract the `d` tag value from `event.tags` as the skill slug
|
||||
4. Extract the `d` tag value from `event.tags` as the skill d_tag
|
||||
5. Extract the `description` tag from `event.tags` as a display label
|
||||
6. The skill is ready to display and execute
|
||||
|
||||
@@ -261,7 +261,7 @@ To find skills by a specific author:
|
||||
|
||||
- **Skills list**: Radio buttons or selectable cards, one skill selected at a time
|
||||
- Each skill card shows:
|
||||
- Skill slug (the `d` tag)
|
||||
- Skill d_tag (the `d` tag)
|
||||
- Description (from the `description` tag or content field)
|
||||
- Author pubkey (truncated, e.g., `52a3e8...0acb8`)
|
||||
- **Run Selected Skill** button: executes the selected skill against the current text
|
||||
@@ -398,7 +398,7 @@ sequenceDiagram
|
||||
**Building the event:**
|
||||
|
||||
```javascript
|
||||
function buildSkillEvent(slug, description, template, temperature, maxTokens) {
|
||||
function buildSkillEvent(d_tag, description, template, temperature, maxTokens) {
|
||||
const content = JSON.stringify({
|
||||
description: description,
|
||||
context_mode: 'full',
|
||||
@@ -413,7 +413,7 @@ function buildSkillEvent(slug, description, template, temperature, maxTokens) {
|
||||
kind: 31123,
|
||||
content: content,
|
||||
tags: [
|
||||
['d', slug],
|
||||
['d', d_tag],
|
||||
['app', 'didactyl'],
|
||||
['scope', 'public'],
|
||||
['description', description],
|
||||
|
||||
@@ -62,13 +62,13 @@ Today, skills are passive — they're injected into the LLM context via [`append
|
||||
#### A. `/run` slash command (admin direct invocation)
|
||||
|
||||
```
|
||||
/run deploy-website staging # run own adopted skill by slug
|
||||
/run deploy-website staging # run own adopted skill by d_tag
|
||||
/run 31123:<pubkey>:deploy-website staging # run anyone's skill by address
|
||||
/run deploy-website {"target": "production"} # JSON args
|
||||
```
|
||||
|
||||
Parsing:
|
||||
1. First token after `/run` is the skill identifier (slug or `kind:pubkey:slug` address)
|
||||
1. First token after `/run` is the skill identifier (d_tag or `kind:pubkey:d_tag` address)
|
||||
2. Everything after is args (try JSON first, fall back to `{"input": "plain text"}`)
|
||||
|
||||
#### B. `skill_run` tool (LLM-mediated invocation)
|
||||
@@ -78,13 +78,13 @@ The LLM can invoke skills on behalf of the admin during conversation:
|
||||
```json
|
||||
{
|
||||
"name": "skill_run",
|
||||
"description": "Fetch and execute a skill one-shot without adopting it. Works with own adopted skills by slug or any public skill by address.",
|
||||
"description": "Fetch and execute a skill one-shot without adopting it. Works with own adopted skills by d_tag or any public skill by address.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": { "type": "string", "description": "Skill slug for own adopted skills" },
|
||||
"address": { "type": "string", "description": "Full skill address: kind:pubkey:slug" },
|
||||
"pubkey": { "type": "string", "description": "Author pubkey, used with slug to form address" },
|
||||
"d_tag": { "type": "string", "description": "Skill d_tag for own adopted skills" },
|
||||
"address": { "type": "string", "description": "Full skill address: kind:pubkey:d_tag" },
|
||||
"pubkey": { "type": "string", "description": "Author pubkey, used with d_tag to form address" },
|
||||
"args": { "type": "string", "description": "Arguments or context to pass to the skill" },
|
||||
"sandbox": { "type": "boolean", "description": "Override sandbox setting. Default: true for external, false for own skills" }
|
||||
}
|
||||
@@ -104,8 +104,8 @@ Both `/run` and `skill_run` use the same underlying executor:
|
||||
```mermaid
|
||||
graph TD
|
||||
A[/run or skill_run called] --> B{Skill identifier type?}
|
||||
B -->|slug only| C[Look up in adopted skills cache]
|
||||
B -->|address or pubkey+slug| D[Fetch skill event from Nostr]
|
||||
B -->|d_tag only| C[Look up in adopted skills cache]
|
||||
B -->|address or pubkey+d_tag| D[Fetch skill event from Nostr]
|
||||
C --> E{Found?}
|
||||
D --> E
|
||||
E -->|No| F[Return error: skill not found]
|
||||
@@ -133,7 +133,7 @@ Skill execution context:
|
||||
- The user's arguments provide the context for this execution.
|
||||
- Keep output concise and actionable.
|
||||
|
||||
Skill slug: deploy-website
|
||||
Skill d_tag: deploy-website
|
||||
Skill address: 31123:<pubkey>:deploy-website
|
||||
Skill source: [own | external:<author_display_name>]
|
||||
|
||||
@@ -175,7 +175,7 @@ graph TD
|
||||
A[Friend creates skill] -->|kind 31123| B[Published on Nostr relays]
|
||||
B --> C{How do you find it?}
|
||||
C -->|skill_search popular:true| D[Discovery via WoT adoption lists]
|
||||
C -->|Friend tells you the slug| E[Direct reference]
|
||||
C -->|Friend tells you the d_tag| E[Direct reference]
|
||||
C -->|skill_search pubkey:friend| F[Browse friends skills]
|
||||
D --> G[skill_run - try it once, sandboxed]
|
||||
E --> G
|
||||
@@ -185,7 +185,7 @@ graph TD
|
||||
H -->|No| J[Done - nothing persisted]
|
||||
I --> K[Shows in adopted skills context]
|
||||
K --> L[Agent uses it automatically]
|
||||
L -->|Or invoke directly| M[/run skill-slug args]
|
||||
L -->|Or invoke directly| M[/run skill-d_tag args]
|
||||
```
|
||||
|
||||
## 3. Skill-Tool Maturity Levels
|
||||
@@ -255,7 +255,7 @@ steps:
|
||||
args:
|
||||
kind: 30023
|
||||
content: "{{file_content}}"
|
||||
tags: [["d", "{{slug}}"]]
|
||||
tags: [["d", "{{d_tag}}"]]
|
||||
|
||||
# Conditional - simple
|
||||
- if: "{{listing.success}}"
|
||||
@@ -266,7 +266,7 @@ steps:
|
||||
- return: "Failed: {{listing.error}}"
|
||||
|
||||
# Return final result
|
||||
- return: "Published to {{slug}}"
|
||||
- return: "Published to {{d_tag}}"
|
||||
```
|
||||
|
||||
### Variable Substitution
|
||||
@@ -333,7 +333,7 @@ typedef enum {
|
||||
|
||||
| Scenario | Default sandbox | Override |
|
||||
|----------|----------------|---------|
|
||||
| Own adopted skill via `/run slug` | No sandbox | N/A |
|
||||
| Own adopted skill via `/run d_tag` | No sandbox | N/A |
|
||||
| External skill via `/run address` | Sandbox ON | `/run --unsafe address` |
|
||||
| `skill_run` tool, own skill | No sandbox | `sandbox: true` |
|
||||
| `skill_run` tool, external skill | Sandbox ON | `sandbox: false` |
|
||||
|
||||
@@ -21,7 +21,7 @@ Create `src/trigger_manager.c` and `src/trigger_manager.h` — the core componen
|
||||
#define TRIGGER_COOLDOWN_SECONDS 60
|
||||
|
||||
typedef struct {
|
||||
char skill_slug[65];
|
||||
char skill_d_tag[65];
|
||||
char skill_content[4096]; // the action template or LLM prompt
|
||||
int action_type; // 0 = llm, 1 = template
|
||||
char filter_json[2048]; // the Nostr subscription filter
|
||||
@@ -43,11 +43,11 @@ typedef struct {
|
||||
```c
|
||||
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_add(trigger_manager_t* mgr, const char* skill_slug,
|
||||
int trigger_manager_add(trigger_manager_t* mgr, const char* skill_d_tag,
|
||||
const char* content, const char* filter_json,
|
||||
int action_type, int enabled);
|
||||
int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_slug);
|
||||
int trigger_manager_update(trigger_manager_t* mgr, const char* skill_slug,
|
||||
int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_d_tag);
|
||||
int trigger_manager_update(trigger_manager_t* mgr, const char* skill_d_tag,
|
||||
const char* content, const char* filter_json,
|
||||
int action_type, int enabled);
|
||||
int trigger_manager_active_count(trigger_manager_t* mgr);
|
||||
@@ -115,7 +115,7 @@ When `skill_remove` is called for a triggered skill:
|
||||
The agent currently only handles DM-initiated conversations via `agent_on_message()`. Add a new entry point:
|
||||
|
||||
```c
|
||||
void agent_on_trigger(const char* skill_slug,
|
||||
void agent_on_trigger(const char* skill_d_tag,
|
||||
const char* skill_content,
|
||||
cJSON* triggering_event,
|
||||
const char* relay_url);
|
||||
|
||||
69
src/agent.c
69
src/agent.c
@@ -48,7 +48,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
int kind;
|
||||
char author_pubkey_hex[65];
|
||||
char slug[65];
|
||||
char d_tag[65];
|
||||
char* content;
|
||||
} agent_adopted_skill_t;
|
||||
|
||||
@@ -473,14 +473,14 @@ static char* build_slash_help_all_json(void) {
|
||||
int sn = cJSON_GetArraySize(skills);
|
||||
for (int i = 0; i < sn; i++) {
|
||||
cJSON* row = cJSON_GetArrayItem(skills, i);
|
||||
cJSON* slug = row ? cJSON_GetObjectItemCaseSensitive(row, "slug") : NULL;
|
||||
cJSON* d_tag = row ? cJSON_GetObjectItemCaseSensitive(row, "d_tag") : NULL;
|
||||
cJSON* desc = row ? cJSON_GetObjectItemCaseSensitive(row, "description") : NULL;
|
||||
const char* slug_s = (slug && cJSON_IsString(slug) && slug->valuestring) ? slug->valuestring : NULL;
|
||||
const char* d_tag_s = (d_tag && cJSON_IsString(d_tag) && d_tag->valuestring) ? d_tag->valuestring : NULL;
|
||||
const char* desc_s = (desc && cJSON_IsString(desc) && desc->valuestring) ? desc->valuestring : "";
|
||||
if (!slug_s || slug_s[0] == '\0') {
|
||||
if (!d_tag_s || d_tag_s[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
if (append_textf_local(&out, &cap, &used, "- %s%s%s\n", slug_s, desc_s[0] ? " — " : "", desc_s) != 0) {
|
||||
if (append_textf_local(&out, &cap, &used, "- %s%s%s\n", d_tag_s, desc_s[0] ? " — " : "", desc_s) != 0) {
|
||||
cJSON_Delete(skill_root);
|
||||
free(skill_list_json);
|
||||
free(out);
|
||||
@@ -1449,8 +1449,8 @@ static cJSON* find_tag_value_string_local(cJSON* tags, const char* key) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int parse_skill_address_tag_local(const char* addr, int* out_kind, char out_pubkey_hex[65], char out_slug[65]) {
|
||||
if (!addr || !out_kind || !out_pubkey_hex || !out_slug) {
|
||||
static int parse_skill_address_tag_local(const char* addr, int* out_kind, char out_pubkey_hex[65], char out_d_tag[65]) {
|
||||
if (!addr || !out_kind || !out_pubkey_hex || !out_d_tag) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1478,11 +1478,11 @@ static int parse_skill_address_tag_local(const char* addr, int* out_kind, char o
|
||||
memcpy(out_pubkey_hex, p1 + 1, 64U);
|
||||
out_pubkey_hex[64] = '\0';
|
||||
|
||||
size_t slug_len = strlen(p2 + 1);
|
||||
if (slug_len == 0 || slug_len >= 65U) {
|
||||
size_t d_tag_len = strlen(p2 + 1);
|
||||
if (d_tag_len == 0 || d_tag_len >= 65U) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(out_slug, p2 + 1, slug_len + 1U);
|
||||
memcpy(out_d_tag, p2 + 1, d_tag_len + 1U);
|
||||
|
||||
*out_kind = kind;
|
||||
return 0;
|
||||
@@ -1494,7 +1494,7 @@ static void clear_adopted_skills_cache_locked(void) {
|
||||
g_adopted_skills[i].content = NULL;
|
||||
g_adopted_skills[i].kind = 0;
|
||||
g_adopted_skills[i].author_pubkey_hex[0] = '\0';
|
||||
g_adopted_skills[i].slug[0] = '\0';
|
||||
g_adopted_skills[i].d_tag[0] = '\0';
|
||||
}
|
||||
g_adopted_skills_count = 0;
|
||||
}
|
||||
@@ -1543,8 +1543,8 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
|
||||
int skill_kind = 0;
|
||||
char skill_author[65] = {0};
|
||||
char skill_slug[65] = {0};
|
||||
if (parse_skill_address_tag_local(val->valuestring, &skill_kind, skill_author, skill_slug) != 0) {
|
||||
char skill_d_tag[65] = {0};
|
||||
if (parse_skill_address_tag_local(val->valuestring, &skill_kind, skill_author, skill_d_tag) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1566,7 +1566,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
|
||||
strcmp(ev_pubkey->valuestring, skill_author) != 0 ||
|
||||
!ev_d || !cJSON_IsString(ev_d) || !ev_d->valuestring ||
|
||||
strcmp(ev_d->valuestring, skill_slug) != 0) {
|
||||
strcmp(ev_d->valuestring, skill_d_tag) != 0) {
|
||||
continue;
|
||||
}
|
||||
cJSON* dup = cJSON_Duplicate(ev, 1);
|
||||
@@ -1595,7 +1595,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds);
|
||||
cJSON_AddItemToArray(sk_authors, cJSON_CreateString(skill_author));
|
||||
cJSON_AddItemToObject(skill_filter, "authors", sk_authors);
|
||||
cJSON_AddItemToArray(d_values, cJSON_CreateString(skill_slug));
|
||||
cJSON_AddItemToArray(d_values, cJSON_CreateString(skill_d_tag));
|
||||
cJSON_AddItemToObject(skill_filter, "#d", d_values);
|
||||
cJSON_AddNumberToObject(skill_filter, "limit", 1);
|
||||
|
||||
@@ -1632,7 +1632,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
|
||||
tmp[tmp_count].kind = skill_kind;
|
||||
snprintf(tmp[tmp_count].author_pubkey_hex, sizeof(tmp[tmp_count].author_pubkey_hex), "%s", skill_author);
|
||||
snprintf(tmp[tmp_count].slug, sizeof(tmp[tmp_count].slug), "%s", skill_slug);
|
||||
snprintf(tmp[tmp_count].d_tag, sizeof(tmp[tmp_count].d_tag), "%s", skill_d_tag);
|
||||
tmp[tmp_count].content = strdup(content->valuestring);
|
||||
if (tmp[tmp_count].content) {
|
||||
tmp_count++;
|
||||
@@ -1652,17 +1652,17 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char slug[65] = {0};
|
||||
char d_tag_val[65] = {0};
|
||||
if (se->tags_json) {
|
||||
cJSON* tags = cJSON_Parse(se->tags_json);
|
||||
cJSON* d_tag = tags ? find_tag_value_string_local(tags, "d") : NULL;
|
||||
if (d_tag && cJSON_IsString(d_tag) && d_tag->valuestring && d_tag->valuestring[0] != '\0') {
|
||||
snprintf(slug, sizeof(slug), "%s", d_tag->valuestring);
|
||||
snprintf(d_tag_val, sizeof(d_tag_val), "%s", d_tag->valuestring);
|
||||
}
|
||||
cJSON_Delete(tags);
|
||||
}
|
||||
if (slug[0] == '\0') {
|
||||
snprintf(slug, sizeof(slug), "startup-skill-%d", i + 1);
|
||||
if (d_tag_val[0] == '\0') {
|
||||
snprintf(d_tag_val, sizeof(d_tag_val), "startup-skill-%d", i + 1);
|
||||
}
|
||||
|
||||
tmp[tmp_count].kind = se->kind;
|
||||
@@ -1672,7 +1672,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
g_cfg->keys.public_key_hex[0] != '\0'
|
||||
? g_cfg->keys.public_key_hex
|
||||
: "unknown");
|
||||
snprintf(tmp[tmp_count].slug, sizeof(tmp[tmp_count].slug), "%s", slug);
|
||||
snprintf(tmp[tmp_count].d_tag, sizeof(tmp[tmp_count].d_tag), "%s", d_tag_val);
|
||||
tmp[tmp_count].content = strdup(se->content);
|
||||
if (tmp[tmp_count].content) {
|
||||
tmp_count++;
|
||||
@@ -1803,11 +1803,11 @@ static int append_adopted_skills_context(cJSON* messages) {
|
||||
|
||||
int head_n = snprintf(payload + used,
|
||||
capacity - used,
|
||||
"\n\n---\n\n### Skill\nSkill slug: %s\nSkill address: %d:%s:%s\n\nInstructions:\n",
|
||||
g_adopted_skills[i].slug,
|
||||
"\n\n---\n\n### Skill\nSkill d_tag: %s\nSkill address: %d:%s:%s\n\nInstructions:\n",
|
||||
g_adopted_skills[i].d_tag,
|
||||
g_adopted_skills[i].kind,
|
||||
g_adopted_skills[i].author_pubkey_hex,
|
||||
g_adopted_skills[i].slug);
|
||||
g_adopted_skills[i].d_tag);
|
||||
if (head_n < 0 || (size_t)head_n >= (capacity - used)) {
|
||||
break;
|
||||
}
|
||||
@@ -1910,11 +1910,11 @@ void agent_set_trigger_manager(struct trigger_manager* trigger_manager) {
|
||||
g_tools_ctx.trigger_manager = trigger_manager;
|
||||
}
|
||||
|
||||
void agent_on_trigger(const char* skill_slug,
|
||||
void agent_on_trigger(const char* skill_d_tag,
|
||||
const char* skill_content,
|
||||
cJSON* triggering_event,
|
||||
const char* relay_url) {
|
||||
if (!g_cfg || !g_system_context || !skill_slug || !skill_content || !triggering_event) {
|
||||
if (!g_cfg || !g_system_context || !skill_d_tag || !skill_content || !triggering_event) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1931,9 +1931,9 @@ void agent_on_trigger(const char* skill_slug,
|
||||
"- Execute the skill instructions below against the triggering event.\n"
|
||||
"- Keep output concise and actionable for the administrator.\n"
|
||||
"- If no action is needed, explicitly say why.\n\n"
|
||||
"Skill slug: ";
|
||||
"Skill d_tag: ";
|
||||
|
||||
size_t system_len = strlen(g_system_context) + 2 + strlen(trigger_prefix) + strlen(skill_slug) +
|
||||
size_t system_len = strlen(g_system_context) + 2 + strlen(trigger_prefix) + strlen(skill_d_tag) +
|
||||
strlen("\nRelay: ") + strlen(relay) + strlen("\n\nSkill instructions:\n") +
|
||||
strlen(skill_content) + 1U;
|
||||
char* system_prompt = (char*)malloc(system_len);
|
||||
@@ -1947,7 +1947,7 @@ void agent_on_trigger(const char* skill_slug,
|
||||
"%s\n\n%s%s\nRelay: %s\n\nSkill instructions:\n%s",
|
||||
g_system_context,
|
||||
trigger_prefix,
|
||||
skill_slug,
|
||||
skill_d_tag,
|
||||
relay,
|
||||
skill_content);
|
||||
|
||||
@@ -1987,7 +1987,6 @@ void agent_on_trigger(const char* skill_slug,
|
||||
free(user_prompt);
|
||||
|
||||
int max_turns = g_cfg->tools.max_turns > 0 ? g_cfg->tools.max_turns : 6;
|
||||
char* final_answer_owned = NULL;
|
||||
|
||||
for (int turn = 0; turn < max_turns; turn++) {
|
||||
char* messages_json = cJSON_PrintUnformatted(messages);
|
||||
@@ -2008,8 +2007,6 @@ void agent_on_trigger(const char* skill_slug,
|
||||
}
|
||||
|
||||
if (resp.tool_call_count <= 0) {
|
||||
const char* answer = resp.content ? resp.content : "No response content.";
|
||||
final_answer_owned = strdup(answer);
|
||||
llm_response_free(&resp);
|
||||
break;
|
||||
}
|
||||
@@ -2043,14 +2040,6 @@ void agent_on_trigger(const char* skill_slug,
|
||||
llm_response_free(&resp);
|
||||
}
|
||||
|
||||
if (!final_answer_owned) {
|
||||
final_answer_owned = strdup("I hit my tool-use limit for this trigger.");
|
||||
}
|
||||
|
||||
const char* final_answer = final_answer_owned ? final_answer_owned : "I hit my tool-use limit for this trigger.";
|
||||
(void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, final_answer);
|
||||
|
||||
free(final_answer_owned);
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ struct trigger_manager;
|
||||
|
||||
int agent_init(didactyl_config_t* config, const char* system_context);
|
||||
void agent_set_trigger_manager(struct trigger_manager* trigger_manager);
|
||||
void agent_on_trigger(const char* skill_slug,
|
||||
void agent_on_trigger(const char* skill_d_tag,
|
||||
const char* skill_content,
|
||||
cJSON* triggering_event,
|
||||
const char* relay_url);
|
||||
|
||||
14
src/config.c
14
src/config.c
@@ -548,27 +548,23 @@ static int normalize_skill_d_tag(int event_kind, cJSON* item, cJSON* tags) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* slug = NULL;
|
||||
cJSON* slug_val = find_tag_value_string(tags, "slug");
|
||||
if (slug_val && cJSON_IsString(slug_val) && slug_val->valuestring && slug_val->valuestring[0] != '\0') {
|
||||
slug = slug_val->valuestring;
|
||||
}
|
||||
const char* d_tag = NULL;
|
||||
|
||||
if (!slug) {
|
||||
if (!d_tag) {
|
||||
cJSON* content_fields = cJSON_GetObjectItemCaseSensitive(item, "content_fields");
|
||||
if (content_fields && cJSON_IsObject(content_fields)) {
|
||||
cJSON* name = cJSON_GetObjectItemCaseSensitive(content_fields, "name");
|
||||
if (name && cJSON_IsString(name) && name->valuestring && name->valuestring[0] != '\0') {
|
||||
slug = name->valuestring;
|
||||
d_tag = name->valuestring;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!slug) {
|
||||
if (!d_tag) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return set_tag_value_string(tags, "d", slug);
|
||||
return set_tag_value_string(tags, "d", d_tag);
|
||||
}
|
||||
|
||||
static int parse_startup_events(cJSON* root, didactyl_config_t* config) {
|
||||
|
||||
@@ -524,14 +524,14 @@ static char* build_slash_help_all_json_local(void) {
|
||||
int sn = cJSON_GetArraySize(skills);
|
||||
for (int i = 0; i < sn; i++) {
|
||||
cJSON* row = cJSON_GetArrayItem(skills, i);
|
||||
cJSON* slug = row ? cJSON_GetObjectItemCaseSensitive(row, "slug") : NULL;
|
||||
cJSON* d_tag = row ? cJSON_GetObjectItemCaseSensitive(row, "d_tag") : NULL;
|
||||
cJSON* desc = row ? cJSON_GetObjectItemCaseSensitive(row, "description") : NULL;
|
||||
const char* slug_s = (slug && cJSON_IsString(slug) && slug->valuestring) ? slug->valuestring : NULL;
|
||||
const char* d_tag_s = (d_tag && cJSON_IsString(d_tag) && d_tag->valuestring) ? d_tag->valuestring : NULL;
|
||||
const char* desc_s = (desc && cJSON_IsString(desc) && desc->valuestring) ? desc->valuestring : "";
|
||||
if (!slug_s || slug_s[0] == '\0') {
|
||||
if (!d_tag_s || d_tag_s[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
if (append_textf_http(&out, &cap, &used, "- %s%s%s\n", slug_s, desc_s[0] ? " — " : "", desc_s) != 0) {
|
||||
if (append_textf_http(&out, &cap, &used, "- %s%s%s\n", d_tag_s, desc_s[0] ? " — " : "", desc_s) != 0) {
|
||||
cJSON_Delete(skill_root);
|
||||
free(skill_list_json);
|
||||
free(out);
|
||||
|
||||
@@ -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 59
|
||||
#define DIDACTYL_VERSION "v0.0.59"
|
||||
#define DIDACTYL_VERSION_PATCH 60
|
||||
#define DIDACTYL_VERSION "v0.0.60"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
@@ -482,13 +482,13 @@ static void register_trigger_from_self_skill_event(cJSON* event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* slug = find_tag_value_local(tags, "d");
|
||||
const char* d_tag = 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' ||
|
||||
if (!d_tag || d_tag[0] == '\0' ||
|
||||
!trigger || strcmp(trigger, "nostr-subscription") != 0 ||
|
||||
!filter || filter[0] == '\0') {
|
||||
return;
|
||||
@@ -500,18 +500,18 @@ static void register_trigger_from_self_skill_event(cJSON* event) {
|
||||
int is_enabled = parse_enabled_flag_local(enabled);
|
||||
|
||||
int rc = trigger_manager_add((trigger_manager_t*)g_trigger_manager,
|
||||
slug,
|
||||
d_tag,
|
||||
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,
|
||||
DEBUG_INFO("[didactyl] live self-skill trigger registered d_tag=%s action=%s enabled=%d",
|
||||
d_tag,
|
||||
action_type == TRIGGER_ACTION_TEMPLATE ? "template" : "llm",
|
||||
is_enabled);
|
||||
} else {
|
||||
DEBUG_WARN("[didactyl] live self-skill trigger register failed slug=%s", slug);
|
||||
DEBUG_WARN("[didactyl] live self-skill trigger register failed d_tag=%s", d_tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
439
src/tools.c
439
src/tools.c
@@ -21,6 +21,8 @@
|
||||
#include "llm.h"
|
||||
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
|
||||
static int validate_skill_d_tag(const char* d_tag);
|
||||
|
||||
static char* json_error(const char* msg) {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (!root) return NULL;
|
||||
@@ -251,6 +253,27 @@ static int add_string_tag(cJSON* tags, const char* key, const char* value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int remove_tag_key_all(cJSON* tags, const char* key) {
|
||||
if (!tags || !cJSON_IsArray(tags) || !key || key[0] == '\0') return 0;
|
||||
int removed = 0;
|
||||
for (int i = cJSON_GetArraySize(tags) - 1; i >= 0; i--) {
|
||||
cJSON* tag = cJSON_GetArrayItem(tags, i);
|
||||
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 1) continue;
|
||||
cJSON* k = cJSON_GetArrayItem(tag, 0);
|
||||
if (k && cJSON_IsString(k) && k->valuestring && strcmp(k->valuestring, key) == 0) {
|
||||
cJSON_DeleteItemFromArray(tags, i);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
static int upsert_string_tag(cJSON* tags, const char* key, const char* value) {
|
||||
if (!tags || !cJSON_IsArray(tags) || !key || !value || value[0] == '\0') return -1;
|
||||
remove_tag_key_all(tags, key);
|
||||
return add_string_tag(tags, key, value);
|
||||
}
|
||||
|
||||
static char* trim_copy(const char* start, size_t len) {
|
||||
while (len > 0 && isspace((unsigned char)start[0])) {
|
||||
start++;
|
||||
@@ -431,7 +454,7 @@ static char* first_markdown_image_url(const char* content) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* slugify_string(const char* in, const char* fallback) {
|
||||
static char* d_tagify_string(const char* in, const char* fallback) {
|
||||
if (!in || in[0] == '\0') {
|
||||
return fallback ? strdup(fallback) : NULL;
|
||||
}
|
||||
@@ -493,17 +516,17 @@ static void ensure_nip23_metadata_tags(int kind, const char* content, cJSON** ta
|
||||
}
|
||||
|
||||
if (!has_tag_key(tags, "d")) {
|
||||
char* slug = NULL;
|
||||
char* d_tag = NULL;
|
||||
if (title) {
|
||||
slug = slugify_string(title, "long-form-note");
|
||||
d_tag = d_tagify_string(title, "long-form-note");
|
||||
} else {
|
||||
char* derived_title = first_markdown_h1(content);
|
||||
slug = slugify_string(derived_title, "long-form-note");
|
||||
d_tag = d_tagify_string(derived_title, "long-form-note");
|
||||
free(derived_title);
|
||||
}
|
||||
if (slug) {
|
||||
(void)add_string_tag(tags, "d", slug);
|
||||
free(slug);
|
||||
if (d_tag) {
|
||||
(void)add_string_tag(tags, "d", d_tag);
|
||||
free(d_tag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,15 +870,15 @@ static cJSON* find_tag_value_string(cJSON* tags, const char* key) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int validate_skill_slug(const char* slug) {
|
||||
if (!slug) return 0;
|
||||
size_t len = strlen(slug);
|
||||
static int validate_skill_d_tag(const char* d_tag) {
|
||||
if (!d_tag) return 0;
|
||||
size_t len = strlen(d_tag);
|
||||
if (len == 0 || len > 64U) return 0;
|
||||
if (slug[0] == '-' || slug[len - 1] == '-') return 0;
|
||||
if (d_tag[0] == '-' || d_tag[len - 1] == '-') return 0;
|
||||
|
||||
int prev_dash = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)slug[i];
|
||||
unsigned char c = (unsigned char)d_tag[i];
|
||||
if (c == '-') {
|
||||
if (prev_dash) return 0;
|
||||
prev_dash = 1;
|
||||
@@ -986,7 +1009,7 @@ static cJSON* extract_skill_summary(cJSON* event) {
|
||||
cJSON* description = find_tag_value_string(tags, "description");
|
||||
|
||||
if (d && cJSON_IsString(d) && d->valuestring) {
|
||||
cJSON_AddStringToObject(summary, "slug", d->valuestring);
|
||||
cJSON_AddStringToObject(summary, "d_tag", d->valuestring);
|
||||
}
|
||||
if (scope && cJSON_IsString(scope) && scope->valuestring) {
|
||||
cJSON_AddStringToObject(summary, "scope", scope->valuestring);
|
||||
@@ -1043,7 +1066,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON_AddItemToObject(t1_props, "content", p_content);
|
||||
cJSON* p_tags = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_tags, "type", "array");
|
||||
cJSON_AddStringToObject(p_tags, "description", "Optional Nostr tags array, e.g. [[\"d\",\"slug\"],[\"t\",\"nostr\"]]");
|
||||
cJSON_AddStringToObject(p_tags, "description", "Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]");
|
||||
cJSON* p_tags_items = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_tags_items, "type", "array");
|
||||
cJSON* p_tag_item = cJSON_CreateObject();
|
||||
@@ -1617,9 +1640,9 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON_AddItemToObject(t22_params, "properties", t22_props);
|
||||
cJSON_AddItemToObject(t22_params, "required", t22_required);
|
||||
|
||||
cJSON* p_skill_create_slug = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_create_slug, "type", "string");
|
||||
cJSON_AddItemToObject(t22_props, "slug", p_skill_create_slug);
|
||||
cJSON* p_skill_create_d = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_create_d, "type", "string");
|
||||
cJSON_AddItemToObject(t22_props, "d", p_skill_create_d);
|
||||
cJSON* p_skill_create_content = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_create_content, "type", "string");
|
||||
cJSON_AddItemToObject(t22_props, "content", p_skill_create_content);
|
||||
@@ -1649,7 +1672,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON_AddStringToObject(p_skill_create_enabled, "type", "boolean");
|
||||
cJSON_AddItemToObject(t22_props, "enabled", p_skill_create_enabled);
|
||||
|
||||
cJSON_AddItemToArray(t22_required, cJSON_CreateString("slug"));
|
||||
cJSON_AddItemToArray(t22_required, cJSON_CreateString("d"));
|
||||
cJSON_AddItemToArray(t22_required, cJSON_CreateString("content"));
|
||||
|
||||
cJSON_AddItemToObject(t22_fn, "parameters", t22_params);
|
||||
@@ -1691,15 +1714,15 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* p_skill_adopt_pubkey = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_adopt_pubkey, "type", "string");
|
||||
cJSON_AddItemToObject(t24_props, "pubkey", p_skill_adopt_pubkey);
|
||||
cJSON* p_skill_adopt_slug = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_adopt_slug, "type", "string");
|
||||
cJSON_AddItemToObject(t24_props, "slug", p_skill_adopt_slug);
|
||||
cJSON* p_skill_adopt_d = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_adopt_d, "type", "string");
|
||||
cJSON_AddItemToObject(t24_props, "d", p_skill_adopt_d);
|
||||
cJSON* p_skill_adopt_kind = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_adopt_kind, "type", "integer");
|
||||
cJSON_AddItemToObject(t24_props, "kind", p_skill_adopt_kind);
|
||||
|
||||
cJSON_AddItemToArray(t24_required, cJSON_CreateString("pubkey"));
|
||||
cJSON_AddItemToArray(t24_required, cJSON_CreateString("slug"));
|
||||
cJSON_AddItemToArray(t24_required, cJSON_CreateString("d"));
|
||||
|
||||
cJSON_AddItemToObject(t24_fn, "parameters", t24_params);
|
||||
cJSON_AddItemToObject(t24, "function", t24_fn);
|
||||
@@ -1721,19 +1744,70 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* p_skill_remove_pubkey = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_remove_pubkey, "type", "string");
|
||||
cJSON_AddItemToObject(t25_props, "pubkey", p_skill_remove_pubkey);
|
||||
cJSON* p_skill_remove_slug = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_remove_slug, "type", "string");
|
||||
cJSON_AddItemToObject(t25_props, "slug", p_skill_remove_slug);
|
||||
cJSON* p_skill_remove_d = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_remove_d, "type", "string");
|
||||
cJSON_AddItemToObject(t25_props, "d", p_skill_remove_d);
|
||||
cJSON* p_skill_remove_kind = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_remove_kind, "type", "integer");
|
||||
cJSON_AddItemToObject(t25_props, "kind", p_skill_remove_kind);
|
||||
|
||||
cJSON_AddItemToArray(t25_required, cJSON_CreateString("slug"));
|
||||
cJSON_AddItemToArray(t25_required, cJSON_CreateString("d"));
|
||||
|
||||
cJSON_AddItemToObject(t25_fn, "parameters", t25_params);
|
||||
cJSON_AddItemToObject(t25, "function", t25_fn);
|
||||
cJSON_AddItemToArray(tools, t25);
|
||||
|
||||
cJSON* t25b = cJSON_CreateObject();
|
||||
cJSON* t25b_fn = cJSON_CreateObject();
|
||||
cJSON* t25b_params = cJSON_CreateObject();
|
||||
cJSON* t25b_props = cJSON_CreateObject();
|
||||
cJSON* t25b_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t25b, "type", "function");
|
||||
cJSON_AddStringToObject(t25b_fn, "name", "skill_edit");
|
||||
cJSON_AddStringToObject(t25b_fn, "description", "Edit an existing self skill by d tag and republish it as kind 31123/31124");
|
||||
cJSON_AddStringToObject(t25b_params, "type", "object");
|
||||
cJSON_AddItemToObject(t25b_params, "properties", t25b_props);
|
||||
cJSON_AddItemToObject(t25b_params, "required", t25b_required);
|
||||
|
||||
cJSON* p_skill_edit_d = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_d, "type", "string");
|
||||
cJSON_AddItemToObject(t25b_props, "d", p_skill_edit_d);
|
||||
|
||||
cJSON* p_skill_edit_content = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_content, "type", "string");
|
||||
cJSON_AddItemToObject(t25b_props, "content", p_skill_edit_content);
|
||||
|
||||
cJSON* p_skill_edit_scope = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_scope, "type", "string");
|
||||
cJSON_AddItemToObject(t25b_props, "scope", p_skill_edit_scope);
|
||||
|
||||
cJSON* p_skill_edit_desc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_desc, "type", "string");
|
||||
cJSON_AddItemToObject(t25b_props, "description", p_skill_edit_desc);
|
||||
|
||||
cJSON* p_skill_edit_trigger = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_trigger, "type", "string");
|
||||
cJSON_AddItemToObject(t25b_props, "trigger", p_skill_edit_trigger);
|
||||
|
||||
cJSON* p_skill_edit_filter = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_filter, "type", "string");
|
||||
cJSON_AddItemToObject(t25b_props, "filter", p_skill_edit_filter);
|
||||
|
||||
cJSON* p_skill_edit_action = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_action, "type", "string");
|
||||
cJSON_AddItemToObject(t25b_props, "action", p_skill_edit_action);
|
||||
|
||||
cJSON* p_skill_edit_enabled = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_enabled, "type", "boolean");
|
||||
cJSON_AddItemToObject(t25b_props, "enabled", p_skill_edit_enabled);
|
||||
|
||||
cJSON_AddItemToArray(t25b_required, cJSON_CreateString("d"));
|
||||
|
||||
cJSON_AddItemToObject(t25b_fn, "parameters", t25b_params);
|
||||
cJSON_AddItemToObject(t25b, "function", t25b_fn);
|
||||
cJSON_AddItemToArray(tools, t25b);
|
||||
|
||||
cJSON* t26 = cJSON_CreateObject();
|
||||
cJSON* t26_fn = cJSON_CreateObject();
|
||||
cJSON* t26_params = cJSON_CreateObject();
|
||||
@@ -3435,7 +3509,7 @@ static char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
|
||||
cJSON* slug = cJSON_GetObjectItemCaseSensitive(args, "slug");
|
||||
cJSON* d_tag = cJSON_GetObjectItemCaseSensitive(args, "d");
|
||||
cJSON* content = cJSON_GetObjectItemCaseSensitive(args, "content");
|
||||
cJSON* scope = cJSON_GetObjectItemCaseSensitive(args, "scope");
|
||||
cJSON* description = cJSON_GetObjectItemCaseSensitive(args, "description");
|
||||
@@ -3445,14 +3519,14 @@ static char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
|
||||
cJSON* action = cJSON_GetObjectItemCaseSensitive(args, "action");
|
||||
cJSON* enabled = cJSON_GetObjectItemCaseSensitive(args, "enabled");
|
||||
|
||||
if (!slug || !cJSON_IsString(slug) || !slug->valuestring ||
|
||||
if (!d_tag || !cJSON_IsString(d_tag) || !d_tag->valuestring ||
|
||||
!content || !cJSON_IsString(content) || !content->valuestring) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_create requires slug and content strings");
|
||||
return json_error("skill_create requires d and content strings");
|
||||
}
|
||||
if (!validate_skill_slug(slug->valuestring)) {
|
||||
if (!validate_skill_d_tag(d_tag->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_create slug must be lowercase letters/digits/hyphens (1-64 chars)");
|
||||
return json_error("skill_create d must be lowercase letters/digits/hyphens (1-64 chars)");
|
||||
}
|
||||
if (scope && (!cJSON_IsString(scope) || !scope->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
@@ -3499,7 +3573,7 @@ static char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
|
||||
return json_error("failed to create skill tags");
|
||||
}
|
||||
|
||||
if (add_string_tag(tags, "d", slug->valuestring) != 0 ||
|
||||
if (add_string_tag(tags, "d", d_tag->valuestring) != 0 ||
|
||||
add_string_tag(tags, "app", "didactyl") != 0 ||
|
||||
add_string_tag(tags, "scope", scope_str) != 0) {
|
||||
cJSON_Delete(tags);
|
||||
@@ -3568,7 +3642,7 @@ static char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
|
||||
char* adoption_content = NULL;
|
||||
if (fetch_adoption_list_tags(ctx, &adoption_tags, &adoption_content) == 0) {
|
||||
char addr[256];
|
||||
snprintf(addr, sizeof(addr), "31123:%s:%s", ctx->cfg->keys.public_key_hex, slug->valuestring);
|
||||
snprintf(addr, sizeof(addr), "31123:%s:%s", ctx->cfg->keys.public_key_hex, d_tag->valuestring);
|
||||
|
||||
cJSON* tuple = cJSON_CreateArray();
|
||||
if (tuple) {
|
||||
@@ -3602,7 +3676,7 @@ static char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
|
||||
? TRIGGER_ACTION_TEMPLATE
|
||||
: TRIGGER_ACTION_LLM;
|
||||
if (trigger_manager_add(ctx->trigger_manager,
|
||||
slug->valuestring,
|
||||
d_tag->valuestring,
|
||||
content->valuestring,
|
||||
filter_str,
|
||||
at,
|
||||
@@ -3620,7 +3694,7 @@ static char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(out, "success", skill_publish.success ? 1 : 0);
|
||||
cJSON_AddStringToObject(out, "slug", slug->valuestring);
|
||||
cJSON_AddStringToObject(out, "d_tag", d_tag->valuestring);
|
||||
cJSON_AddNumberToObject(out, "kind", kind);
|
||||
cJSON_AddStringToObject(out, "scope", scope_str);
|
||||
cJSON_AddStringToObject(out, "skill_event_id", skill_publish.event_id);
|
||||
@@ -3726,13 +3800,13 @@ static char* execute_skill_adopt(tools_context_t* ctx, const char* args_json) {
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
|
||||
cJSON* slug = cJSON_GetObjectItemCaseSensitive(args, "slug");
|
||||
cJSON* d_tag = cJSON_GetObjectItemCaseSensitive(args, "d");
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
|
||||
|
||||
if (!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len(pubkey->valuestring, 64U) ||
|
||||
!slug || !cJSON_IsString(slug) || !slug->valuestring || !validate_skill_slug(slug->valuestring)) {
|
||||
!d_tag || !cJSON_IsString(d_tag) || !d_tag->valuestring || !validate_skill_d_tag(d_tag->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_adopt requires pubkey (hex) and valid slug");
|
||||
return json_error("skill_adopt requires pubkey (hex) and valid d tag");
|
||||
}
|
||||
|
||||
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : 31123;
|
||||
@@ -3742,7 +3816,7 @@ static char* execute_skill_adopt(tools_context_t* ctx, const char* args_json) {
|
||||
}
|
||||
|
||||
char addr[256];
|
||||
snprintf(addr, sizeof(addr), "%d:%s:%s", kind_val, pubkey->valuestring, slug->valuestring);
|
||||
snprintf(addr, sizeof(addr), "%d:%s:%s", kind_val, pubkey->valuestring, d_tag->valuestring);
|
||||
|
||||
cJSON* tags = NULL;
|
||||
char* content = NULL;
|
||||
@@ -3819,7 +3893,7 @@ static char* execute_skill_remove(tools_context_t* ctx, const char* args_json) {
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
|
||||
cJSON* slug = cJSON_GetObjectItemCaseSensitive(args, "slug");
|
||||
cJSON* d_tag = cJSON_GetObjectItemCaseSensitive(args, "d");
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
|
||||
|
||||
const char* pubkey_str = ctx->cfg->keys.public_key_hex;
|
||||
@@ -3831,9 +3905,9 @@ static char* execute_skill_remove(tools_context_t* ctx, const char* args_json) {
|
||||
pubkey_str = pubkey->valuestring;
|
||||
}
|
||||
|
||||
if (!slug || !cJSON_IsString(slug) || !slug->valuestring || !validate_skill_slug(slug->valuestring)) {
|
||||
if (!d_tag || !cJSON_IsString(d_tag) || !d_tag->valuestring || !validate_skill_d_tag(d_tag->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_remove requires valid slug");
|
||||
return json_error("skill_remove requires valid d tag");
|
||||
}
|
||||
|
||||
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : 31123;
|
||||
@@ -3843,7 +3917,7 @@ static char* execute_skill_remove(tools_context_t* ctx, const char* args_json) {
|
||||
}
|
||||
|
||||
char addr[256];
|
||||
snprintf(addr, sizeof(addr), "%d:%s:%s", kind_val, pubkey_str, slug->valuestring);
|
||||
snprintf(addr, sizeof(addr), "%d:%s:%s", kind_val, pubkey_str, d_tag->valuestring);
|
||||
|
||||
cJSON* tags = NULL;
|
||||
char* content = NULL;
|
||||
@@ -3878,7 +3952,7 @@ static char* execute_skill_remove(tools_context_t* ctx, const char* args_json) {
|
||||
}
|
||||
|
||||
if (ctx->trigger_manager) {
|
||||
(void)trigger_manager_remove(ctx->trigger_manager, slug->valuestring);
|
||||
(void)trigger_manager_remove(ctx->trigger_manager, d_tag->valuestring);
|
||||
}
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
@@ -3909,6 +3983,276 @@ static char* execute_skill_remove(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_skill_edit(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
|
||||
cJSON* d = cJSON_GetObjectItemCaseSensitive(args, "d");
|
||||
cJSON* content = cJSON_GetObjectItemCaseSensitive(args, "content");
|
||||
cJSON* scope = cJSON_GetObjectItemCaseSensitive(args, "scope");
|
||||
cJSON* description = cJSON_GetObjectItemCaseSensitive(args, "description");
|
||||
cJSON* trigger = cJSON_GetObjectItemCaseSensitive(args, "trigger");
|
||||
cJSON* filter = cJSON_GetObjectItemCaseSensitive(args, "filter");
|
||||
cJSON* action = cJSON_GetObjectItemCaseSensitive(args, "action");
|
||||
cJSON* enabled = cJSON_GetObjectItemCaseSensitive(args, "enabled");
|
||||
|
||||
if (!d || !cJSON_IsString(d) || !d->valuestring || !validate_skill_d_tag(d->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit requires valid d tag");
|
||||
}
|
||||
if (content && (!cJSON_IsString(content) || !content->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit content must be string when provided");
|
||||
}
|
||||
if (scope && (!cJSON_IsString(scope) || !scope->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit scope must be string when provided");
|
||||
}
|
||||
if (description && (!cJSON_IsString(description) || !description->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit description must be string when provided");
|
||||
}
|
||||
if (trigger && (!cJSON_IsString(trigger) || !trigger->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit trigger must be string when provided");
|
||||
}
|
||||
if (filter && (!cJSON_IsString(filter) || !filter->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit filter must be string when provided");
|
||||
}
|
||||
if (action && (!cJSON_IsString(action) || !action->valuestring)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit action must be string when provided");
|
||||
}
|
||||
if (enabled && !cJSON_IsBool(enabled)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit enabled must be boolean when provided");
|
||||
}
|
||||
|
||||
char* events_json = nostr_handler_get_self_skill_events_json();
|
||||
if (!events_json) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("skill_edit cache unavailable");
|
||||
}
|
||||
|
||||
cJSON* events = cJSON_Parse(events_json);
|
||||
free(events_json);
|
||||
if (!events || !cJSON_IsArray(events)) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
return json_error("skill_edit cache returned invalid JSON");
|
||||
}
|
||||
|
||||
cJSON* target = NULL;
|
||||
long long best_created_at = -1;
|
||||
int event_count = cJSON_GetArraySize(events);
|
||||
for (int i = 0; i < event_count; i++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(events, i);
|
||||
if (!ev || !cJSON_IsObject(ev)) continue;
|
||||
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(ev, "kind");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(ev, "tags");
|
||||
if (!kind || !cJSON_IsNumber(kind) || !tags || !cJSON_IsArray(tags)) continue;
|
||||
|
||||
int kind_val = (int)kind->valuedouble;
|
||||
if (kind_val != 31123 && kind_val != 31124) continue;
|
||||
|
||||
cJSON* d_val = find_tag_value_string(tags, "d");
|
||||
if (!d_val || !cJSON_IsString(d_val) || !d_val->valuestring) continue;
|
||||
if (strcmp(d_val->valuestring, d->valuestring) != 0) continue;
|
||||
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
long long created = (created_at && cJSON_IsNumber(created_at)) ? (long long)created_at->valuedouble : 0;
|
||||
if (!target || created > best_created_at) {
|
||||
target = ev;
|
||||
best_created_at = created;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
return json_error("skill_edit could not find self skill with that d");
|
||||
}
|
||||
|
||||
cJSON* target_kind = cJSON_GetObjectItemCaseSensitive(target, "kind");
|
||||
cJSON* target_content = cJSON_GetObjectItemCaseSensitive(target, "content");
|
||||
cJSON* target_tags = cJSON_GetObjectItemCaseSensitive(target, "tags");
|
||||
if (!target_kind || !cJSON_IsNumber(target_kind) || !target_content || !cJSON_IsString(target_content) ||
|
||||
!target_content->valuestring || !target_tags || !cJSON_IsArray(target_tags)) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
return json_error("skill_edit found malformed skill event");
|
||||
}
|
||||
|
||||
int out_kind = (int)target_kind->valuedouble;
|
||||
if (scope && scope->valuestring) {
|
||||
if (strcmp(scope->valuestring, "public") == 0) {
|
||||
out_kind = 31123;
|
||||
} else if (strcmp(scope->valuestring, "private") == 0) {
|
||||
out_kind = 31124;
|
||||
} else {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
return json_error("skill_edit scope must be public or private");
|
||||
}
|
||||
}
|
||||
|
||||
const char* out_content = (content && content->valuestring) ? content->valuestring : target_content->valuestring;
|
||||
|
||||
cJSON* tags_out = cJSON_Duplicate(target_tags, 1);
|
||||
if (!tags_out || !cJSON_IsArray(tags_out)) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit failed to duplicate existing tags");
|
||||
}
|
||||
|
||||
if (upsert_string_tag(tags_out, "d", d->valuestring) != 0) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit failed to set d tag");
|
||||
}
|
||||
if (upsert_string_tag(tags_out, "app", "didactyl") != 0) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit failed to set app tag");
|
||||
}
|
||||
if (upsert_string_tag(tags_out, "scope", out_kind == 31123 ? "public" : "private") != 0) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit failed to set scope tag");
|
||||
}
|
||||
|
||||
if (description) {
|
||||
if (description->valuestring[0] == '\0') {
|
||||
remove_tag_key_all(tags_out, "description");
|
||||
} else if (upsert_string_tag(tags_out, "description", description->valuestring) != 0) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit failed to set description tag");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* existing_trigger = find_tag_value_string(tags_out, "trigger");
|
||||
cJSON* existing_filter = find_tag_value_string(tags_out, "filter");
|
||||
cJSON* existing_action = find_tag_value_string(tags_out, "action");
|
||||
cJSON* existing_enabled = find_tag_value_string(tags_out, "enabled");
|
||||
|
||||
const char* merged_trigger = (trigger && trigger->valuestring) ? trigger->valuestring
|
||||
: (existing_trigger && cJSON_IsString(existing_trigger) ? existing_trigger->valuestring : NULL);
|
||||
const char* merged_filter = (filter && filter->valuestring) ? filter->valuestring
|
||||
: (existing_filter && cJSON_IsString(existing_filter) ? existing_filter->valuestring : NULL);
|
||||
const char* merged_action = (action && action->valuestring) ? action->valuestring
|
||||
: (existing_action && cJSON_IsString(existing_action) && existing_action->valuestring[0] != '\0'
|
||||
? existing_action->valuestring
|
||||
: "llm");
|
||||
|
||||
int merged_enabled = (enabled && cJSON_IsBool(enabled)) ? (cJSON_IsTrue(enabled) ? 1 : 0)
|
||||
: (existing_enabled && cJSON_IsString(existing_enabled) && existing_enabled->valuestring &&
|
||||
strcmp(existing_enabled->valuestring, "false") == 0)
|
||||
? 0
|
||||
: 1;
|
||||
|
||||
int trigger_patch_requested = (trigger || filter || action || enabled) ? 1 : 0;
|
||||
if (trigger_patch_requested || (merged_trigger || merged_filter)) {
|
||||
if ((merged_trigger && !merged_filter) || (!merged_trigger && merged_filter)) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit trigger and filter must both be set together");
|
||||
}
|
||||
remove_tag_key_all(tags_out, "trigger");
|
||||
remove_tag_key_all(tags_out, "filter");
|
||||
remove_tag_key_all(tags_out, "action");
|
||||
remove_tag_key_all(tags_out, "enabled");
|
||||
|
||||
if (merged_trigger && merged_filter) {
|
||||
if (strcmp(merged_trigger, "nostr-subscription") != 0) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit trigger must be nostr-subscription");
|
||||
}
|
||||
if (add_string_tag(tags_out, "trigger", merged_trigger) != 0 ||
|
||||
add_string_tag(tags_out, "filter", merged_filter) != 0 ||
|
||||
add_string_tag(tags_out, "action", merged_action) != 0 ||
|
||||
add_string_tag(tags_out, "enabled", merged_enabled ? "true" : "false") != 0) {
|
||||
cJSON_Delete(args);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(tags_out);
|
||||
return json_error("skill_edit failed to set trigger tags");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nostr_publish_result_t publish_result;
|
||||
memset(&publish_result, 0, sizeof(publish_result));
|
||||
int rc = nostr_handler_publish_kind_event(out_kind, out_content, tags_out, &publish_result);
|
||||
cJSON_Delete(tags_out);
|
||||
cJSON_Delete(events);
|
||||
|
||||
if (rc != 0) {
|
||||
cJSON_Delete(args);
|
||||
nostr_handler_publish_result_free(&publish_result);
|
||||
return json_error("skill_edit failed to publish updated skill");
|
||||
}
|
||||
|
||||
int trigger_registered = 0;
|
||||
if (ctx->trigger_manager) {
|
||||
if (merged_trigger && merged_filter && merged_enabled) {
|
||||
trigger_action_type_t at = (strcmp(merged_action, "template") == 0)
|
||||
? TRIGGER_ACTION_TEMPLATE
|
||||
: TRIGGER_ACTION_LLM;
|
||||
if (trigger_manager_update(ctx->trigger_manager,
|
||||
d->valuestring,
|
||||
out_content,
|
||||
merged_filter,
|
||||
at,
|
||||
merged_enabled) == 0 ||
|
||||
trigger_manager_add(ctx->trigger_manager,
|
||||
d->valuestring,
|
||||
out_content,
|
||||
merged_filter,
|
||||
at,
|
||||
merged_enabled) == 0) {
|
||||
trigger_registered = 1;
|
||||
}
|
||||
} else {
|
||||
(void)trigger_manager_remove(ctx->trigger_manager, d->valuestring);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) {
|
||||
cJSON_Delete(args);
|
||||
nostr_handler_publish_result_free(&publish_result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
|
||||
cJSON_AddStringToObject(out, "d_tag", d->valuestring);
|
||||
cJSON_AddNumberToObject(out, "kind", out_kind);
|
||||
cJSON_AddStringToObject(out, "scope", out_kind == 31123 ? "public" : "private");
|
||||
cJSON_AddStringToObject(out, "skill_event_id", publish_result.event_id);
|
||||
cJSON_AddBoolToObject(out, "trigger_registered", trigger_registered ? 1 : 0);
|
||||
if (publish_result.naddr_uri[0] != '\0') {
|
||||
cJSON_AddStringToObject(out, "naddr_uri", publish_result.naddr_uri);
|
||||
}
|
||||
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
cJSON_Delete(args);
|
||||
nostr_handler_publish_result_free(&publish_result);
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_skill_search(const char* args_json) {
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
@@ -4106,11 +4450,11 @@ static char* execute_skill_search(const char* args_json) {
|
||||
if (!summary) continue;
|
||||
|
||||
if (query_str && query_str[0] != '\0') {
|
||||
cJSON* slug = cJSON_GetObjectItemCaseSensitive(summary, "slug");
|
||||
cJSON* d_tag = cJSON_GetObjectItemCaseSensitive(summary, "d_tag");
|
||||
cJSON* desc = cJSON_GetObjectItemCaseSensitive(summary, "description");
|
||||
cJSON* preview = cJSON_GetObjectItemCaseSensitive(summary, "content_preview");
|
||||
int match = 0;
|
||||
if (slug && cJSON_IsString(slug) && slug->valuestring && ci_contains(slug->valuestring, query_str)) match = 1;
|
||||
if (d_tag && cJSON_IsString(d_tag) && d_tag->valuestring && ci_contains(d_tag->valuestring, query_str)) match = 1;
|
||||
if (!match && desc && cJSON_IsString(desc) && desc->valuestring && ci_contains(desc->valuestring, query_str)) match = 1;
|
||||
if (!match && preview && cJSON_IsString(preview) && preview->valuestring && ci_contains(preview->valuestring, query_str)) match = 1;
|
||||
if (!match) {
|
||||
@@ -5649,6 +5993,9 @@ char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* arg
|
||||
if (strcmp(tool_name, "skill_search") == 0) {
|
||||
return execute_skill_search(args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "skill_edit") == 0) {
|
||||
return execute_skill_edit(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "trigger_list") == 0) {
|
||||
return execute_trigger_list(ctx, args_json);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
typedef struct {
|
||||
trigger_manager_t* mgr;
|
||||
char skill_slug[TRIGGER_SKILL_SLUG_MAX];
|
||||
char skill_d_tag[TRIGGER_SKILL_D_TAG_MAX];
|
||||
} trigger_subscription_ctx_t;
|
||||
|
||||
static int clamp_enabled(int enabled) {
|
||||
@@ -52,13 +52,13 @@ static int ensure_capacity(trigger_manager_t* mgr, int needed) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_trigger_index_locked(trigger_manager_t* mgr, const char* skill_slug) {
|
||||
if (!mgr || !skill_slug || skill_slug[0] == '\0') {
|
||||
static int find_trigger_index_locked(trigger_manager_t* mgr, const char* skill_d_tag) {
|
||||
if (!mgr || !skill_d_tag || skill_d_tag[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) {
|
||||
if (strncmp(mgr->triggers[i].skill_d_tag, skill_d_tag, TRIGGER_SKILL_D_TAG_MAX) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -85,8 +85,8 @@ static cJSON* find_tag_value_string(cJSON* tags, const char* key) {
|
||||
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) {
|
||||
static int parse_address_tag(const char* addr, int* out_kind, char out_pubkey[65], char out_d_tag[65]) {
|
||||
if (!addr || !out_kind || !out_pubkey || !out_d_tag) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -108,9 +108,9 @@ static int parse_address_tag(const char* addr, int* out_kind, char out_pubkey[65
|
||||
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);
|
||||
size_t d_tag_len = strlen(p2 + 1);
|
||||
if (d_tag_len == 0 || d_tag_len >= 65U) return -1;
|
||||
memcpy(out_d_tag, p2 + 1, d_tag_len + 1U);
|
||||
|
||||
*out_kind = kind;
|
||||
return 0;
|
||||
@@ -173,7 +173,7 @@ static void execute_template_action(trigger_manager_t* mgr,
|
||||
} 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);
|
||||
DEBUG_INFO("[didactyl] trigger template log (%s): %s", t->skill_d_tag, body);
|
||||
} else {
|
||||
(void)nostr_handler_send_dm_auto(mgr->cfg->admin.pubkey, rendered);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ static void execute_llm_action(const active_trigger_t* t, cJSON* event, const ch
|
||||
if (!t || !event) {
|
||||
return;
|
||||
}
|
||||
agent_on_trigger(t->skill_slug, t->skill_content, event, relay_url);
|
||||
agent_on_trigger(t->skill_d_tag, t->skill_content, event, relay_url);
|
||||
}
|
||||
|
||||
static int maybe_fire_trigger_locked(trigger_manager_t* mgr, int index, cJSON* event, const char* relay_url) {
|
||||
@@ -256,7 +256,7 @@ static void on_trigger_subscription_event(cJSON* event, const char* relay_url, v
|
||||
trigger_manager_t* mgr = ctx->mgr;
|
||||
pthread_mutex_lock(&mgr->mutex);
|
||||
|
||||
int idx = find_trigger_index_locked(mgr, ctx->skill_slug);
|
||||
int idx = find_trigger_index_locked(mgr, ctx->skill_d_tag);
|
||||
if (idx >= 0) {
|
||||
(void)maybe_fire_trigger_locked(mgr, idx, event, relay_url);
|
||||
}
|
||||
@@ -300,7 +300,7 @@ static int register_trigger_subscription_locked(trigger_manager_t* mgr, active_t
|
||||
}
|
||||
|
||||
ctx->mgr = mgr;
|
||||
snprintf(ctx->skill_slug, sizeof(ctx->skill_slug), "%s", t->skill_slug);
|
||||
snprintf(ctx->skill_d_tag, sizeof(ctx->skill_d_tag), "%s", t->skill_d_tag);
|
||||
|
||||
nostr_pool_subscription_t* sub = nostr_handler_subscribe_with_filter(
|
||||
filter,
|
||||
@@ -392,8 +392,8 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
|
||||
int kind = 0;
|
||||
char pubkey[65] = {0};
|
||||
char slug[65] = {0};
|
||||
if (parse_address_tag(val->valuestring, &kind, pubkey, slug) != 0) {
|
||||
char d_tag[65] = {0};
|
||||
if (parse_address_tag(val->valuestring, &kind, pubkey, d_tag) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
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) {
|
||||
strcmp(ev_d->valuestring, d_tag) != 0) {
|
||||
continue;
|
||||
}
|
||||
cJSON* dup = cJSON_Duplicate(ev, 1);
|
||||
@@ -444,7 +444,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
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_AddItemToArray(d_values, cJSON_CreateString(d_tag));
|
||||
cJSON_AddItemToObject(skill_filter, "#d", d_values);
|
||||
cJSON_AddNumberToObject(skill_filter, "limit", 1);
|
||||
|
||||
@@ -482,7 +482,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
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) {
|
||||
if (trigger_manager_add(mgr, d_tag, content->valuestring, filter_s, at, is_enabled) == 0) {
|
||||
loaded++;
|
||||
}
|
||||
}
|
||||
@@ -532,13 +532,13 @@ int trigger_manager_load_from_startup_events(trigger_manager_t* mgr) {
|
||||
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* d_tag = (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') {
|
||||
if (!d_tag || d_tag[0] == '\0') {
|
||||
cJSON_Delete(tags);
|
||||
DEBUG_WARN("[didactyl] startup trigger skip: missing d tag for startup event index=%d", i);
|
||||
continue;
|
||||
@@ -552,11 +552,11 @@ int trigger_manager_load_from_startup_events(trigger_manager_t* mgr) {
|
||||
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) {
|
||||
if (trigger_manager_add(mgr, d_tag, 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);
|
||||
DEBUG_INFO("[didactyl] startup trigger registered d_tag=%s action=%s enabled=%d", d_tag, at == TRIGGER_ACTION_TEMPLATE ? "template" : "llm", is_enabled);
|
||||
} else {
|
||||
DEBUG_WARN("[didactyl] startup trigger failed slug=%s", slug);
|
||||
DEBUG_WARN("[didactyl] startup trigger failed d_tag=%s", d_tag);
|
||||
}
|
||||
|
||||
cJSON_Delete(tags);
|
||||
@@ -567,12 +567,12 @@ 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* skill_d_tag,
|
||||
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) {
|
||||
if (!mgr || !skill_d_tag || skill_d_tag[0] == '\0' || !content || !filter_json) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -582,10 +582,10 @@ int trigger_manager_add(trigger_manager_t* mgr,
|
||||
|
||||
pthread_mutex_lock(&mgr->mutex);
|
||||
|
||||
int existing = find_trigger_index_locked(mgr, skill_slug);
|
||||
int existing = find_trigger_index_locked(mgr, skill_d_tag);
|
||||
if (existing >= 0) {
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
return trigger_manager_update(mgr, skill_slug, content, filter_json, action_type, enabled);
|
||||
return trigger_manager_update(mgr, skill_d_tag, content, filter_json, action_type, enabled);
|
||||
}
|
||||
|
||||
int max_active = mgr->cfg ? mgr->cfg->triggers.max_active : TRIGGER_DEFAULT_MAX_ACTIVE;
|
||||
@@ -603,7 +603,7 @@ int trigger_manager_add(trigger_manager_t* mgr,
|
||||
|
||||
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_d_tag, sizeof(t->skill_d_tag), "%s", skill_d_tag);
|
||||
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;
|
||||
@@ -615,7 +615,7 @@ int trigger_manager_add(trigger_manager_t* mgr,
|
||||
|
||||
if (register_trigger_subscription_locked(mgr, t) != 0) {
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
DEBUG_WARN("[didactyl] trigger add rejected: failed to create subscription slug=%s", skill_slug);
|
||||
DEBUG_WARN("[didactyl] trigger add rejected: failed to create subscription d_tag=%s", skill_d_tag);
|
||||
memset(t, 0, sizeof(*t));
|
||||
return -1;
|
||||
}
|
||||
@@ -624,18 +624,18 @@ int trigger_manager_add(trigger_manager_t* mgr,
|
||||
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
|
||||
DEBUG_INFO("[didactyl] trigger added slug=%s action=%d enabled=%d", skill_slug, (int)action_type, clamp_enabled(enabled));
|
||||
DEBUG_INFO("[didactyl] trigger added d_tag=%s action=%d enabled=%d", skill_d_tag, (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') {
|
||||
int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_d_tag) {
|
||||
if (!mgr || !skill_d_tag || skill_d_tag[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mgr->mutex);
|
||||
|
||||
int idx = find_trigger_index_locked(mgr, skill_slug);
|
||||
int idx = find_trigger_index_locked(mgr, skill_d_tag);
|
||||
if (idx < 0) {
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
return 0;
|
||||
@@ -654,17 +654,17 @@ int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_slug) {
|
||||
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
|
||||
DEBUG_INFO("[didactyl] trigger removed slug=%s", skill_slug);
|
||||
DEBUG_INFO("[didactyl] trigger removed d_tag=%s", skill_d_tag);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int trigger_manager_update(trigger_manager_t* mgr,
|
||||
const char* skill_slug,
|
||||
const char* skill_d_tag,
|
||||
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) {
|
||||
if (!mgr || !skill_d_tag || skill_d_tag[0] == '\0' || !content || !filter_json) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -674,10 +674,10 @@ int trigger_manager_update(trigger_manager_t* mgr,
|
||||
|
||||
pthread_mutex_lock(&mgr->mutex);
|
||||
|
||||
int idx = find_trigger_index_locked(mgr, skill_slug);
|
||||
int idx = find_trigger_index_locked(mgr, skill_d_tag);
|
||||
if (idx < 0) {
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
return trigger_manager_add(mgr, skill_slug, content, filter_json, action_type, enabled);
|
||||
return trigger_manager_add(mgr, skill_d_tag, content, filter_json, action_type, enabled);
|
||||
}
|
||||
|
||||
active_trigger_t* t = &mgr->triggers[idx];
|
||||
@@ -688,13 +688,13 @@ int trigger_manager_update(trigger_manager_t* mgr,
|
||||
|
||||
if (register_trigger_subscription_locked(mgr, t) != 0) {
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
DEBUG_WARN("[didactyl] trigger update failed to (re)subscribe slug=%s", skill_slug);
|
||||
DEBUG_WARN("[didactyl] trigger update failed to (re)subscribe d_tag=%s", skill_d_tag);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
|
||||
DEBUG_INFO("[didactyl] trigger updated slug=%s action=%d enabled=%d", skill_slug, (int)action_type, clamp_enabled(enabled));
|
||||
DEBUG_INFO("[didactyl] trigger updated d_tag=%s action=%d enabled=%d", skill_d_tag, (int)action_type, clamp_enabled(enabled));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -758,7 +758,7 @@ char* trigger_manager_status_json(trigger_manager_t* mgr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(item, "skill_slug", t->skill_slug);
|
||||
cJSON_AddStringToObject(item, "skill_d_tag", t->skill_d_tag);
|
||||
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);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
|
||||
#define TRIGGER_DEFAULT_MAX_ACTIVE 16
|
||||
#define TRIGGER_SKILL_SLUG_MAX 65
|
||||
#define TRIGGER_SKILL_D_TAG_MAX 65
|
||||
#define TRIGGER_SKILL_CONTENT_MAX 4096
|
||||
#define TRIGGER_FILTER_JSON_MAX 2048
|
||||
|
||||
@@ -18,7 +18,7 @@ typedef enum {
|
||||
} trigger_action_type_t;
|
||||
|
||||
typedef struct {
|
||||
char skill_slug[TRIGGER_SKILL_SLUG_MAX];
|
||||
char skill_d_tag[TRIGGER_SKILL_D_TAG_MAX];
|
||||
char skill_content[TRIGGER_SKILL_CONTENT_MAX];
|
||||
char filter_json[TRIGGER_FILTER_JSON_MAX];
|
||||
trigger_action_type_t action_type;
|
||||
@@ -42,14 +42,14 @@ 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* skill_d_tag,
|
||||
const char* content,
|
||||
const char* filter_json,
|
||||
trigger_action_type_t action_type,
|
||||
int enabled);
|
||||
int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_slug);
|
||||
int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_d_tag);
|
||||
int trigger_manager_update(trigger_manager_t* mgr,
|
||||
const char* skill_slug,
|
||||
const char* skill_d_tag,
|
||||
const char* content,
|
||||
const char* filter_json,
|
||||
trigger_action_type_t action_type,
|
||||
|
||||
Reference in New Issue
Block a user