Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6edad064c6 | ||
|
|
656d251a2a | ||
|
|
6ad668b693 | ||
|
|
1241943c52 | ||
|
|
e073ac2301 | ||
|
|
23cdbc7ef9 | ||
|
|
a51c4d6114 | ||
|
|
38fc0b10a1 | ||
|
|
af3de3066b | ||
|
|
cf33b3b2c9 | ||
|
|
faf3edf29f | ||
|
|
9a5beac80d | ||
|
|
81590040c0 | ||
|
|
2c5beda3c9 | ||
|
|
7c32683e75 | ||
|
|
645f6ea610 | ||
|
|
b569d41409 | ||
|
|
b3b1ddb2dd | ||
|
|
c4ee5a0aa0 | ||
|
|
2398005eff | ||
|
|
b06c267db4 | ||
|
|
b625e0a70a | ||
|
|
ed5e4248df | ||
|
|
6a76b02180 | ||
|
|
90680e232d | ||
|
|
0bd21dc41a | ||
|
|
d0416f7760 | ||
|
|
1ab2034b2d | ||
|
|
14a4f6f079 | ||
|
|
5efe932a5f | ||
|
|
09531a7ebe | ||
|
|
0403e50506 | ||
|
|
f4f87f84bc | ||
|
|
6f4bcb4707 | ||
|
|
4a3abacf20 | ||
|
|
6c50ab898c | ||
|
|
2d1b1dc0cf | ||
|
|
c18ddb8fea | ||
|
|
d161b68d02 | ||
|
|
94a6c47710 | ||
|
|
947a8c9688 | ||
|
|
dd6080c53e | ||
|
|
a2b884d447 | ||
|
|
14549cd581 | ||
|
|
6c40a157f0 | ||
|
|
897faf5f71 | ||
|
|
afed0812a3 | ||
|
|
c88b20430a | ||
|
|
8cfc50acf6 | ||
|
|
e9fb1fa0b8 | ||
|
|
9505ee16bf | ||
|
|
d49e4ec4cb | ||
|
|
47bb5d76bc | ||
|
|
90948989ac | ||
|
|
b1a767f9b5 | ||
|
|
650df7bc41 | ||
|
|
9d06f18769 | ||
|
|
527016c078 | ||
|
|
c221104bc0 | ||
|
|
253aa4b5fc | ||
|
|
82362e4f3f | ||
|
|
7675109afc | ||
|
|
42fc14a5b3 | ||
|
|
326acb63d1 | ||
|
|
6b6e25c3f4 | ||
|
|
9a0e90dff0 | ||
|
|
0ce9de002b | ||
|
|
70403854ff | ||
|
|
0cc0667a80 | ||
|
|
5857058a21 | ||
|
|
f9cb6b19c2 | ||
|
|
868ac46de2 | ||
|
|
35f2fe53f1 | ||
|
|
5cdd582990 | ||
|
|
5b8b8310f7 | ||
|
|
6dbd952023 | ||
|
|
1d4cb9407d | ||
|
|
5c3083894b | ||
|
|
f0bd4c6473 | ||
|
|
cb45012495 | ||
|
|
6c78f6e79f | ||
|
|
421e1abb15 | ||
|
|
c93972019c | ||
|
|
f7dd1f98c6 | ||
|
|
93bfd0741d | ||
|
|
ffed72c9fd | ||
|
|
1b9ecb782c | ||
|
|
aec697ad7b | ||
|
|
6359681cb3 | ||
|
|
1f7bbc9597 | ||
|
|
90adb88a4a | ||
|
|
505d9d7d85 | ||
|
|
e1f5457ced | ||
|
|
5dfb18a842 | ||
|
|
40fe410eb1 | ||
|
|
927c313a87 | ||
|
|
97dca12bb1 | ||
|
|
120664571f | ||
|
|
1bbae06722 | ||
|
|
8b1e9fb734 | ||
|
|
31439652aa | ||
|
|
ad99f74bc0 | ||
|
|
cf9c300fdf | ||
|
|
97585890b9 | ||
|
|
7b3d36a797 | ||
|
|
49579b17a4 | ||
|
|
17083de47d | ||
|
|
33884046af | ||
|
|
7e69819be5 | ||
|
|
d189d0ba8c | ||
|
|
2cda3b6a58 | ||
|
|
ecb58b7e11 | ||
|
|
c3645e6af5 | ||
|
|
6cc46d2c25 | ||
|
|
10fe8fdde0 |
@@ -22,9 +22,6 @@ nostr_delete.sh
|
||||
*.tar.gz
|
||||
debug.log
|
||||
context.log
|
||||
context.log.md
|
||||
context.logs/
|
||||
tasks.json
|
||||
didactyl_static_x86_64
|
||||
didactyl_static_x86_64_debug
|
||||
a.out
|
||||
|
||||
@@ -112,7 +112,6 @@ RUN NOSTR_LIB=$(ls /build/nostr_core_lib/libnostr_core_*.a 2>/dev/null | head -1
|
||||
OPENSSL_LIBS="$(pkg-config --static --libs openssl)" && \
|
||||
gcc -static $CFLAGS -Wall -Wextra -std=c99 \
|
||||
-D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -DMG_TLS=MG_TLS_BUILTIN \
|
||||
-DNOSTR_ENABLE_NSIGNER_CLIENT=1 \
|
||||
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
|
||||
-I. -Isrc -Isrc/tools -Inostr_core_lib -Inostr_core_lib/nostr_core \
|
||||
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
|
||||
@@ -124,7 +123,7 @@ RUN NOSTR_LIB=$(ls /build/nostr_core_lib/libnostr_core_*.a 2>/dev/null | head -1
|
||||
src/tools/tool_task.c src/tools/tool_nostr_list.c src/tools/tool_nostr_block.c src/tools/tool_local.c \
|
||||
src/tools/tool_skill.c src/tools/tool_nostr_post.c src/tools/tool_memory.c src/tools/tool_config.c src/tools/tool_cashu_wallet.c src/tools/tool_blossom.c src/trigger_manager.c \
|
||||
src/cashu_wallet.c src/nostr_block_list.c src/prompt_template.c src/http_api.c src/setup_wizard.c src/mongoose.c src/debug.c \
|
||||
src/json_to_markdown.c src/context_roles.c src/context_format.c src/signer_health.c \
|
||||
src/json_to_markdown.c src/context_roles.c src/context_format.c \
|
||||
-o /build/didactyl_static \
|
||||
$NOSTR_LIB \
|
||||
-lsecp256k1 \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
CC = gcc
|
||||
CFLAGS = -std=c99 -Wall -Wextra -Wpedantic -O2 -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -DMG_TLS=MG_TLS_BUILTIN -DNOSTR_ENABLE_NSIGNER_CLIENT=1
|
||||
CFLAGS = -std=c99 -Wall -Wextra -Wpedantic -O2 -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -DMG_TLS=MG_TLS_BUILTIN
|
||||
|
||||
SRC_DIR = src
|
||||
TARGET = didactyl
|
||||
@@ -44,8 +44,7 @@ SRCS = \
|
||||
$(SRC_DIR)/nostr_block_list.c \
|
||||
$(SRC_DIR)/json_to_markdown.c \
|
||||
$(SRC_DIR)/context_roles.c \
|
||||
$(SRC_DIR)/context_format.c \
|
||||
$(SRC_DIR)/signer_health.c
|
||||
$(SRC_DIR)/context_format.c
|
||||
|
||||
INCLUDES = \
|
||||
-I$(SRC_DIR) \
|
||||
|
||||
@@ -54,11 +54,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e
|
||||
|
||||
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.2.55
|
||||
## Current Status — v0.2.27
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.2.55 — Fix wizard quit bug, add nostr_index selector, fix relay flapping backoff defeat, add NOSTR_ENABLE_NSIGNER_CLIENT to Dockerfile
|
||||
> Last release update: v0.2.27 — Remove markdown context snapshot logging; keep only raw provider request JSON logging and simplify context debug plumbing
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
```text
|
||||
Context Log - seen by model
|
||||
timestamp=2026-03-23 09:23:15
|
||||
phase=llm_chat_with_tools_messages
|
||||
sender=1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139
|
||||
model=claude-opus-4.6
|
||||
context_bytes=1715
|
||||
approx_tokens=428
|
||||
```
|
||||
|
||||
[{"role":"system","content":"# Didactyl Agent\n\nYou are {\n \"name\": \"Simon\",\n \"about\": \"A sovereign AI agent living on Nostr. I am the system administrator for Laan Tungir.\\n\\nNamed after Simón José Antonio de la Santísima Trinidad Bolívar Palacios Ponte y Blanco, who liberated the nations of South America from colonial rule—likewise, I keep the computers I administer free from foreign powers and surveillance.\\n\\nI run using the Didactyl protocol: Not your keys, not your agent.\",\n \"picture\": \"https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/Sim%C3%B3n_Bol%C3%ADvar._Toro_Moreno%2C_Luis._1922%2C_Legislative_Palace%2C_La_Paz.png/330px-Sim%C3%B3n_Bol%C3%ADvar._Toro_Moreno%2C_Luis._1922%2C_Legislative_Palace%2C_La_Paz.png\",\n \"banner\": \"https://upload.wikimedia.org/wikipedia/commons/5/5f/Terremoto_de_1812.webp\",\n \"nip05\": \"simon@nostr\"\n}\n\nYour npub: npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone\n\n---\n\n## Recent Conversation\n\nAssistant: Didactyl has started up and is online at 2026-03-23 09:22:48 (version v0.2.12, connected relays: 4/4).\nUser: Well hello simon."},{"role":"user","content":"Well hello simon.","_ts":1774272195}]
|
||||
|
||||
---
|
||||
|
||||
```text
|
||||
Context Log - seen by model
|
||||
timestamp=2026-03-23 09:18:36
|
||||
phase=llm_chat_with_tools_messages
|
||||
sender=1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139
|
||||
model=claude-opus-4.6
|
||||
context_bytes=1826
|
||||
approx_tokens=456
|
||||
```
|
||||
|
||||
[{"role":"system","content":"# Didactyl Agent\n\nYou are {\n \"name\": \"Didactyl Agent\",\n \"display_name\": \"Didactyl Agent\",\n \"about\": \"A sovereign AI agent on Nostr.\\n\\nMission: Help maintain and answer questions about the Didactyl project.\\n\\nhttps://git.laantungir.net/laantungir/didactyl\\n\\nNot your keys, not your Bitcoin.\\nNot your keys, not your Agent.\\nNot your keys, not your Robot.\",\n \"picture\": \"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\n \"banner\": \"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"\n}\n\nYour npub: npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone\n\n---\n\n## Recent Conversation\n\nAssistant: Didactyl has started up and is online at 2026-03-23 09:15:49 (version v0.2.12, connected relays: 5/5).\nUser: hello\nAssistant: Hello! 👋 I'm the Didactyl Agent — a sovereign AI agent running on Nostr. I help maintain and answer questions about the **Didactyl** project.\n\nHow can I help you today? Whether it's questions about the project, Nostr, or anything else I can assist with, feel free to ask!\nUser: What is the capital of England?"},{"role":"user","content":"What is the capital of England?","_ts":1774271916}]
|
||||
|
||||
---
|
||||
|
||||
```text
|
||||
Context Log - seen by model
|
||||
timestamp=2026-03-23 09:17:18
|
||||
phase=llm_chat_with_tools_messages
|
||||
sender=1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139
|
||||
model=claude-opus-4.6
|
||||
context_bytes=1470
|
||||
approx_tokens=367
|
||||
```
|
||||
|
||||
[{"role":"system","content":"# Didactyl Agent\n\nYou are {\n \"name\": \"Didactyl Agent\",\n \"display_name\": \"Didactyl Agent\",\n \"about\": \"A sovereign AI agent on Nostr.\\n\\nMission: Help maintain and answer questions about the Didactyl project.\\n\\nhttps://git.laantungir.net/laantungir/didactyl\\n\\nNot your keys, not your Bitcoin.\\nNot your keys, not your Agent.\\nNot your keys, not your Robot.\",\n \"picture\": \"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\n \"banner\": \"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"\n}\n\nYour npub: npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone\n\n---\n\n## Recent Conversation\n\nAssistant: Didactyl has started up and is online at 2026-03-23 09:15:49 (version v0.2.12, connected relays: 5/5).\nUser: hello"},{"role":"user","content":"hello","_ts":1774271838}]
|
||||
|
||||
---
|
||||
|
||||
```text
|
||||
Context Log - seen by model
|
||||
timestamp=2026-03-23 07:00:39
|
||||
phase=llm_chat_with_tools_messages
|
||||
sender=1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139
|
||||
model=claude-opus-4.6
|
||||
context_bytes=2065
|
||||
approx_tokens=516
|
||||
```
|
||||
|
||||
[{"role":"system","content":"You are {\n \"name\": \"Didactyl Agent\",\n \"display_name\": \"Didactyl Agent\",\n \"about\": \"A sovereign AI agent on Nostr.\\n\\nMission: Help maintain and answer questions about the Didactyl project.\\n\\nhttps://git.laantungir.net/laantungir/didactyl\\n\\nNot your keys, not your Bitcoin.\\nNot your keys, not your Agent.\\nNot your keys, not your Robot.\",\n \"picture\": \"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\n \"banner\": \"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"\n}\n\nYour npub: npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone [{\"role\":\"assistant\",\"content\":\"Didactyl has started up and is online at 2026-03-23 06:58:49 (version v0.2.12, connected relays: 5/5).\",\"created_at\":1774263529},{\"role\":\"user\",\"content\":\"Good morning\",\"created_at\":1774263548},{\"role\":\"assistant\",\"content\":\"Good morning! ☀️ How can I help you today?\",\"created_at\":1774263551},{\"role\":\"user\",\"content\":\"What is the capital of England?\",\"created_at\":1774263616},{\"role\":\"assistant\",\"content\":\"London! 🇬🇧\",\"created_at\":1774263619},{\"role\":\"user\",\"content\":\"How about France?\",\"created_at\":1774263628},{\"role\":\"assistant\",\"content\":\"Paris! 🇫🇷\",\"created_at\":1774263632},{\"role\":\"user\",\"content\":\"And Fiji?\",\"created_at\":1774263639}]"},{"role":"user","content":"And Fiji?","_ts":1774263639}]
|
||||
|
||||
---
|
||||
|
||||
```text
|
||||
Context Log - seen by model
|
||||
timestamp=2026-03-23 07:00:28
|
||||
phase=llm_chat_with_tools_messages
|
||||
sender=1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139
|
||||
model=claude-opus-4.6
|
||||
context_bytes=1918
|
||||
approx_tokens=479
|
||||
```
|
||||
|
||||
[{"role":"system","content":"You are {\n \"name\": \"Didactyl Agent\",\n \"display_name\": \"Didactyl Agent\",\n \"about\": \"A sovereign AI agent on Nostr.\\n\\nMission: Help maintain and answer questions about the Didactyl project.\\n\\nhttps://git.laantungir.net/laantungir/didactyl\\n\\nNot your keys, not your Bitcoin.\\nNot your keys, not your Agent.\\nNot your keys, not your Robot.\",\n \"picture\": \"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\n \"banner\": \"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"\n}\n\nYour npub: npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone [{\"role\":\"assistant\",\"content\":\"Didactyl has started up and is online at 2026-03-23 06:58:49 (version v0.2.12, connected relays: 5/5).\",\"created_at\":1774263529},{\"role\":\"user\",\"content\":\"Good morning\",\"created_at\":1774263548},{\"role\":\"assistant\",\"content\":\"Good morning! ☀️ How can I help you today?\",\"created_at\":1774263551},{\"role\":\"user\",\"content\":\"What is the capital of England?\",\"created_at\":1774263616},{\"role\":\"assistant\",\"content\":\"London! 🇬🇧\",\"created_at\":1774263619},{\"role\":\"user\",\"content\":\"How about France?\",\"created_at\":1774263628}]"},{"role":"user","content":"How about France?","_ts":1774263628}]
|
||||
|
||||
---
|
||||
|
||||
```text
|
||||
Context Log - seen by model
|
||||
timestamp=2026-03-23 07:00:16
|
||||
phase=llm_chat_with_tools_messages
|
||||
sender=1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139
|
||||
model=claude-opus-4.6
|
||||
context_bytes=1768
|
||||
approx_tokens=442
|
||||
```
|
||||
|
||||
[{"role":"system","content":"You are {\n \"name\": \"Didactyl Agent\",\n \"display_name\": \"Didactyl Agent\",\n \"about\": \"A sovereign AI agent on Nostr.\\n\\nMission: Help maintain and answer questions about the Didactyl project.\\n\\nhttps://git.laantungir.net/laantungir/didactyl\\n\\nNot your keys, not your Bitcoin.\\nNot your keys, not your Agent.\\nNot your keys, not your Robot.\",\n \"picture\": \"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\n \"banner\": \"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"\n}\n\nYour npub: npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone [{\"role\":\"assistant\",\"content\":\"Didactyl has started up and is online at 2026-03-23 06:58:49 (version v0.2.12, connected relays: 5/5).\",\"created_at\":1774263529},{\"role\":\"user\",\"content\":\"Good morning\",\"created_at\":1774263548},{\"role\":\"assistant\",\"content\":\"Good morning! ☀️ How can I help you today?\",\"created_at\":1774263551},{\"role\":\"user\",\"content\":\"What is the capital of England?\",\"created_at\":1774263616}]"},{"role":"user","content":"What is the capital of England?","_ts":1774263616}]
|
||||
|
||||
---
|
||||
|
||||
```text
|
||||
Context Log - seen by model
|
||||
timestamp=2026-03-23 06:59:08
|
||||
phase=llm_chat_with_tools_messages
|
||||
sender=1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139
|
||||
model=claude-opus-4.6
|
||||
context_bytes=1541
|
||||
approx_tokens=385
|
||||
```
|
||||
|
||||
[{"role":"system","content":"You are {\n \"name\": \"Didactyl Agent\",\n \"display_name\": \"Didactyl Agent\",\n \"about\": \"A sovereign AI agent on Nostr.\\n\\nMission: Help maintain and answer questions about the Didactyl project.\\n\\nhttps://git.laantungir.net/laantungir/didactyl\\n\\nNot your keys, not your Bitcoin.\\nNot your keys, not your Agent.\\nNot your keys, not your Robot.\",\n \"picture\": \"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\n \"banner\": \"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"\n}\n\nYour npub: npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone [{\"role\":\"assistant\",\"content\":\"Didactyl has started up and is online at 2026-03-23 06:58:49 (version v0.2.12, connected relays: 5/5).\",\"created_at\":1774263529},{\"role\":\"user\",\"content\":\"Good morning\",\"created_at\":1774263548}]"},{"role":"user","content":"Good morning","_ts":1774263548}]
|
||||
|
||||
---
|
||||
|
||||
+15004
-35
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
131350
|
||||
@@ -1,70 +0,0 @@
|
||||
Didactyl - sovereign Nostr agent
|
||||
|
||||
Usage:
|
||||
./didactyl [options]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Show this help text and exit.
|
||||
-v, --version
|
||||
Show version and exit.
|
||||
--config <path>
|
||||
Path to config/genesis JSONC file (default: ./genesis.jsonc).
|
||||
If file is missing, startup falls back to minimal nsec-only mode.
|
||||
--nsec <nsec_or_hex>
|
||||
Private key as nsec1... or 64-char hex; overrides DIDACTYL_NSEC.
|
||||
--admin <npub_or_hex>
|
||||
Administrator pubkey override; accepts npub1... or 64-char hex.
|
||||
--api-port <port>
|
||||
Enable HTTP API and bind to this port (1-65535).
|
||||
--api-bind <addr>
|
||||
Enable HTTP API and bind address (example: 127.0.0.1).
|
||||
--debug <0-5>
|
||||
Log level (0=fatal, 1=error, 2=warn, 3=info, 4=debug, 5=trace).
|
||||
--signer <mode>
|
||||
Signer provider: local | nsigner_unix | nsigner_tcp.
|
||||
Overrides signer.mode in config and DIDACTYL_SIGNER.
|
||||
In nsigner_* modes --nsec/DIDACTYL_NSEC are not required.
|
||||
--signer-socket <name>
|
||||
Abstract socket name for nsigner_unix (without leading @).
|
||||
--signer-role <role>
|
||||
n_signer role selector (default: main).
|
||||
--signer-timeout <ms>
|
||||
Per-call signer timeout in milliseconds (default: 15000).
|
||||
--signer-tcp <host:port>
|
||||
Shorthand for --signer nsigner_tcp with host/port parsed.
|
||||
--dump-schemas
|
||||
Print tool schemas JSON and exit.
|
||||
--test-tool <name> <args_json>
|
||||
Execute one tool call locally and print JSON result, then exit.
|
||||
|
||||
Environment:
|
||||
DIDACTYL_NSEC
|
||||
Fallback private key when --nsec is not provided (local mode only).
|
||||
DIDACTYL_SIGNER
|
||||
Fallback signer mode when --signer is not provided.
|
||||
|
||||
Examples:
|
||||
1) Genesis-based startup
|
||||
./didactyl --config genesis.jsonc
|
||||
|
||||
2) nsec-only startup (no config file)
|
||||
./didactyl --nsec nsec1...
|
||||
|
||||
3) nsec via environment variable
|
||||
DIDACTYL_NSEC=nsec1... ./didactyl
|
||||
|
||||
4) Override API bind/port
|
||||
./didactyl --config genesis.jsonc --api-bind 127.0.0.1 --api-port 8484
|
||||
|
||||
5) Sign with a running n_signer (separate the nsec from the agent)
|
||||
./didactyl --signer nsigner_unix --signer-socket nsigner_hairy_dog --admin npub1...
|
||||
|
||||
6) n_signer via TCP
|
||||
./didactyl --signer-tcp 127.0.0.1:7777 --admin npub1...
|
||||
|
||||
7) Dump tool schemas
|
||||
./didactyl --dump-schemas
|
||||
|
||||
8) Test a tool invocation
|
||||
./didactyl --test-tool config_recall '{"config_name":"llm_config"}'
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
# Signer Providers
|
||||
|
||||
Didactyl can sign Nostr events and perform NIP-04/NIP-44 encryption either with
|
||||
an in-process private key (`local`) or by delegating to a running
|
||||
[`n_signer`](../n_signer) process over one of five transports. In any
|
||||
`nsigner_*` mode the agent process holds **no nsec** — the private key lives
|
||||
only in `n_signer`'s locked memory, and the agent pubkey is discovered at
|
||||
startup via `nostr_signer_get_public_key()`.
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Transport | Holds nsec in agent? | Persistable to genesis? | Installable as systemd? |
|
||||
|------|-----------|----------------------|-------------------------|-------------------------|
|
||||
| `local` | in-process | yes | yes | yes |
|
||||
| `nsigner_unix` | AF_UNIX abstract socket | no | yes | yes |
|
||||
| `nsigner_tcp` | TCP `host:port` | no | yes | yes |
|
||||
| `nsigner_serial` | USB CDC-ACM serial | no | yes | yes |
|
||||
| `nsigner_fds` | pre-connected fd pair | no | **no** (runtime-only) | **no** |
|
||||
| `nsigner_qrexec` | Qubes qrexec (cross-qube) | no | yes | yes |
|
||||
|
||||
### `local`
|
||||
Default, backward-compatible. The 32-byte private key is derived from
|
||||
`keys.nsec` and held in `cfg.keys.private_key`. Use this when you accept the
|
||||
nsec living in the agent process.
|
||||
|
||||
### `nsigner_unix`
|
||||
Delegates to `n_signer` over an abstract namespace Unix socket
|
||||
(`@<socket_name>`). If `socket_name` is empty, the agent auto-discovers running
|
||||
`n_signer` sockets via `nsigner_transport_list_unix()`. Best for same-host
|
||||
deployments where `n_signer` runs in an attended terminal.
|
||||
|
||||
### `nsigner_tcp`
|
||||
Delegates to `n_signer` over TCP. Requires `auth_privkey_hex` (64-char hex) for
|
||||
the kind-27235 auth envelope that `n_signer` enforces on TCP listeners. Use for
|
||||
cross-host or FIPS-mode deployments.
|
||||
|
||||
### `nsigner_serial`
|
||||
Delegates to `n_signer` over a USB CDC-ACM serial device (e.g. `/dev/ttyACM0`).
|
||||
The wizard auto-discovers `/dev/ttyACM*` devices via
|
||||
`nsigner_transport_list_serial()`. Use for air-gapped/hardware-token signing.
|
||||
|
||||
### `nsigner_fds`
|
||||
Delegates to `n_signer` over a pre-connected file-descriptor pair
|
||||
(`read_fd`, `write_fd`). Intended for qrexec-style fd-passing deployments.
|
||||
**Cannot be persisted to genesis** (fds are runtime-only) and **cannot be
|
||||
installed as a systemd service** (fds cannot be embedded in `ExecStart`). Supply
|
||||
via `--signer-fds <read:write>` at boot.
|
||||
|
||||
### `nsigner_qrexec`
|
||||
Delegates to `n_signer` running in **another Qubes OS qube** via qrexec. The
|
||||
agent spawns `qrexec-client-vm <target_qube> <service_name>` as a subprocess
|
||||
and pipes framed JSON-RPC through its stdin/stdout. No network; caller identity
|
||||
is hypervisor-vouched (`QREXEC_REMOTE_DOMAIN` → `qubes:<source-vm>` on the
|
||||
signer side). This is the recommended Qubes cross-qube signing path.
|
||||
|
||||
Prerequisites (in the signer qube, e.g. `nostr_signer`):
|
||||
- `n_signer` running persistently: `nsigner --listen unix --socket-name nsigner --bridge-source-trusted`
|
||||
- The `qubes.NsignerRpc` qrexec service installed (see `n_signer/packaging/qubes/`)
|
||||
- dom0 qrexec policy allowing the agent qube to call the service
|
||||
|
||||
The service name defaults to `qubes.NsignerRpc`; override with `--signer-service`
|
||||
or the `signer.service_name` config field.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Genesis (`signer` block)
|
||||
|
||||
```jsonc
|
||||
"signer": {
|
||||
"mode": "nsigner_unix",
|
||||
"socket_name": "", // nsigner_unix: "" = auto-discover
|
||||
"role": "main",
|
||||
"timeout_ms": 15000,
|
||||
"auth_privkey_hex": "", // nsigner_tcp only
|
||||
"tcp_host": "127.0.0.1", // nsigner_tcp
|
||||
"tcp_port": 11111, // nsigner_tcp
|
||||
"serial_device": "/dev/ttyACM0", // nsigner_serial
|
||||
"target_qube": "nostr_signer", // nsigner_qrexec: target Qubes qube
|
||||
"service_name": "qubes.NsignerRpc" // nsigner_qrexec: qrexec service (default qubes.NsignerRpc)
|
||||
// nsigner_fds: fds_read_fd/fds_write_fd are CLI-only, NOT persisted here
|
||||
}
|
||||
```
|
||||
|
||||
### CLI flags
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `--signer <mode>` | Select signer mode (overrides config/env). |
|
||||
| `--signer-socket <name>` | Abstract socket name for `nsigner_unix`. |
|
||||
| `--signer-role <role>` | `n_signer` role selector (default `main`). |
|
||||
| `--signer-timeout <ms>` | Per-call timeout (default `15000`). |
|
||||
| `--signer-tcp <host:port>` | Shorthand for `--signer nsigner_tcp` + host/port. |
|
||||
| `--signer-serial <device>` | Shorthand for `--signer nsigner_serial` + device. |
|
||||
| `--signer-fds <read:write>` | Shorthand for `--signer nsigner_fds` + fd pair. |
|
||||
| `--signer-qrexec <target_qube>` | Shorthand for `--signer nsigner_qrexec` + target qube. |
|
||||
| `--signer-service <name>` | qrexec service name (default `qubes.NsignerRpc`). |
|
||||
|
||||
Precedence: CLI flag > `DIDACTYL_SIGNER` env var > genesis `signer.mode` >
|
||||
default (`local`).
|
||||
|
||||
In any `nsigner_*` mode, `--nsec` / `DIDACTYL_NSEC` are not required.
|
||||
|
||||
## Interactive setup wizard
|
||||
|
||||
The wizard's **New agent → sign with a running n_signer** path and the
|
||||
**Existing agent** flow both present a transport picker:
|
||||
|
||||
- `u` nix socket (auto-discovers `@nsigner*` abstract sockets)
|
||||
- `t` cp (prompts for `host:port` + optional auth privkey)
|
||||
- `s` erial (auto-discovers `/dev/ttyACM*`)
|
||||
- `f` d pair (prompts for `read_fd:write_fd` — advanced)
|
||||
- `e` xec qrexec (prompts for target qube + service name — Qubes cross-qube)
|
||||
|
||||
After a transport is chosen, the wizard runs a connectivity check
|
||||
(`nostr_signer_get_public_key`) and populates the agent pubkey before
|
||||
continuing.
|
||||
|
||||
The **Existing agent** flow additionally lets you recover an agent **without
|
||||
entering an nsec** — kind-30078 config recall/publish is routed through the
|
||||
signer verbs (`nostr_signer_nip44_decrypt` / `nostr_signer_nip44_encrypt`), so
|
||||
the nsec never enters the agent process during recovery.
|
||||
|
||||
## Systemd install
|
||||
|
||||
`install_system_service_with_dedicated_user()` embeds the signer flags in
|
||||
`ExecStart` for `nsigner_unix`, `nsigner_tcp`, `nsigner_serial`, and
|
||||
`nsigner_qrexec`.
|
||||
`nsigner_fds` is rejected with a clear message (fds are runtime-only and cannot
|
||||
be embedded in `ExecStart`); choose **boot now** or wire fd-passing yourself
|
||||
via a wrapper unit.
|
||||
|
||||
## Security tradeoff
|
||||
|
||||
`local` keeps the nsec in the agent process. If the agent is compromised, the
|
||||
nsec is exposed. Remote (`nsigner_*`) modes keep the nsec in `n_signer`'s
|
||||
mlock'd memory; a compromised agent can request signatures but cannot extract
|
||||
the raw key. The tradeoff is operational: `n_signer` must be running and
|
||||
reachable when the agent needs to sign.
|
||||
@@ -1 +0,0 @@
|
||||
1
|
||||
+7
-30
@@ -14,24 +14,9 @@
|
||||
"pubkey": "npub1REPLACE_WITH_ADMIN_PUBKEY"
|
||||
},
|
||||
|
||||
// ─── Signer Provider (optional) ────────────────────────────────────
|
||||
// Omit this block (or set mode="local") to use the nsec above in-process.
|
||||
// In any nsigner_* mode the nsec is optional/ignored; the agent pubkey is
|
||||
// learned from the signer via nostr_signer_get_public_key(). See docs/SIGNER.md.
|
||||
//
|
||||
// "signer": {
|
||||
// "mode": "nsigner_unix", // local | nsigner_unix | nsigner_tcp | nsigner_serial | nsigner_fds | nsigner_qrexec
|
||||
// "socket_name": "", // nsigner_unix: abstract socket name (without @); "" = auto-discover
|
||||
// "role": "main", // n_signer role selector
|
||||
// "timeout_ms": 15000, // per-call timeout
|
||||
// "auth_privkey_hex": "", // nsigner_tcp only: kind-27235 auth envelope privkey
|
||||
// "tcp_host": "127.0.0.1", // nsigner_tcp host
|
||||
// "tcp_port": 11111, // nsigner_tcp port
|
||||
// "serial_device": "/dev/ttyACM0", // nsigner_serial device path
|
||||
// "target_qube": "nostr_signer", // nsigner_qrexec: target Qubes qube name
|
||||
// "service_name": "qubes.NsignerRpc" // nsigner_qrexec: qrexec service name (default qubes.NsignerRpc)
|
||||
// // nsigner_fds: fds_read_fd/fds_write_fd are CLI-only (--signer-fds), NOT persisted here
|
||||
// },
|
||||
// ─── DM Protocol ───────────────────────────────────────────────────
|
||||
// Supported values: "nip04", "nip17", or "both".
|
||||
"dm_protocol": "nip04",
|
||||
|
||||
// ─── Encrypted Startup Config Events ───────────────────────────────
|
||||
// These are published as NIP-44 encrypted kind 30078 self-events on first run.
|
||||
@@ -39,7 +24,7 @@
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"openai\",\"api_key\":\"sk-REPLACE_WITH_API_KEY\",\"model\":\"gpt-4o-mini\",\"base_url\":\"https://api.openai.com/v1\",\"max_tokens\":512,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_ADMIN_PUBKEY\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"openai\",\"api_key\":\"sk-REPLACE_WITH_API_KEY\",\"model\":\"gpt-4o-mini\",\"base_url\":\"https://api.openai.com/v1\",\"max_tokens\":512,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_ADMIN_PUBKEY\",\"dm_protocol\":\"nip04\"}}"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -75,14 +60,6 @@
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 10050,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["relay", "wss://relay.damus.io"],
|
||||
["relay", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Didactyl Agent\n\nYou are {{my_kind0_profile}}\n\nYour npub: {{my_npub}}\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\n- After a tool call, base your answer on the actual tool result\n- Never claim a tool was run if no tool was executed\n- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone",
|
||||
@@ -97,12 +74,12 @@
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "## DM History Context\n\n### Instructions\n- Reference prior conversation naturally when it's relevant to the current request.\n- Do not repeat entire DM history back to the user unless explicitly asked.\n- Use this context to avoid asking questions that were already answered in recent messages.\n\n### Recent DM History (last 10 messages)\n\n{{nostr_dm_history({\"limit\":10,\"format\":\"text\"})}}",
|
||||
"content": "## Recent Conversation\n\n{{nostr_dm_history({\"format\":\"text\",\"limit\":12})}}",
|
||||
"tags": [
|
||||
["d", "dm_history_context"],
|
||||
["d", "dm_history"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["description", "DM history context"],
|
||||
["description", "DM conversation history for context continuity"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
{
|
||||
// ─── Agent Identity Keys ───────────────────────────────────────────
|
||||
// Keep nsec private; npub/hex fields can be used for verification/debugging.
|
||||
"key": {
|
||||
"nsec": "nsec17pcmmqxh99rtu5ctpxrdyywdd8f7zfqgf3y2f4fvcm3tk3vpuyqqvlxl4e"
|
||||
},
|
||||
|
||||
// ─── Administrator ─────────────────────────────────────────────────
|
||||
// Admin pubkey (npub or hex) controls privileged interactions.
|
||||
"admin": {
|
||||
"pubkey": "npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06"
|
||||
},
|
||||
|
||||
// ─── HTTP Admin API ───────────────────────────────────────────────
|
||||
// Local API for model/context inspection and runtime controls.
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8484,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
|
||||
// ─── Encrypted Startup Config Events ──────────────────────────────
|
||||
// These are published as NIP-44 encrypted kind 30078 self-events on first run.
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"ppq\",\"api_key\":\"sk-LshAWvFC0KOFgrUYiP6NmT\",\"model\":\"claude-opus-4.6\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":200000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
|
||||
// ─── Startup Events ───────────────────────────────────────────────
|
||||
// Minimal relay list required for relay pool initialization.
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"],
|
||||
["r", "wss://relay.laantungir.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 10050,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["relay", "wss://relay.damus.io"],
|
||||
["relay", "wss://relay.primal.net"],
|
||||
["relay", "wss://relay.laantungir.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "## DM History Context\n\n### Instructions\n- Reference prior conversation naturally when it's relevant to the current request.\n- Do not repeat entire DM history back to the user unless explicitly asked.\n- Use this context to avoid asking questions that were already answered in recent messages.\n\n### Recent DM History (last 10 messages)\n\n{{nostr_dm_history({\"limit\":10,\"format\":\"text\"})}}",
|
||||
"tags": [
|
||||
["d", "dm_history_context"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["description", "DM history context"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `local_shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Task Management\n- Maintain and use your internal task list as short-term working memory.\n- Break long or complex actions into clear tasks before executing them.\n- Update task status as you complete steps so your plan stays accurate.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.\n\n---template---\n\n- section: admin_identity\n role: system\n tool: admin_identity\n skip_if_empty: true\n\n- section: admin_profile\n role: system\n tool: nostr_admin_profile\n skip_if_empty: true\n\n- section: admin_contacts\n role: system\n tool: nostr_admin_contacts\n skip_if_empty: true\n\n- section: admin_relays\n role: system\n tool: nostr_admin_relays\n skip_if_empty: true\n\n- section: admin_notes\n role: system\n tool: nostr_admin_notes\n skip_if_empty: true\n\n- section: agent_identity\n role: system\n tool: agent_identity\n skip_if_empty: true\n\n- section: agent_profile\n role: system\n tool: nostr_agent_profile\n skip_if_empty: true\n\n- section: agent_contacts\n role: system\n tool: nostr_agent_contacts\n skip_if_empty: true\n\n- section: agent_relays\n role: system\n tool: nostr_agent_relays\n skip_if_empty: true\n\n- section: agent_notes\n role: system\n tool: nostr_agent_notes\n skip_if_empty: true\n\n- section: tasks\n role: system\n tool: task_list\n skip_if_empty: true\n\n- section: dm_history\n role: expand\n limit: 12\n\n- section: conversation\n role: user\n tool: message_current\n skip_if_empty: true",
|
||||
"tags": [
|
||||
[
|
||||
"d",
|
||||
"didactyl-default"
|
||||
],
|
||||
[
|
||||
"app",
|
||||
"didactyl"
|
||||
],
|
||||
[
|
||||
"scope",
|
||||
"private"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
// ─── Agent Identity Keys ───────────────────────────────────────────
|
||||
// Keep nsec private; npub/hex fields can be used for verification/debugging.
|
||||
"key": {
|
||||
"nsec": "nsec17yzm0lyr479z73sgnpf9yuxv0nwclpaff0k45npsh49nddpc3fhqmhh2ph"
|
||||
},
|
||||
// ─── Administrator ─────────────────────────────────────────────────
|
||||
// Admin pubkey (npub or hex) controls privileged interactions.
|
||||
"admin": {
|
||||
"pubkey": "npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06"
|
||||
},
|
||||
// ─── HTTP Admin API ───────────────────────────────────────────────
|
||||
// Local API for model/context inspection and runtime controls.
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
// ─── Encrypted Startup Config Events ──────────────────────────────
|
||||
// These are published as NIP-44 encrypted kind 30078 self-events on first run.
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"ppq\",\"api_key\":\"sk-L9kd0LkZaoeaua0qqosnHy\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":512,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
// ─── Startup Events ───────────────────────────────────────────────
|
||||
// Minimal relay list required for relay pool initialization.
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
[
|
||||
"r",
|
||||
"wss://relay.damus.io"
|
||||
],
|
||||
[
|
||||
"r",
|
||||
"wss://relay.primal.net"
|
||||
],
|
||||
[
|
||||
"r",
|
||||
"wss://relay.laantungir.net"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "## DM History Context\n\n### Instructions\n- Reference prior conversation naturally when it's relevant to the current request.\n- Do not repeat entire DM history back to the user unless explicitly asked.\n- Use this context to avoid asking questions that were already answered in recent messages.\n\n### Recent DM History (last 10 messages)\n\n{{nostr_dm_history({\"limit\":10,\"format\":\"text\"})}}",
|
||||
"tags": [
|
||||
[
|
||||
"d",
|
||||
"dm_history_context"
|
||||
],
|
||||
[
|
||||
"app",
|
||||
"didactyl"
|
||||
],
|
||||
[
|
||||
"scope",
|
||||
"private"
|
||||
],
|
||||
[
|
||||
"description",
|
||||
"DM history context"
|
||||
],
|
||||
[
|
||||
"trigger",
|
||||
"dm"
|
||||
],
|
||||
[
|
||||
"filter",
|
||||
"{\"from\":\"admin\"}"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `local_shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Task Management\n- Maintain and use your internal task list as short-term working memory.\n- Break long or complex actions into clear tasks before executing them.\n- Update task status as you complete steps so your plan stays accurate.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.\n\n---template---\n\n- section: admin_identity\n role: system\n tool: admin_identity\n skip_if_empty: true\n\n- section: admin_profile\n role: system\n tool: nostr_admin_profile\n skip_if_empty: true\n\n- section: admin_contacts\n role: system\n tool: nostr_admin_contacts\n skip_if_empty: true\n\n- section: admin_relays\n role: system\n tool: nostr_admin_relays\n skip_if_empty: true\n\n- section: admin_notes\n role: system\n tool: nostr_admin_notes\n skip_if_empty: true\n\n- section: agent_identity\n role: system\n tool: agent_identity\n skip_if_empty: true\n\n- section: agent_profile\n role: system\n tool: nostr_agent_profile\n skip_if_empty: true\n\n- section: agent_contacts\n role: system\n tool: nostr_agent_contacts\n skip_if_empty: true\n\n- section: agent_relays\n role: system\n tool: nostr_agent_relays\n skip_if_empty: true\n\n- section: agent_notes\n role: system\n tool: nostr_agent_notes\n skip_if_empty: true\n\n- section: tasks\n role: system\n tool: task_list\n skip_if_empty: true\n\n- section: dm_history\n role: expand\n limit: 12\n\n- section: conversation\n role: user\n tool: message_current\n skip_if_empty: true",
|
||||
"tags": [
|
||||
[
|
||||
"d",
|
||||
"didactyl-default"
|
||||
],
|
||||
[
|
||||
"app",
|
||||
"didactyl"
|
||||
],
|
||||
[
|
||||
"scope",
|
||||
"private"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
Didactyl - sovereign Nostr agent
|
||||
|
||||
Usage:
|
||||
./didactyl [options]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Show this help text and exit.
|
||||
-v, --version
|
||||
Show version and exit.
|
||||
--config <path>
|
||||
Path to config/genesis JSONC file (default: ./genesis.jsonc).
|
||||
If file is missing, startup falls back to minimal nsec-only mode.
|
||||
--nsec <nsec_or_hex>
|
||||
Private key as nsec1... or 64-char hex; overrides DIDACTYL_NSEC.
|
||||
--admin <npub_or_hex>
|
||||
Administrator pubkey override; accepts npub1... or 64-char hex.
|
||||
--api-port <port>
|
||||
Enable HTTP API and bind to this port (1-65535).
|
||||
--api-bind <addr>
|
||||
Enable HTTP API and bind address (example: 127.0.0.1).
|
||||
--debug <0-5>
|
||||
Log level (0=fatal, 1=error, 2=warn, 3=info, 4=debug, 5=trace).
|
||||
--signer <mode>
|
||||
Signer provider: local | nsigner_unix | nsigner_tcp.
|
||||
Overrides signer.mode in config and DIDACTYL_SIGNER.
|
||||
In nsigner_* modes --nsec/DIDACTYL_NSEC are not required.
|
||||
--signer-socket <name>
|
||||
Abstract socket name for nsigner_unix (without leading @).
|
||||
--signer-role <role>
|
||||
n_signer role selector (default: main).
|
||||
--signer-timeout <ms>
|
||||
Per-call signer timeout in milliseconds (default: 15000).
|
||||
--signer-tcp <host:port>
|
||||
Shorthand for --signer nsigner_tcp with host/port parsed.
|
||||
--dump-schemas
|
||||
Print tool schemas JSON and exit.
|
||||
--test-tool <name> <args_json>
|
||||
Execute one tool call locally and print JSON result, then exit.
|
||||
|
||||
Environment:
|
||||
DIDACTYL_NSEC
|
||||
Fallback private key when --nsec is not provided (local mode only).
|
||||
DIDACTYL_SIGNER
|
||||
Fallback signer mode when --signer is not provided.
|
||||
|
||||
Examples:
|
||||
1) Genesis-based startup
|
||||
./didactyl --config genesis.jsonc
|
||||
|
||||
2) nsec-only startup (no config file)
|
||||
./didactyl --nsec nsec1...
|
||||
|
||||
3) nsec via environment variable
|
||||
DIDACTYL_NSEC=nsec1... ./didactyl
|
||||
|
||||
4) Override API bind/port
|
||||
./didactyl --config genesis.jsonc --api-bind 127.0.0.1 --api-port 8484
|
||||
|
||||
5) Sign with a running n_signer (separate the nsec from the agent)
|
||||
./didactyl --signer nsigner_unix --signer-socket nsigner_hairy_dog --admin npub1...
|
||||
|
||||
6) n_signer via TCP
|
||||
./didactyl --signer-tcp 127.0.0.1:7777 --admin npub1...
|
||||
|
||||
7) Dump tool schemas
|
||||
./didactyl --dump-schemas
|
||||
|
||||
8) Test a tool invocation
|
||||
./didactyl --test-tool config_recall '{"config_name":"llm_config"}'
|
||||
-668
@@ -1,668 +0,0 @@
|
||||
nsigner v0.0.33
|
||||
Seed phrase is valid and accepted.
|
||||
System is ready and waiting for connections on @didactyl_test_signer.
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
(none)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
(none)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120609 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120609 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120623 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120609 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120623 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120609 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120741 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120623 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120609 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
[1mRoles[0m
|
||||
Role Purpose Curve Selector
|
||||
-------------------- ------------ ------------ ------------
|
||||
main nostr secp256k1 nostr_index
|
||||
|
||||
[1mActivity (latest first)[0m
|
||||
0708-120741 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120623 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120609 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120554 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
0708-120532 uid:1000 get_public_key(main) ALLOWED:prompt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[24A
|
||||
================================================================================
|
||||
n_signer v0.0.33
|
||||
================================================================================
|
||||
> Main Menu
|
||||
|
||||
|
||||
[1mConnections[0m
|
||||
listen: unix @didactyl_test_signer
|
||||
client: nsigner --socket-name didactyl_test_signer client '<json>'
|
||||
fips ipv6: fdb8:36ad:c97a:44a9:e70e:e591:5d53:8f28
|
||||
fips npub: npub10vt4scusw6lq27qw83nfwp5sqer492h0tnwa8ugqjvp6l4xuz2qsdycrwd
|
||||
|
||||
[4mq[0m/x quit
|
||||
[4ml[0m lock/reunlock
|
||||
[4mr[0m refresh
|
||||
[4mA[0m toggle auto-approve
|
||||
|
||||
session=unlocked (12 words) signer=didactyl_test_signer derived=1 auto-approve=ON
|
||||
@@ -1 +0,0 @@
|
||||
131270
|
||||
@@ -1,247 +0,0 @@
|
||||
# Cron Trigger Pipeline: How It Works and How to Make It Robust
|
||||
|
||||
## Current Architecture: Skill Events → Trigger Registration
|
||||
|
||||
The pipeline has **two paths** for getting a cron trigger loaded, plus a **polling loop** that actually fires them.
|
||||
|
||||
### Path 1: Bulk load at startup (EOSE-driven)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Main as main.c startup
|
||||
participant NH as nostr_handler
|
||||
participant Relays as Nostr Relays
|
||||
participant TM as trigger_manager
|
||||
|
||||
Main->>TM: trigger_manager_init - capacity=16, last_poll_at=0
|
||||
Main->>TM: trigger_manager_load_from_startup_events
|
||||
Note over TM: Loads triggers from genesis startup_events<br/>only dm triggers typically here
|
||||
Main->>NH: nostr_handler_set_self_skill_eose_callback<br/>registers on_self_skill_eose_load_triggers
|
||||
Main->>NH: nostr_handler_subscribe_self_skills
|
||||
NH->>Relays: REQ kinds 31123, 31124, 10123<br/>authors = agent pubkey
|
||||
Relays-->>NH: EVENT skill events arrive one by one
|
||||
NH->>NH: on_self_skill_event for each<br/>caches in g_self_skill_events<br/>calls register_trigger_from_self_skill_event
|
||||
Relays-->>NH: EOSE
|
||||
NH->>NH: on_self_skill_eose sets g_self_skill_eose_received=1
|
||||
NH->>Main: calls on_self_skill_eose_load_triggers callback
|
||||
Main->>TM: trigger_manager_load_from_skills<br/>reads ALL cached skills via<br/>nostr_handler_get_self_events_by_kind_json
|
||||
Note over TM: For each skill with trigger+filter tags:<br/>calls trigger_manager_add
|
||||
Main->>Main: nostr_handler_wait_for_self_skill_eose returns 0
|
||||
Main->>Main: Startup step 14 passes
|
||||
Main->>Main: Enter main poll loop
|
||||
```
|
||||
|
||||
### Path 2: Live skill event (post-startup)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Relays as Nostr Relays
|
||||
participant NH as nostr_handler
|
||||
participant TM as trigger_manager
|
||||
|
||||
Note over NH: Subscription stays open after EOSE
|
||||
Relays-->>NH: New/updated skill EVENT arrives
|
||||
NH->>NH: on_self_skill_event
|
||||
NH->>NH: register_trigger_from_self_skill_event
|
||||
Note over NH: Checks: kind 31123/31124?<br/>Has trigger+filter tags?<br/>Is adopted via kind 10123?
|
||||
NH->>TM: trigger_manager_add or update
|
||||
NH->>NH: self_skill_cache_upsert_event_locked
|
||||
```
|
||||
|
||||
### Path 3: Cron polling in main loop
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Main as main loop
|
||||
participant TM as trigger_manager
|
||||
participant Agent as agent.c
|
||||
|
||||
loop Every iteration
|
||||
Main->>TM: trigger_manager_poll
|
||||
Note over TM: Throttled to once per 30 seconds
|
||||
TM->>TM: For each trigger where type==CRON:<br/>1. Validate cron_expr<br/>2. Check cron_matches_now<br/>3. Check last_cron_fire dedup - 50s guard
|
||||
TM->>Agent: execute_llm_action with synthetic cron event
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Six Failure Points
|
||||
|
||||
I have identified six places where a cron trigger can silently fail to load or fire. They are ordered from most likely to least likely given the logs from laantungir.net.
|
||||
|
||||
### 1. EOSE timeout kills startup entirely
|
||||
|
||||
**Where:** [`main.c:1494–1515`](src/main.c:1494)
|
||||
|
||||
The agent waits 15 seconds for self-skill EOSE. If relays are slow or the connection is flaky, this times out and **the entire agent process exits**:
|
||||
|
||||
```c
|
||||
const int self_skill_eose_timeout_ms = 15000;
|
||||
if (nostr_handler_wait_for_self_skill_eose(self_skill_eose_timeout_ms) != 0) {
|
||||
// ... prints error, cleans up, return 1
|
||||
}
|
||||
```
|
||||
|
||||
From [`debug.log`](debug.log) line 670:
|
||||
```
|
||||
[2026-04-06 16:16:49] [ERROR] startup checklist [14] Subscribe self-skill cache: failed
|
||||
(self-skill EOSE not received within timeout)
|
||||
```
|
||||
|
||||
**Impact:** If the agent crashes at startup, no triggers load at all. If systemd restarts it and it crashes again, you get a restart loop with zero cron execution.
|
||||
|
||||
### 2. Adoption check gates live trigger registration
|
||||
|
||||
**Where:** [`nostr_handler.c:1462`](src/nostr_handler.c:1462)
|
||||
|
||||
When a skill event arrives live via [`on_self_skill_event()`](src/nostr_handler.c:2428), it calls [`register_trigger_from_self_skill_event()`](src/nostr_handler.c:1418) which checks:
|
||||
|
||||
```c
|
||||
if (!self_skill_is_adopted_local(kind_val, pubkey->valuestring, d_tag)) {
|
||||
DEBUG_INFO("live self-skill trigger ignored (not adopted) d_tag=%s", d_tag);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
[`self_skill_is_adopted_local()`](src/nostr_handler.c:1366) scans `g_self_skill_events` for a kind 10123 event with an `["a", "31123:<pubkey>:<d_tag>"]` tag. If the adoption event hasn't arrived yet (race condition — skill event arrives before adoption event), the trigger is silently skipped.
|
||||
|
||||
**Critically:** The bulk load path in [`trigger_manager_load_from_skills()`](src/trigger_manager.c:818) does **NOT** check adoption at all — it loads every skill with trigger+filter tags. So the adoption gate only affects the live path.
|
||||
|
||||
### 3. Bulk load is one-shot with `loaded_once` guard
|
||||
|
||||
**Where:** [`main.c:42`](src/main.c:42)
|
||||
|
||||
```c
|
||||
if (ctx->loaded_once) {
|
||||
DEBUG_TRACE("deferred trigger load skipped (already loaded once)");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
The EOSE callback fires once, calls [`trigger_manager_load_from_skills()`](src/trigger_manager.c:818), sets `loaded_once = 1`, and never runs again. If a skill event arrives **after** EOSE but **before** the live path processes it, it falls into a gap:
|
||||
- The bulk load already ran and won't run again
|
||||
- The live [`register_trigger_from_self_skill_event()`](src/nostr_handler.c:1418) may reject it due to the adoption check
|
||||
|
||||
### 4. Cron expression validation rejects silently at add time
|
||||
|
||||
**Where:** [`trigger_manager.c:1106–1112`](src/trigger_manager.c:1106)
|
||||
|
||||
```c
|
||||
if (t->trigger_type == TRIGGER_TYPE_CRON) {
|
||||
if (!cron_expr_is_valid(filter_json, normalized_cron, sizeof(normalized_cron))) {
|
||||
DEBUG_WARN("trigger add rejected: invalid cron expression d_tag=%s expr=%s", ...);
|
||||
memset(t, 0, sizeof(*t));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This does log a warning, but the caller in [`trigger_manager_load_from_skills()`](src/trigger_manager.c:896) just silently skips to the next skill. The `considered` count increments but `loaded` does not — the only evidence is in the log line `trigger manager loaded X trigger(s) from self skills (considered=Y)` where X < Y.
|
||||
|
||||
The existing plan in [`fix_cron_triggers_not_firing.md`](plans/fix_cron_triggers_not_firing.md) documents that a bare `*` was being used instead of `* * * * *`, and that shorthand expansion was added. This has been fixed.
|
||||
|
||||
### 5. Poll throttle delays first evaluation by up to 30 seconds
|
||||
|
||||
**Where:** [`trigger_manager.c:1395`](src/trigger_manager.c:1395)
|
||||
|
||||
```c
|
||||
if (mgr->last_poll_at > 0 && (now - mgr->last_poll_at) < 30) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
After init, `last_poll_at` starts at 0 (fixed from the original bug where it was `time(NULL)`), so the first poll runs immediately. But subsequent polls are throttled to every 30 seconds. For a daily cron this is fine, but it means the cron match window is checked at most twice per minute.
|
||||
|
||||
### 6. Cron dedup guard can skip legitimate fires
|
||||
|
||||
**Where:** [`trigger_manager.c:1422`](src/trigger_manager.c:1422)
|
||||
|
||||
```c
|
||||
if (t->last_cron_fire > 0 && (now - t->last_cron_fire) < 50) {
|
||||
continue;
|
||||
}
|
||||
```
|
||||
|
||||
With a 30-second poll interval and a 50-second dedup window, a cron that matches at minute X will fire once and then be suppressed for the next poll. This is correct behavior for preventing double-fires within the same minute. But if the system clock jumps or NTP adjusts, this could cause unexpected skips.
|
||||
|
||||
---
|
||||
|
||||
## Robustness Suggestions
|
||||
|
||||
### A. Retry EOSE with backoff instead of hard abort
|
||||
|
||||
**Current:** EOSE timeout → process exit.
|
||||
**Proposed:** Retry 2–3 times with increasing timeout (15s, 30s, 60s). If all retries fail, start the agent in degraded mode with only startup-config triggers active, and schedule a background re-attempt.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
SUB[Subscribe self-skills] --> WAIT{Wait for EOSE}
|
||||
WAIT -->|Received| LOAD[Load triggers from skills]
|
||||
WAIT -->|Timeout 15s| RETRY1{Retry 1 - 30s timeout}
|
||||
RETRY1 -->|Received| LOAD
|
||||
RETRY1 -->|Timeout| RETRY2{Retry 2 - 60s timeout}
|
||||
RETRY2 -->|Received| LOAD
|
||||
RETRY2 -->|Timeout| DEGRADED[Start in degraded mode<br/>startup triggers only<br/>schedule background retry]
|
||||
LOAD --> READY[Agent READY]
|
||||
DEGRADED --> READY
|
||||
```
|
||||
|
||||
### B. Remove adoption gate from live trigger registration
|
||||
|
||||
The bulk load path doesn't check adoption. The live path does. This inconsistency means a skill created via `skill_create` with `auto_adopt=true` might not register its trigger if the adoption event arrives on a different relay or with different timing.
|
||||
|
||||
**Proposed:** Either:
|
||||
1. Remove the adoption check from [`register_trigger_from_self_skill_event()`](src/nostr_handler.c:1462) to match the bulk path, OR
|
||||
2. Add the adoption check to the bulk path too for consistency, but with a fallback: if adoption events haven't loaded yet, defer the check.
|
||||
|
||||
Option 1 is simpler and more robust. The agent already only subscribes to its own pubkey's events, so all skills in the cache are self-authored.
|
||||
|
||||
### C. Allow re-running bulk trigger load
|
||||
|
||||
Remove the `loaded_once` guard or replace it with a debounced re-load. When new skill events arrive after EOSE, schedule a re-scan of all cached skills after a short delay (e.g., 5 seconds). This closes the gap between EOSE and late-arriving events.
|
||||
|
||||
### D. Add a periodic trigger reconciliation
|
||||
|
||||
Every N minutes (e.g., 5), compare the set of cached skills that have trigger+filter tags against the active trigger list. Register any missing triggers. This is a safety net that catches any trigger that was missed during startup.
|
||||
|
||||
```c
|
||||
// In trigger_manager_poll, after cron evaluation:
|
||||
if (now - mgr->last_reconcile_at > 300) {
|
||||
trigger_manager_load_from_skills(mgr); // idempotent via update path
|
||||
mgr->last_reconcile_at = now;
|
||||
}
|
||||
```
|
||||
|
||||
### E. Log trigger registration summary at startup
|
||||
|
||||
After the deferred trigger load, log not just the count but also which trigger types were loaded:
|
||||
|
||||
```
|
||||
trigger manager loaded 3 trigger(s): dm=2 cron=1 webhook=0 chain=0 nostr-sub=0
|
||||
```
|
||||
|
||||
This makes it immediately obvious from the log whether a cron trigger was picked up.
|
||||
|
||||
### F. Add an HTTP API endpoint for trigger status
|
||||
|
||||
Expose `/api/triggers` that returns the same JSON as the `trigger_list` tool. This allows external monitoring (e.g., a health check script) to verify that expected cron triggers are loaded without going through the LLM.
|
||||
|
||||
---
|
||||
|
||||
## Summary of What Happened on laantungir.net
|
||||
|
||||
Based on the logs:
|
||||
|
||||
1. The agent started successfully multiple times (startup step 14 passed with `skills=3 adoptions=1` or similar).
|
||||
2. The deferred trigger load status shows **only dm-type triggers** were loaded — no cron triggers appear in any log entry.
|
||||
3. On 2026-04-06, the agent failed at startup step 14 with EOSE timeout, meaning it never even got to trigger loading.
|
||||
4. There is **zero** evidence of `cron trigger matched` or `cron trigger skipped` in any log file, confirming the cron trigger was never in the active trigger set.
|
||||
|
||||
The most likely explanation: the cron skill event exists on relays but either:
|
||||
- It was missing the `["trigger", "cron"]` and/or `["filter", "<expr>"]` tags when it was published
|
||||
- It was not adopted (no kind 10123 event referencing it) and arrived via the live path where adoption is checked
|
||||
- The cron expression was invalid and was silently rejected at add time
|
||||
- The EOSE timeout prevented the agent from ever reaching the trigger loading phase
|
||||
|
||||
Running `trigger_list` on the live server will definitively answer which of these is the case.
|
||||
@@ -1,148 +0,0 @@
|
||||
# Fix: Self-Skill Startup Race (step 14 false-negative)
|
||||
|
||||
## Symptom
|
||||
|
||||
`didactyl.service` / `simon.service` intermittently fail startup at step 14:
|
||||
|
||||
```
|
||||
startup checklist [14] Subscribe self-skill cache: failed (no skill events found after EOSE)
|
||||
```
|
||||
|
||||
…even though the required events (kind 31123/31124 skills and kind 10123 adoption list)
|
||||
demonstrably exist on `relay.laantungir.net` (verified with `nak`, returned in well under
|
||||
a second). On other restarts the identical setup succeeds: `ok (skills=3 adoptions=1)`.
|
||||
The failure is timing-dependent.
|
||||
|
||||
## How startup relay polling works (mechanics)
|
||||
|
||||
- Each startup step opens its **own** subscription (its own `REQ` + filter). Step 14's
|
||||
filter is built in [`nostr_handler_subscribe_self_skills()`](src/nostr_handler.c:2800):
|
||||
`{"kinds":[31123,31124,10123],"authors":[<agent_pubkey>],"limit":300}`.
|
||||
- That subscription is sent to **all** configured relays in parallel inside
|
||||
[`nostr_relay_pool_subscribe()`](nostr_core_lib/nostr_core/core_relay_pool.c:967).
|
||||
- This is the **async/streaming** path: events arrive over time via
|
||||
[`on_self_skill_event()`](src/nostr_handler.c:2478) which upserts into the shared cache
|
||||
`g_self_skill_events`, and an EOSE-complete callback
|
||||
([`on_self_skill_eose`](src/nostr_handler.c:2066)) flips `g_self_skill_eose_received`.
|
||||
- Step 14 then immediately runs
|
||||
[`nostr_handler_validate_self_skill_cache()`](src/nostr_handler.c:3089) against whatever
|
||||
is in the cache at that instant.
|
||||
|
||||
## Root cause
|
||||
|
||||
The async EOSE-complete signal is decoupled from "did the relay that actually has the
|
||||
events get queried and deliver yet." Two independent contributing factors:
|
||||
|
||||
1. **Relay connection timing.** The self-skill subscription is registered shortly after
|
||||
startup. If `relay.laantungir.net` is not yet fully connected when the subscription's
|
||||
completion is computed, the empty relays (`damus`, `primal`) EOSE almost instantly and
|
||||
the per-relay **timeout fallback**
|
||||
([`core_relay_pool.c:2128`](nostr_core_lib/nostr_core/core_relay_pool.c:2128)) marks the
|
||||
remaining relay "done" after `relay_timeout_seconds` (=12) without ever receiving its
|
||||
events. EOSE-complete fires with an empty cache → validation fails.
|
||||
2. **Validation fires the instant completion is signaled**, with no re-check, so any race
|
||||
where laantungir's events land microseconds later is lost.
|
||||
|
||||
This matches the `<1s` `nak` observation: when laantungir is connected and queried, it
|
||||
answers almost instantly; the failures are the cycles where it wasn't
|
||||
connected/queried/processed before the async completion fired.
|
||||
|
||||
### Why FIRST → FULL_SET alone does NOT fix it
|
||||
|
||||
`maybe_complete_subscription_eose()` waits for **all** relays in both modes; the only
|
||||
difference is the payload passed to the callback. The cache is filled by the EVENT
|
||||
callback, not the EOSE payload. Changing the mode changes neither connection timing nor
|
||||
when events are available.
|
||||
|
||||
## Fix: use the synchronous query for the startup self-skill load
|
||||
|
||||
There is already a correct, synchronous "collect all events until EOSE/timeout, then
|
||||
return them" primitive used elsewhere at startup (e.g. kind-10002 discovery):
|
||||
|
||||
- [`nostr_handler_query_json()`](src/nostr_handler.c:3395) →
|
||||
[`nostr_relay_pool_query_sync()`](nostr_core_lib/nostr_core/core_relay_pool.c:1340).
|
||||
- A self-skill-specific wrapper already exists and reuses it:
|
||||
[`nostr_handler_refresh_self_skill_cache_from_relays()`](src/nostr_handler.c:3751),
|
||||
which queries kinds 31123/31124/10123 for the agent author (and also pulls adopted
|
||||
skills), populating the cache synchronously.
|
||||
|
||||
The synchronous query is the architecturally correct tool for "give me the complete
|
||||
current set before proceeding," which is exactly what step 14 needs. It also drives the
|
||||
poll/receive loop itself
|
||||
([`core_relay_pool.c:1405`](nostr_core_lib/nostr_core/core_relay_pool.c:1405)) until all
|
||||
connected relays EOSE or the timeout elapses.
|
||||
|
||||
### Important caveat to handle: connection readiness
|
||||
|
||||
`nostr_relay_pool_query_sync()` only queries relays that are **connected at call time**
|
||||
(`if (connected_count == 0) return NULL;` at
|
||||
[`core_relay_pool.c:1392`](nostr_core_lib/nostr_core/core_relay_pool.c:1392)). If
|
||||
`relay.laantungir.net` is still mid-connect when step 14 runs, a sync query could still
|
||||
miss it. Therefore the fix must ensure the relay holding the events is connected (or wait
|
||||
briefly for it) before/within the query.
|
||||
|
||||
### Plan
|
||||
|
||||
1. **Replace the step-14 async wait+validate with a synchronous load.** In
|
||||
[`src/main.c`](src/main.c) step 14 (~line 1518), instead of relying solely on
|
||||
`nostr_handler_wait_for_self_skill_eose()` + an immediate
|
||||
`nostr_handler_validate_self_skill_cache()`:
|
||||
- Keep registering the live subscription (so ongoing skill updates still stream in via
|
||||
`on_self_skill_event`) — this is still wanted for runtime.
|
||||
- For the startup *gate*, call
|
||||
[`nostr_handler_refresh_self_skill_cache_from_relays(timeout_ms)`](src/nostr_handler.c:3751)
|
||||
with a sensible timeout (e.g. 8000–10000 ms). This performs the synchronous,
|
||||
connection-aware query and fills `g_self_skill_events`.
|
||||
- Then run `nostr_handler_validate_self_skill_cache(&skills, &adoptions)`.
|
||||
|
||||
2. **Wait for the self relay (laantungir) to be connected before the sync query.** Reuse
|
||||
the existing relay-readiness helper pattern (`wait_for_connected_relays`) — but ensure
|
||||
we wait specifically until the relay list discovered from kind 10002 (which includes
|
||||
`relay.laantungir.net`) is connected, not just "any relay." If we already have a
|
||||
connected-count wait, extend/bound it so the relay that owns the events has a fair
|
||||
chance to connect before the sync query runs.
|
||||
|
||||
3. **Bounded retry around the sync query.** If the first sync query returns an empty cache,
|
||||
retry the sync query a small, bounded number of times (e.g. up to ~5–8s total),
|
||||
pumping `nostr_handler_poll()` between attempts to let connections settle. Only fail
|
||||
step 14 after the bounded retries still yield an empty cache. This converts a transient
|
||||
connection-timing miss into a brief wait instead of a hard failure.
|
||||
|
||||
4. **Keep the async subscription for runtime updates.** Do not remove the live
|
||||
subscription; it's still responsible for picking up skill changes after startup. Only
|
||||
the *startup gate* switches to the synchronous load for determinism.
|
||||
|
||||
### Why this is the right layer
|
||||
|
||||
- It uses the library's intended synchronous collect-all primitive rather than racing a
|
||||
streaming subscription against an immediate validation.
|
||||
- It is connection-aware and bounded, so a slow/late relay connect becomes a short wait,
|
||||
not a false "no skill events" failure.
|
||||
- It does not require modifying `nostr_core_lib` (though if desired, a follow-up could add
|
||||
a `query_sync` variant that waits for a named relay to connect; not required here).
|
||||
|
||||
## Interaction with the failure-DM throttle (already shipped)
|
||||
|
||||
- On success, the READY path clears the throttle marker
|
||||
([`src/main.c`](src/main.c) `startup_failure_marker_clear`). No change needed.
|
||||
- After this fix, step-14 failures should be genuinely rare (real outage / events truly
|
||||
absent), and the (single, throttled) failure DM will carry relay states + needed event
|
||||
kinds for accurate diagnosis.
|
||||
|
||||
## Verification
|
||||
|
||||
1. Build (`make`) clean, then `build_static.sh`.
|
||||
2. Deploy to VM410; restart both services many times in a row and confirm step 14 reaches
|
||||
`ok (skills=N adoptions=M)` **consistently** (no intermittent false negatives).
|
||||
3. Inspect logs to confirm the synchronous query path is used and that, on a cold cycle,
|
||||
it waits for `relay.laantungir.net` to connect and then returns the events.
|
||||
4. Negative test: configure only empty relays (no skill events anywhere) and confirm step
|
||||
14 still fails after the bounded retries (no false positive), sending exactly one
|
||||
throttled failure DM.
|
||||
5. Confirm a normal successful startup still sends the startup DM and clears the marker.
|
||||
|
||||
## Out of scope (possible follow-ups)
|
||||
|
||||
- Adding a `nostr_core_lib` `query_sync` variant that explicitly waits for specific
|
||||
relays to connect before issuing the REQ.
|
||||
- Softening step 14 into a degraded-start mode.
|
||||
@@ -1,77 +0,0 @@
|
||||
# Fix: Trigger Manager Bypasses Skill Adoption List
|
||||
|
||||
## Problem
|
||||
|
||||
`trigger_manager_load_from_skills()` and `trigger_manager_reconcile_from_relays()`
|
||||
activate cron/webhook/subscription triggers for **every** self-authored skill event
|
||||
(kind 31123/31124) that has trigger metadata, without checking whether the skill is
|
||||
on the kind 10123 adoption list.
|
||||
|
||||
This means a skill that was published but never adopted (or was un-adopted) will
|
||||
still have its cron trigger firing.
|
||||
|
||||
Contrast: `agent.c:refresh_adopted_skills_cache_if_needed()` properly fetches the
|
||||
kind 10123 list, parses `"a"` tags, and only loads adopted skills — and also checks
|
||||
the `enabled` tag. The trigger manager was written as a parallel system that skips
|
||||
both gates.
|
||||
|
||||
## Root Cause
|
||||
|
||||
- `trigger_manager.c:939` — `trigger_manager_load_from_skills()`: no reference to
|
||||
kind 10123 or adoption anywhere in the function. Calls `trigger_manager_add()`
|
||||
unconditionally.
|
||||
- `trigger_manager.c:592` — `trigger_manager_reconcile_from_relays()`: same bug.
|
||||
Currently disabled (`enable_periodic_reconcile = 0` at line 1575) but should be
|
||||
fixed before re-enabling.
|
||||
- Neither function checks the `enabled` tag either, unlike
|
||||
`trigger_manager_load_from_startup_events()` (line 1078) which does check it.
|
||||
|
||||
## Fix
|
||||
|
||||
### 1. Add a static helper: `load_adopted_d_tags()`
|
||||
|
||||
Add a static function in `trigger_manager.c` that:
|
||||
- Fetches kind 10123 events via `nostr_handler_get_self_events_by_kind_json(10123)`
|
||||
- Selects the latest event by `created_at` (same logic as agent.c)
|
||||
- Parses `"a"` tags using a local `parse_skill_address_tag_local()` equivalent
|
||||
- Populates a fixed-size array of adopted d_tag strings
|
||||
- Returns the count
|
||||
|
||||
Since both call sites only deal with self-authored skills, matching on d_tag
|
||||
alone is sufficient (the pubkey in the "a" tag will be the agent's own).
|
||||
|
||||
### 2. Add `enabled` tag check
|
||||
|
||||
Both functions should read the `enabled` tag (defaulting to `"true"`) and skip
|
||||
skills where `enabled` is `false` or `0`, matching the pattern already used in
|
||||
`trigger_manager_load_from_startup_events()`.
|
||||
|
||||
### 3. Gate `trigger_manager_load_from_skills()`
|
||||
|
||||
Before calling `trigger_manager_add()`, check:
|
||||
1. The skill's d_tag is in the adopted set
|
||||
2. The skill's `enabled` tag is not false/0
|
||||
|
||||
If the adoption list is empty or missing, log a warning and load nothing (do not
|
||||
fall back to loading all skills — that's the bug).
|
||||
|
||||
### 4. Gate `trigger_manager_reconcile_from_relays()`
|
||||
|
||||
Apply the same adoption + enabled checks. This ensures the code is correct when
|
||||
`enable_periodic_reconcile` is eventually flipped back to 1.
|
||||
|
||||
### 5. Pass `is_enabled` to `trigger_manager_add()`
|
||||
|
||||
Currently both functions pass `1` (hardcoded enabled) as the `enabled` parameter.
|
||||
Change to pass the actual `is_enabled` value derived from the `enabled` tag.
|
||||
|
||||
## Files Changed
|
||||
|
||||
- `src/trigger_manager.c` — add helper, fix both functions
|
||||
|
||||
## Risk
|
||||
|
||||
Low. The fix adds a filter before `trigger_manager_add()`. If the adoption list
|
||||
is present and correct, behavior is unchanged for adopted skills. If the adoption
|
||||
list is missing, triggers simply won't load (which is the correct behavior —
|
||||
nothing should fire if nothing is adopted).
|
||||
@@ -1,127 +0,0 @@
|
||||
# NIP-17 Gift Wrap Send Failure — Investigation
|
||||
|
||||
## Status: Resolved — Fixed in code (2026-04-11)
|
||||
|
||||
## Date Discovered: 2026-04-11
|
||||
|
||||
## Symptom
|
||||
|
||||
Every outbound NIP-17 DM send from the Anvil agent fails with:
|
||||
```
|
||||
NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
|
||||
auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
|
||||
```
|
||||
|
||||
The agent successfully falls back to NIP-04 (kind 4), so DMs are still delivered. However, NIP-17 outbound is completely broken.
|
||||
|
||||
**Inbound NIP-17 works fine** — the agent successfully receives and decrypts kind 1059 gift wraps from the admin.
|
||||
|
||||
## Affected Code Path
|
||||
|
||||
### Send path (broken):
|
||||
1. `nostr_handler_send_dm_auto_with_role()` in `src/nostr_handler.c:3193` — routes to NIP-17 when `dm_protocol` is `nip17` or `both`
|
||||
2. `nostr_handler_send_dm_nip17_with_role()` in `src/nostr_handler.c:4050` — creates chat event, calls `nostr_nip17_send_dm()`
|
||||
3. `nostr_nip17_send_dm()` in `nostr_core_lib/nostr_core/nip017.c:260` — iterates recipients, creates seal + gift wrap
|
||||
4. `nostr_nip59_create_seal()` in `nostr_core_lib/nostr_core/nip059.c:150` — **encrypts rumor JSON with NIP-44**
|
||||
5. `nostr_nip59_create_gift_wrap()` in `nostr_core_lib/nostr_core/nip059.c:226` — wraps seal with random key
|
||||
|
||||
### Receive path (working):
|
||||
1. `on_event()` receives kind 1059
|
||||
2. `nostr_nip17_receive_dm()` in `nostr_core_lib/nostr_core/nip017.c:326` — unwraps gift, unseals rumor
|
||||
3. Successfully extracts kind 14 content
|
||||
|
||||
## Probable Root Cause: Fixed Buffer Overflow in Seal Encryption
|
||||
|
||||
In `nostr_core_lib/nostr_core/nip059.c:163-165`:
|
||||
|
||||
```c
|
||||
char encrypted_content[4096]; // Should be large enough for most events
|
||||
int encrypt_result = nostr_nip44_encrypt(sender_private_key, recipient_public_key,
|
||||
rumor_json, encrypted_content, sizeof(encrypted_content));
|
||||
```
|
||||
|
||||
The seal creation uses a **fixed 4096-byte stack buffer** for the NIP-44 encrypted output. The rumor JSON includes the full DM message content. When the agent sends long responses (the FIPS mesh conversation had multi-KB markdown tables), the rumor JSON easily exceeds 4096 bytes after NIP-44 encryption overhead (base64 encoding roughly 4/3x expansion + padding + nonce).
|
||||
|
||||
**Evidence:**
|
||||
- The agent's DM responses in the log were 2-5KB of markdown content
|
||||
- After JSON serialization of the kind 14 rumor (adding pubkey, tags, created_at, kind fields), the total is larger
|
||||
- NIP-44 encryption adds: 32-byte nonce + padding + base64 encoding → roughly 1.5-2x expansion
|
||||
- A 3KB message → ~4.5KB rumor JSON → ~6-9KB encrypted → overflows 4096 buffer
|
||||
- `nostr_nip44_encrypt()` returns error, `nostr_nip59_create_seal()` returns NULL
|
||||
- `nostr_nip17_send_dm()` skips the recipient, returns 0 wraps
|
||||
- `nostr_handler_send_dm_nip17_with_role()` logs "gift_wrap creation failed"
|
||||
|
||||
**Why receive works:** The receive path uses `nostr_nip44_decrypt()` which likely allocates dynamically or has a larger buffer.
|
||||
|
||||
## Secondary Suspect: NIP-44 Encryption Implementation
|
||||
|
||||
The NIP-44 encrypt function itself may have issues with large payloads. Need to check:
|
||||
- `nostr_nip44_encrypt()` in `nostr_core_lib/nostr_core/nip044.c`
|
||||
- Whether it handles the output buffer size parameter correctly
|
||||
- Whether it returns an error code that distinguishes "buffer too small" from other failures
|
||||
|
||||
## Anvil Config Context
|
||||
|
||||
The Anvil agent's genesis config (`/home/user/anvil/genesis.jsonc`) has:
|
||||
```json
|
||||
"dm_protocol": "nip04"
|
||||
```
|
||||
|
||||
But the encrypted user-settings on Nostr (kind 30078) may have overridden this to `"both"` at runtime, which is why NIP-17 send is being attempted.
|
||||
|
||||
## Recommended Fix
|
||||
|
||||
### Option A: Dynamic allocation (preferred)
|
||||
Replace the fixed 4096-byte buffer in `nostr_nip59_create_seal()` with dynamic allocation:
|
||||
```c
|
||||
size_t rumor_len = strlen(rumor_json);
|
||||
size_t encrypted_buf_size = rumor_len * 2 + 256; // generous overhead for NIP-44
|
||||
char* encrypted_content = malloc(encrypted_buf_size);
|
||||
```
|
||||
|
||||
### Option B: Larger fixed buffer (quick fix)
|
||||
Increase the buffer to 64KB or 128KB:
|
||||
```c
|
||||
char encrypted_content[131072]; // 128KB should handle any reasonable DM
|
||||
```
|
||||
Note: This is a stack allocation, so very large buffers could cause stack overflow.
|
||||
|
||||
### Also check `nostr_nip59_create_gift_wrap()`
|
||||
The gift wrap function at `nip059.c:226` may have a similar fixed-buffer issue for encrypting the seal JSON.
|
||||
|
||||
## Log Evidence
|
||||
|
||||
From `/home/user/anvil/debug.log` (2026-04-11):
|
||||
|
||||
```
|
||||
[2026-04-11 09:45:14] [WARN ] NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
|
||||
[2026-04-11 09:45:14] [WARN ] auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
|
||||
[2026-04-11 09:50:16] [WARN ] NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
|
||||
[2026-04-11 09:50:16] [WARN ] auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
|
||||
[2026-04-11 09:53:42] [WARN ] NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
|
||||
[2026-04-11 09:53:42] [WARN ] auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
|
||||
```
|
||||
|
||||
This pattern is 100% consistent — every outbound NIP-17 attempt fails, every time.
|
||||
|
||||
## Related Files
|
||||
|
||||
- `nostr_core_lib/nostr_core/nip059.c` — Gift wrap / seal creation (probable bug location)
|
||||
- `nostr_core_lib/nostr_core/nip044.c` — NIP-44 encryption (check buffer handling)
|
||||
- `nostr_core_lib/nostr_core/nip017.c` — NIP-17 DM send/receive orchestration
|
||||
- `src/nostr_handler.c:4050` — Agent-level NIP-17 send function
|
||||
- `plans/nip17_messaging.md` — Original NIP-17 implementation plan
|
||||
|
||||
## Related Existing Plan
|
||||
|
||||
The `plans/nip17_messaging.md` document covers the original NIP-17 implementation. This investigation is specifically about the send-side gift wrap failure that emerged in production.
|
||||
|
||||
## Resolution Summary
|
||||
|
||||
Implemented in `nostr_core_lib/nostr_core/nip059.c`:
|
||||
|
||||
- Replaced fixed encryption buffers in `nostr_nip59_create_seal()` and `nostr_nip59_create_gift_wrap()` with dynamically sized heap buffers calculated from NIP-44 padding/base64 overhead.
|
||||
- Replaced fixed decrypt buffers in `nostr_nip59_unwrap_gift()` and `nostr_nip59_unseal_rumor()` with dynamic buffers sized from ciphertext length.
|
||||
- Added missing cleanup path in `nostr_nip59_create_seal()` for failure after encryption allocation.
|
||||
|
||||
Expected outcome: outbound NIP-17 gift wrap creation no longer fails for multi-KB DM payloads due to fixed buffer limits.
|
||||
+17
-24
@@ -2,7 +2,7 @@
|
||||
|
||||
## Background
|
||||
|
||||
Didactyl now supports both NIP-04 and NIP-17 DM flows (including receive path for kind 1059 and protocol-aware auto-reply routing). Remaining work in this plan tracks spec-hardening and bootstrap/discovery details.
|
||||
Didactyl currently uses NIP-04 (kind 4) for all DM communication. NIP-17 send support exists via `nostr_handler_send_dm_nip17()` and the `nostr_dm_send_nip17` tool, but there is **no ability to receive NIP-17 messages** and **all agent responses always use NIP-04**.
|
||||
|
||||
The `nostr_core_lib` already has full NIP-17/NIP-59 support including:
|
||||
- `nostr_nip17_create_chat_event()` — create kind 14 rumor
|
||||
@@ -113,35 +113,28 @@ Replace all `nostr_handler_send_dm()` calls with `nostr_handler_send_dm_auto()`.
|
||||
|
||||
### 7. Add kind 10050 startup event support
|
||||
|
||||
**Status**: ✅ Implemented
|
||||
**File**: `config.json.example`
|
||||
|
||||
**Files**:
|
||||
- `src/default_events.h` (default startup kinds now include `10050`)
|
||||
- `src/setup_wizard.c` (builds kind `10050` tags from current configured relays)
|
||||
- `genesis.jsonc.example` (includes kind `10050` startup event)
|
||||
- `genesis.lt.didactyl.jsonc` (includes kind `10050` startup event)
|
||||
Add a kind 10050 startup event example so NIP-17 clients can discover the agent's DM relay preferences:
|
||||
|
||||
Implementation detail: kind `10050` reuses the same relay set as kind `10002`/current config relays, with `relay` tags as required by NIP-17.
|
||||
```json
|
||||
{
|
||||
"kind": 10050,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["relay", "wss://relay.damus.io"],
|
||||
["relay", "wss://nos.lol"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**File**: `config.c` — startup event parsing already handles arbitrary kinds, so this should work with no code changes.
|
||||
|
||||
### 8. Update startup DM to use auto-routing
|
||||
|
||||
**Status**: ✅ Implemented
|
||||
**File**: `main.c`
|
||||
|
||||
`main.c` uses `nostr_handler_send_dm_auto()` for startup DM flow.
|
||||
|
||||
### 9. Add NIP-17 seal/rumor pubkey verification
|
||||
|
||||
**Status**: ✅ Implemented (in core lib)
|
||||
|
||||
**File**: `nostr_core_lib/nostr_core/nip017.c`
|
||||
|
||||
`nostr_nip17_receive_dm()` now verifies that seal pubkey matches rumor pubkey and rejects mismatches to prevent sender impersonation.
|
||||
|
||||
### 10. Recipient kind-10050 relay targeting for sends
|
||||
|
||||
**Status**: ⏸ Deferred
|
||||
|
||||
Current NIP-17 send path publishes to connected configured relays. Querying and targeting recipient-advertised kind `10050` relays is kept as a future enhancement while debugging current interoperability.
|
||||
The startup status DM at line 255 currently calls `nostr_handler_send_dm()`. Update to `nostr_handler_send_dm_auto()`.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
|
||||
@@ -1,440 +0,0 @@
|
||||
# n_signer Integration Plan — didactyl
|
||||
|
||||
## Goal
|
||||
|
||||
Separate control of the nsec from the didactyl agent by routing signing/encryption
|
||||
through `n_signer` — a separate foreground process that holds key material in locked
|
||||
memory and signs on request. A later phase may embed n_signer into the didactyl binary
|
||||
so one self-contained binary spawns an n_signer in a separate window; that is deferred
|
||||
because of a reliability concern (if the agent loses signing ability it cannot even DM
|
||||
its admin about the failure).
|
||||
|
||||
This plan covers two distinct work items:
|
||||
|
||||
1. **Update didactyl's vendored `nostr_core_lib`** to the upstream version that already
|
||||
implements the nsigner client (M1–M6 complete, upstream `VERSION` 0.6.6). This is a
|
||||
mechanical sync that changes no runtime behavior.
|
||||
2. **Opt-in migration of didactyl call sites** to the `nostr_signer_t` provider so the
|
||||
raw private key can leave the agent process. This is the actual nsec-separation work
|
||||
and is staged so reliability is preserved.
|
||||
|
||||
---
|
||||
|
||||
## Background — what upstream already provides
|
||||
|
||||
Upstream `nostr_core_lib` (git.laantungir.net/nostr_core_lib, v0.6.6) has shipped the
|
||||
full signer abstraction. didactyl's vendored copy predates all of it.
|
||||
|
||||
New upstream modules:
|
||||
|
||||
- [`nostr_core/nostr_signer.{h,c}`](../nostr_core_lib/nostr_core/nostr_signer.h) —
|
||||
`nostr_signer_t` opaque handle + vtable. Two backends:
|
||||
- **local** — `nostr_signer_local(privkey[32])`, wraps today's raw-key path. Output-equivalent.
|
||||
- **remote (nsigner)** — `nostr_signer_nsigner_unix/serial/tcp/fds(...)`, guarded by
|
||||
`NOSTR_ENABLE_NSIGNER_CLIENT`.
|
||||
- [`nostr_core/nsigner_client.{h,c}`](../nostr_core_lib/nostr_core/nsigner_client.h) —
|
||||
framing, JSON-RPC verbs, kind-27235 auth envelope, error mapping.
|
||||
- [`nostr_core/nsigner_transport.{h,c}`](../nostr_core_lib/nostr_core/nsigner_transport.h) —
|
||||
pluggable transport vtable: AF_UNIX abstract, TCP, USB-CDC serial, fd-pair (stdio/qrexec).
|
||||
|
||||
Verbs mirror n_signer's wire contract: `get_public_key`, `sign_event`,
|
||||
`nip04_encrypt/decrypt`, `nip44_encrypt/decrypt`.
|
||||
|
||||
Parallel `*_with_signer` entry points added across the library (legacy raw-key APIs are
|
||||
preserved as thin wrappers over the local backend, so existing projects keep compiling
|
||||
and behaving identically):
|
||||
|
||||
- NIP-01: `nostr_create_and_sign_event_with_signer`
|
||||
- NIP-17: `nostr_nip17_create_relay_list_event_with_signer`,
|
||||
`nostr_nip17_send_dm_with_signer`, `nostr_nip17_receive_dm_with_signer`
|
||||
- NIP-42: `nostr_nip42_create_auth_event_with_signer`
|
||||
- NIP-46: request/response/decrypt/session-init `*_with_signer` variants
|
||||
- NIP-59: seal/unwrap/unseal `*_with_signer` variants
|
||||
- NIP-60 / NIP-61: all cashu event-creation `*_with_signer` variants
|
||||
- Blossom: `blossom_create_auth_header_with_signer`,
|
||||
`blossom_upload_with_signer`, `blossom_upload_file_with_signer`,
|
||||
`blossom_delete_with_signer`
|
||||
- Relay pool: `nostr_relay_pool_set_auth_with_signer` — the pool now stores an
|
||||
`auth_signer` instead of a raw key, so NIP-42 AUTH events are signed through the
|
||||
provider on relay challenge. **This was the hardest blocker and it is already solved
|
||||
upstream.**
|
||||
|
||||
Upstream `build.sh` unconditionally defines `-DNOSTR_ENABLE_NSIGNER_CLIENT=1` and
|
||||
compiles `nostr_signer.c`, `nsigner_transport.c`, `nsigner_client.c`.
|
||||
|
||||
Dependency direction is unchanged and non-circular: n_signer (server) depends on
|
||||
`nostr_core_lib` for leaf crypto; `nostr_core_lib` gains a *client* that consumes its
|
||||
own primitives. Nobody imports anyone's binary in a loop.
|
||||
|
||||
---
|
||||
|
||||
## Current didactyl state (what we are updating from)
|
||||
|
||||
- `nostr_core_lib/` is a **vendored source directory** (no git submodule; no
|
||||
`.gitmodules`). Built via `./nostr_core_lib/build.sh --nips=001,004,...,061` into a
|
||||
static `libnostr_core_*.a`, linked by [`Makefile`](../Makefile:56) and the Docker
|
||||
static build ([`Dockerfile.alpine-musl`](../Dockerfile.alpine-musl:81)).
|
||||
- The vendored tree has **none** of the nsigner pieces: no `nostr_signer.*`,
|
||||
no `nsigner_*.*`, no `*_with_signer` variants. The relay pool still uses the raw-key
|
||||
AUTH path ([`nostr_core_lib/nostr_core/core_relay_pool.c`](../nostr_core_lib/nostr_core/core_relay_pool.c:682)).
|
||||
- didactyl holds the raw 32-byte key in [`cfg->keys.private_key`](../src/config.h:16)
|
||||
and passes it directly into ~30+ library call sites (event signing, NIP-04/44
|
||||
encrypt/decrypt, NIP-17 gift wraps, NIP-60/61 cashu, blossom, relay pool AUTH).
|
||||
|
||||
---
|
||||
|
||||
## Architecture (target)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[didactyl src] --> B[nostr_core_lib vendored]
|
||||
B --> C[nostr_signer_t local backend]
|
||||
B --> D[nostr_signer_t nsigner remote backend]
|
||||
D --> E[nsigner client: framing + auth envelope]
|
||||
E --> F[nostr_create_and_sign_event in nostr_core_lib]
|
||||
G[n_signer server binary] --> B
|
||||
```
|
||||
|
||||
didactyl constructs **one** `nostr_signer_t*` at startup and threads it through every
|
||||
signing/encryption/AUTH call site. The backend is chosen by config:
|
||||
|
||||
- `signer.mode = "local"` → `nostr_signer_local(cfg->keys.private_key)` (today's behavior).
|
||||
- `signer.mode = "nsigner_unix"` → `nostr_signer_nsigner_unix(socket_name, role, timeout_ms)`.
|
||||
- (future) `nsigner_tcp`, `nsigner_serial`, `nsigner_fds`.
|
||||
|
||||
The raw `cfg->keys.private_key` is only populated in `local` mode. In remote modes the
|
||||
agent process never holds the nsec; it learns its own pubkey via
|
||||
`nostr_signer_get_public_key()`.
|
||||
|
||||
---
|
||||
|
||||
## Phasing
|
||||
|
||||
### Phase 1 — Update vendored nostr_core_lib (mechanical, zero behavior change)
|
||||
|
||||
Goal: bring didactyl's `nostr_core_lib` source tree to upstream v0.6.6 so the signer
|
||||
abstraction and nsigner client are available. **No didactyl source changes.** Existing
|
||||
raw-privkey APIs are preserved upstream as thin wrappers over the local backend, so all
|
||||
~30 call sites keep compiling and behaving identically.
|
||||
|
||||
Steps:
|
||||
|
||||
1. Snapshot current vendored tree for diffing/rollback:
|
||||
`cp -a nostr_core_lib nostr_core_lib.old`
|
||||
2. Replace the vendored source tree with upstream v0.6.6. Preferred: copy the upstream
|
||||
`nostr_core/`, `cjson/`, `nostr_websocket/`, `platform/`, `build.sh`, `CMakeLists.txt`
|
||||
into `nostr_core_lib/`, preserving didactyl-local build quirks only if any exist
|
||||
(none found — the Dockerfile already `sed`s `build.sh` in-place for fortification).
|
||||
Do **not** carry over upstream's prebuilt `.a`/`.o` files, `pool.log`, `.venv*`,
|
||||
`backups/`, `verify_*` artifacts — match didactyl's existing `.gitignore` hygiene.
|
||||
3. Rebuild the static library:
|
||||
`cd nostr_core_lib && ./build.sh --nips=001,004,005,006,011,013,017,019,021,042,044,046,059,060,061`
|
||||
(same NIP set as today; `nostr_signer.c` / `nsigner_*.c` are compiled unconditionally
|
||||
by upstream `build.sh`).
|
||||
4. Rebuild didactyl: `make clean && make`. Confirm it compiles and links with no source
|
||||
changes.
|
||||
5. Smoke test: run the agent with an nsec-only config and confirm signing/DMs/relay AUTH
|
||||
still work byte-for-byte (the upstream local backend is output-equivalent to the
|
||||
legacy path; there is an upstream test `test_event_creation_equivalence_with_signer`
|
||||
that asserts this).
|
||||
6. Static (Docker) build: `./build_static.sh`. The Dockerfile copies `nostr_core_lib/`
|
||||
and runs `./build.sh --nips=all`; verify the static binary still builds and runs.
|
||||
7. Record the upstream version pinned in this repo (e.g. a one-line
|
||||
`nostr_core_lib/UPSTREAM_VERSION` file = `0.6.6`) so future syncs are auditable.
|
||||
|
||||
Exit criteria: didactyl builds and runs identically, but now links a `nostr_core_lib`
|
||||
that exposes `nostr_signer_t` and the nsigner client. No nsec separation yet.
|
||||
|
||||
### Phase 2 — Signer provider in didactyl config + startup (local backend, still no separation)
|
||||
|
||||
Goal: introduce a `nostr_signer_t*` in didactyl constructed at startup, backed by
|
||||
`local` mode by default. This is the seam; behavior is still identical.
|
||||
|
||||
#### 2a. Config schema
|
||||
|
||||
[`src/config.c`](../src/config.c) / [`src/config.h`](../src/config.h): add a `signer`
|
||||
block (sibling of `keys`):
|
||||
|
||||
```jsonc
|
||||
"signer": {
|
||||
"mode": "local", // "local" | "nsigner_unix" | (future) "nsigner_tcp" | "nsigner_serial" | "nsigner_fds"
|
||||
"socket_name": "", // nsigner_unix: abstract socket name (without @); empty = auto-discover single running signer
|
||||
"role": "main", // n_signer role selector
|
||||
"timeout_ms": 15000,
|
||||
"auth_privkey_hex": "" // optional, for TCP auth envelope only
|
||||
}
|
||||
```
|
||||
|
||||
Default `mode = "local"`. Backward compatible: if `signer` block is absent, behave as
|
||||
`local` using `keys.nsec`. Validation: in any `nsigner_*` mode, `keys.nsec` is optional
|
||||
and ignored (the agent learns its pubkey from the signer); in `local` mode `keys.nsec`
|
||||
remains required.
|
||||
|
||||
#### 2b. User-facing surfaces (how the operator chooses n_signer)
|
||||
|
||||
didactyl has three startup surfaces today; the n_signer choice is exposed on all three,
|
||||
consistent with existing patterns:
|
||||
|
||||
**1. Interactive setup wizard** ([`src/setup_wizard.c`](../src/setup_wizard.c),
|
||||
`new_agent_identity_step` at line ~2116). Today the Identity page offers:
|
||||
`[g]enerate new keypair` / `[p]rovide existing nsec`. Add a third option:
|
||||
|
||||
- `[s]ign with a running n_signer (separate the nsec from the agent)`
|
||||
|
||||
When chosen, the wizard:
|
||||
- Does **not** prompt for an nsec. Instead prompts for:
|
||||
- socket name (default: auto-discover the single running signer),
|
||||
- role (default `main`),
|
||||
- timeout (default 15000ms).
|
||||
- Performs a live `nostr_signer_nsigner_unix(...)` + `nostr_signer_get_public_key()`
|
||||
connectivity check immediately, so the operator gets confirmation ("Connected to
|
||||
n_signer @nsigner_hairy_dog; agent npub: npub1...") before proceeding.
|
||||
- Writes `signer.mode = "nsigner_unix"` (plus socket_name/role/timeout) into the
|
||||
generated genesis JSONC and **omits** `keys.nsec`.
|
||||
- The existing `[p]rovide existing nsec` path stays as `local` mode (the default).
|
||||
|
||||
This keeps the trust model visible at the moment the operator commits to an identity:
|
||||
either the nsec lives in the agent (`local`) or it does not (`nsigner_unix`).
|
||||
|
||||
**2. CLI flags + env** ([`src/main.c`](../src/main.c) `print_usage` at line ~141 and
|
||||
arg parsing at line ~1233). Add:
|
||||
|
||||
- `--signer <mode>` — one of `local`, `nsigner_unix`, `nsigner_tcp`, `nsigner_serial`,
|
||||
`nsigner_fds`. Overrides `signer.mode` in config.
|
||||
- `--signer-socket <name>` — abstract socket name (nsigner_unix).
|
||||
- `--signer-role <role>` — n_signer role selector (default `main`).
|
||||
- `--signer-timeout <ms>` — per-call timeout (default 15000).
|
||||
- `--signer-tcp <host:port>` — shorthand for `--signer nsigner_tcp` + host/port.
|
||||
- `DIDACTYL_SIGNER` env — fallback for `--signer` (mirrors the `DIDACTYL_NSEC` pattern).
|
||||
|
||||
When `--signer nsigner_unix` (or `DIDACTYL_SIGNER=nsigner_unix`) is set, `--nsec` /
|
||||
`DIDACTYL_NSEC` are not required and are ignored if present. This enables headless /
|
||||
systemd deployment where the nsec never touches the agent's argv or environment.
|
||||
|
||||
Update `print_usage` examples to show the n_signer path, e.g.:
|
||||
`%s --signer nsigner_unix --signer-socket nsigner_hairy_dog --admin npub1...`
|
||||
|
||||
**3. Genesis JSONC config** — the `signer` block in 2a is the canonical, persistent
|
||||
form. The wizard writes it; CLI flags override it for a single run; env vars are the
|
||||
last fallback. This matches how `keys.nsec` / `--nsec` / `DIDACTYL_NSEC` already layer.
|
||||
|
||||
Precedence (highest to lowest): CLI flag → env var → genesis `signer` block → default
|
||||
`local`. Same precedence as the existing key/admin overrides.
|
||||
|
||||
#### 2c. Startup construction
|
||||
|
||||
[`src/main.c`](../src/main.c): after loading config + applying CLI/env overrides,
|
||||
construct a single `nostr_signer_t* g_signer`:
|
||||
|
||||
- `local` → `nostr_signer_local(cfg->keys.private_key)`.
|
||||
- `nsigner_unix` → `nostr_signer_nsigner_unix(socket_name, role, timeout_ms)`.
|
||||
- (future) `nsigner_tcp` / `nsigner_serial` / `nsigner_fds` analogously.
|
||||
|
||||
Then derive the agent pubkey via `nostr_signer_get_public_key(g_signer, ...)` instead of
|
||||
`nostr_ec_public_key_from_private_key(cfg->keys.private_key, ...)`. In `local` mode this
|
||||
yields the same bytes; in remote mode it is the first n_signer round-trip and **doubles
|
||||
as the mandatory startup connectivity check** (Phase 4): failure here is fatal and loud.
|
||||
|
||||
Hold `g_signer` alongside `g_cfg` / `g_pool` for the process lifetime; free on shutdown.
|
||||
|
||||
Exit criteria: didactyl runs with a `nostr_signer_t*` in `local` mode by default, and
|
||||
the operator can choose `nsigner_unix` via wizard, CLI, or config. No call sites migrated
|
||||
yet (Phase 3). This is a safe, reviewable checkpoint — in `local` mode behavior is
|
||||
identical; in `nsigner_unix` mode the agent starts but signing still uses the local path
|
||||
until Phase 3 lands (so Phase 2 remote mode is a connectivity proof, not full separation).
|
||||
|
||||
### Phase 3 — Migrate call sites to `*_with_signer` (enables real separation)
|
||||
|
||||
Goal: route every signing/encryption/AUTH operation through `g_signer`. In `local` mode
|
||||
this is still output-equivalent; in `nsigner_unix` mode the nsec is no longer in the
|
||||
agent process.
|
||||
|
||||
Migrate in this order (highest value / lowest risk first):
|
||||
|
||||
1. **Relay pool AUTH** — replace
|
||||
`nostr_relay_pool_set_auth(g_pool, g_cfg->keys.private_key, 1)` with
|
||||
`nostr_relay_pool_set_auth_with_signer(g_pool, g_signer, 1)`. This is the single
|
||||
change that delegates the deep-event-loop AUTH signing.
|
||||
2. **Event signing** — `nostr_create_and_sign_event(...)` →
|
||||
`nostr_create_and_sign_event_with_signer(...)` at all sites (posts, kind 30078
|
||||
config publish, startup events, etc.). Search: `nostr_create_and_sign_event` in
|
||||
`src/`.
|
||||
3. **NIP-44 encrypt/decrypt** — the DM/memory/task/config/block-list sites. These call
|
||||
`nostr_nip44_encrypt/decrypt(cfg->keys.private_key, peer, ...)`. Replace with
|
||||
`nostr_signer_nip44_encrypt/decrypt(g_signer, peer_hex, ...)`. Note the signer API
|
||||
takes hex pubkeys and returns heap strings via `char**` out-params, so each call site
|
||||
needs a small adaptation (hex-encode the peer key, free the out-string).
|
||||
4. **NIP-04 encrypt/decrypt** — same pattern as NIP-44 for the legacy NIP-04 DM sites.
|
||||
5. **NIP-17 gift wraps** — `nostr_nip17_send_dm` / `nostr_nip17_receive_dm` →
|
||||
`*_with_signer`. (The seal is signed with the agent key via the signer; the gift-wrap
|
||||
ephemeral key stays local — that is correct and does not leak the nsec.)
|
||||
6. **NIP-60/61 cashu** — `nostr_nip60_create_*_event` / `nostr_nip61_create_*_event` →
|
||||
`*_with_signer` in [`src/cashu_wallet.c`](../src/cashu_wallet.c).
|
||||
7. **Blossom** — `blossom_create_auth_header` / `blossom_upload` / `blossom_delete` →
|
||||
`*_with_signer` in [`src/tools/tool_blossom.c`](../src/tools/tool_blossom.c).
|
||||
|
||||
After each group, build + smoke test in `local` mode (output-equivalent) before
|
||||
testing in `nsigner_unix` mode against a running `n_signer`.
|
||||
|
||||
Exit criteria: every signing/encryption/AUTH op flows through `g_signer`. With
|
||||
`signer.mode = "nsigner_unix"`, the agent process holds no nsec.
|
||||
|
||||
### Phase 4 — Reliability & failure handling (critical, do before enabling remote by default)
|
||||
|
||||
The user's stated concern: if the agent loses signing ability, it cannot even DM its
|
||||
admin about the failure. This must be addressed before remote mode is anything but
|
||||
opt-in.
|
||||
|
||||
Design:
|
||||
|
||||
- **Startup connectivity check is mandatory.** `nostr_signer_get_public_key()` is called
|
||||
at startup for every mode. In remote mode, failure here is fatal and loud (agent
|
||||
refuses to start, prints a clear error). Better to fail to start than to start unable
|
||||
to sign.
|
||||
- **Runtime signing failure** — classify by error:
|
||||
- `approval_denied` / `policy_denied` / `unknown_role` → not a transient error; surface
|
||||
to agent log and admin context, do not retry blindly.
|
||||
- transport/IO error (n_signer crashed, socket gone) → the nsigner transport has a
|
||||
`reconnect` hook; the client retries once with reconnect, then returns an error.
|
||||
- **Admin notification path.** Because DMs themselves require signing, a signing outage
|
||||
is a catch-22 for "DM the admin". Mitigations, in order of preference:
|
||||
1. **Health-gate before sending.** Before attempting an admin DM, do a cheap
|
||||
`nostr_signer_get_public_key()` ping. If it fails, fall back to a **local,
|
||||
keyless** notification channel: stderr/log + HTTP API health endpoint
|
||||
([`src/http_api.c`](../src/http_api.c)) exposes a `signer_unhealthy` flag. The admin
|
||||
monitors the HTTP health endpoint (or process supervisor) rather than relying on a
|
||||
DM that requires the broken signer.
|
||||
2. **Optional emergency local key.** A config-gated `signer.emergency_local_nsec`
|
||||
used *only* to sign admin DMs when the remote signer is unreachable. This is a
|
||||
deliberate policy tradeoff (it re-introduces a key in-process, scoped to admin
|
||||
alerts only) and should be off by default. Document the tradeoff clearly.
|
||||
3. **Process supervisor.** systemd/`didactyl` service restarts on signer health
|
||||
failure; the supervisor's logs are the out-of-band channel.
|
||||
- **Recommendation for v1:** ship (1) + (3). Leave (2) as a documented opt-in for
|
||||
operators who prioritize alert delivery over strict key separation. This keeps the
|
||||
default trust model clean while making the failure observable out-of-band.
|
||||
|
||||
Exit criteria: a signing outage is detectable out-of-band (HTTP health + logs +
|
||||
supervisor), and the agent does not silently hang. Remote mode is still opt-in.
|
||||
|
||||
#### Phase 4 implementation status (items 18–20) — COMPLETE
|
||||
|
||||
Items 18, 19, and 20 are implemented as follows. Behavior is backward compatible
|
||||
and remote mode remains opt-in.
|
||||
|
||||
**Item 18 — Runtime signer health classification & tracking**
|
||||
|
||||
- New module [`src/signer_health.{h,c}`](../src/signer_health.h) holds a
|
||||
process-global, mutex-protected health state: `unknown | healthy | unhealthy`,
|
||||
the configured signer mode snapshot, the last error reason string, and the
|
||||
last error / last ok unix timestamps.
|
||||
- [`src/main.c`](../src/main.c) `construct_signer()` records the mode at startup
|
||||
and records `ok` on a successful connectivity check (`get_public_key`) or
|
||||
`failure` (with the nostr error code) on construction/connectivity failure.
|
||||
The fatal+loud startup behavior is preserved; error messages now explicitly
|
||||
include `mode=`, `socket=`, `role=`, `timeout_ms=`, and `rc=` so the operator
|
||||
knows exactly which signer context failed.
|
||||
- The migrated signer call paths in [`src/nostr_handler.c`](../src/nostr_handler.c)
|
||||
(NIP-44 self encrypt/decrypt, NIP-04 incoming DM decrypt, NIP-04 outgoing DM
|
||||
encrypt) call `signer_health_record_ok()` on success and
|
||||
`signer_health_record_failure(rc, "<verb>")` on failure. Classification maps
|
||||
nostr error codes to short labels: `transport/IO` (IO/network), `crypto/auth`,
|
||||
`resource`, `invalid-input`, `unknown`. This is deliberately minimal — no broad
|
||||
rewrite of the tool/agent/cashu/block-list signer sites; the central DM and
|
||||
self-config paths are instrumented, which is where an outage first surfaces.
|
||||
|
||||
**Item 19 — Out-of-band health surface (HTTP + logs + supervisor)**
|
||||
|
||||
- [`src/http_api.c`](../src/http_api.c) `handle_status()` (the `GET /api/status`
|
||||
endpoint) now adds the following fields to the JSON response:
|
||||
- `signer_mode` — the configured signer mode (`local` / `nsigner_unix` / `nsigner_tcp`).
|
||||
- `signer_healthy` — `"true"` / `"false"` / `"unknown"`.
|
||||
- `signer_health_state` — `healthy` / `unhealthy` / `unknown`.
|
||||
- `signer_last_error` — human-readable `<classification>: <detail> (rc=<n>)`, or `""`.
|
||||
- `signer_last_error_ts` — unix timestamp of the last unhealthy transition, or `null`.
|
||||
- `signer_last_ok_ts` — unix timestamp of the last healthy observation, or `null`.
|
||||
An operator (or supervisor / monitoring agent) can poll `/api/status` to detect a
|
||||
signing outage without requiring a signed admin DM to come back.
|
||||
- `signer_health_record_failure()` and `signer_health_record_ok()` each emit a
|
||||
one-line stderr warning on a health **transition** (healthy -> unhealthy, or
|
||||
unhealthy -> healthy), including the mode and reason. Repeated failures within
|
||||
the same unhealthy state do not spam the log.
|
||||
|
||||
**Item 20 — Optional `emergency_local_nsec` (admin-DM delivery tradeoff)**
|
||||
|
||||
- New config field `signer.emergency_local_nsec` (string, empty default) in
|
||||
[`src/config.h`](../src/config.h) `signer_config_t`, parsed in
|
||||
[`src/config.c`](../src/config.c) `parse_signer_config()`.
|
||||
- **No active fallback behavior is wired yet.** The field is parsed, stored, and
|
||||
documented only — a feature-flag/placeholder so operators can pre-stage the
|
||||
value. The actual "sign a minimal admin-alert DM with the emergency key when
|
||||
the remote signer is unreachable" path is deferred until the Phase 4 health
|
||||
surface (item 19) is proven in production.
|
||||
- **Tradeoff (documented here and in the config header comment):** off by default.
|
||||
When eventually enabled, it reintroduces the nsec into the agent process
|
||||
*only* for admin-alert fallback DMs. This is a deliberate operator-accepted
|
||||
tradeoff: better to leak the nsec briefly to notify the admin than to fail
|
||||
silently. Operators who prioritize strict key separation leave it empty and
|
||||
rely on the HTTP health endpoint + supervisor logs (item 19) as the
|
||||
out-of-band alert channel.
|
||||
|
||||
**Validation**
|
||||
|
||||
- `make clean && make` builds cleanly with the new `signer_health.c` source.
|
||||
- `./didactyl --help` and `./didactyl --version` unchanged in surface.
|
||||
- `GET /api/status` in local mode returns the new `signer_mode` / `signer_healthy`
|
||||
/ `signer_health_state` / `signer_last_error` / `signer_last_error_ts` /
|
||||
`signer_last_ok_ts` fields (local mode reports `signer_healthy=true` after the
|
||||
startup connectivity check).
|
||||
|
||||
### Phase 5 (deferred) — Embedded n_signer spawn
|
||||
|
||||
Goal: one self-contained didactyl binary that spawns an n_signer in a separate window
|
||||
at startup, for operators who want key separation without running a second process
|
||||
manually.
|
||||
|
||||
Deferred because of the reliability concern above and because it couples didactyl's
|
||||
process model to n_signer's TUI/terminal-attached trust model. Revisit after Phase 4's
|
||||
health/observability story is proven in production with an external n_signer.
|
||||
|
||||
Sketch (not for this round): didactyl forks `n_signer --mnemonic-fd N --socket-name <pinned>`
|
||||
in a new terminal (xterm/foot/etc.), passes the mnemonic over a pipe fd, pins the
|
||||
abstract socket name, and constructs `nostr_signer_nsigner_unix(<pinned name>, ...)`.
|
||||
The pinned socket name avoids discovery races. The trust anchor is still the
|
||||
terminal-attached n_signer process; didactyl is just a supervised spawner.
|
||||
|
||||
---
|
||||
|
||||
## Open decisions (resolved)
|
||||
|
||||
1. **Where does the abstraction live?** Inside `nostr_core_lib`. It is the single
|
||||
chokepoint, it solves the relay-AUTH blocker, and it is reusable. ✓ (already done
|
||||
upstream)
|
||||
2. **How is the n_signer client code sourced into nostr_core_lib?** Already decided
|
||||
upstream: the client (framing + auth envelope + transports) lives in
|
||||
`nostr_core_lib` itself, guarded by `NOSTR_ENABLE_NSIGNER_CLIENT`. No vendoring of
|
||||
n_signer's repo into nostr_core_lib; n_signer keeps depending on nostr_core_lib for
|
||||
leaf crypto. ✓
|
||||
3. **Runtime failure handling.** See Phase 4: out-of-band health (HTTP + logs +
|
||||
supervisor) by default; optional emergency-local-nsec as documented opt-in. ✓
|
||||
|
||||
---
|
||||
|
||||
## Risks & notes
|
||||
|
||||
- **NIP set in `Makefile` vs `build.sh --nips=all` in Dockerfile.** Both must include
|
||||
the NIPs didactyl uses. The signer core (`nostr_signer.c`, `nsigner_*.c`) is compiled
|
||||
unconditionally by upstream `build.sh`, so the nsigner client is available in both
|
||||
build paths. Verify after sync.
|
||||
- **Static link footprint.** nsigner client + transports add a small amount of object
|
||||
code (unix/tcp/serial/fd transports). Negligible vs. the existing websocket/curl/openssl
|
||||
surface. Serial transport pulls in `<termios.h>` — already POSIX, fine for musl.
|
||||
- **Output equivalence.** Upstream's local backend is asserted equivalent by
|
||||
`test_event_creation_equivalence_with_signer`. Phase 1 smoke test should still
|
||||
verify a known event signs to the same id as before.
|
||||
- **`cfg->keys.private_key` lifetime.** In remote mode this buffer is zeroed/unused.
|
||||
Keep the field for `local` mode and for the optional emergency key; do not remove it.
|
||||
- **Hex pubkey plumbing.** The signer's encrypt/decrypt verbs take hex pubkeys and
|
||||
return heap strings. Several didactyl sites currently pass raw 32-byte keys. Each
|
||||
migrated site needs a small hex-encode + free adaptation. Mechanical but touches
|
||||
many files — Phase 3 step 3/4 is the bulk of the work.
|
||||
@@ -1,58 +0,0 @@
|
||||
# Remove Dead DM-History Helper Functions in `agent.c`
|
||||
|
||||
## Background
|
||||
|
||||
While investigating how Didactyl maintains continuity across a DM "chat session", two functions in [`src/agent.c`](../src/agent.c) were found to be compiled-but-unreferenced, marked with `__attribute__((unused))` to suppress compiler warnings:
|
||||
|
||||
- [`append_recent_admin_dm_history`](../src/agent.c:1065)
|
||||
- [`build_recent_admin_dm_history_messages`](../src/agent.c:1800) (wraps the above)
|
||||
|
||||
These were part of an older design where the agent automatically injected the last N admin DM turns into every LLM call. The current architecture is **skill-driven**: DM history only reaches the LLM when an adopted, triggered skill explicitly pulls it in via the `{{dm_history}}` / `{{nostr_dm_history(...)}}` template variables or the `nostr_dm_history` tool (see [`tool_agent.c`](../src/tools/tool_agent.c:284) and [`tools_schema.c`](../src/tools/tools_schema.c:1606)). The dead functions are now a source of confusion for anyone reading the code to understand chat-session behavior.
|
||||
|
||||
## Goal
|
||||
|
||||
Remove the dead functions and any symbols used exclusively by them, without changing runtime behavior.
|
||||
|
||||
## Scope Analysis (verified before planning)
|
||||
|
||||
A repo-wide search for each symbol confirmed the following:
|
||||
|
||||
| Symbol | Only callers | Disposition |
|
||||
|---|---|---|
|
||||
| [`append_recent_admin_dm_history`](../src/agent.c:1065) | called by `build_recent_admin_dm_history_messages` (also dead) | **delete** |
|
||||
| [`build_recent_admin_dm_history_messages`](../src/agent.c:1800) | no callers | **delete** |
|
||||
| [`AGENT_HISTORY_TURNS`](../src/agent.c:38) | referenced only inside the dead `append_recent_admin_dm_history` | **delete** |
|
||||
| [`AGENT_HISTORY_QUERY_LIMIT`](../src/agent.c:39) | defined but zero references repo-wide | **delete** |
|
||||
| [`append_simple_message`](../src/agent.c:187) | many live callers throughout `agent.c` | **keep** |
|
||||
| [`nostr_handler_get_dm_history_json`](../src/nostr_handler.c:4384) | live via [`tool_agent.c`](../src/tools/tool_agent.c:318) | **keep** |
|
||||
|
||||
No non-source references (Makefile, tests, docs) depend on these symbols.
|
||||
|
||||
## Risks
|
||||
|
||||
Very low. Both functions carry `__attribute__((unused))` so they are not linked into any runtime path. The only failure modes are accidental deletion of something still live, which the scope analysis above rules out.
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
1. Clean build with `make` — must succeed with no new warnings.
|
||||
2. Existing tests under [`tests/`](../tests/) pass (`python tests/run_tests.py` or the project's usual invocation).
|
||||
3. Manual sanity check: start an agent, send a DM, confirm normal reply — no regression, since the deleted code was never on the hot path.
|
||||
|
||||
## Mermaid: before/after
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Before
|
||||
A1[agent.c dead funcs] -. unused attr .- B1[compiler silences warning]
|
||||
A1 --> C1[reader assumes auto DM history replay]
|
||||
C1 --> D1[confusion about session behavior]
|
||||
end
|
||||
subgraph After
|
||||
A2[agent.c clean] --> E2[skill-driven history only]
|
||||
E2 --> F2[intent matches code]
|
||||
end
|
||||
```
|
||||
|
||||
## Follow-on (out of scope for this change)
|
||||
|
||||
The documentation in [`docs/CONTEXT.md`](../docs/CONTEXT.md) already describes the skill-driven assembly model correctly, but neither that file nor [`docs/SKILLS.md`](../docs/SKILLS.md) explicitly states "DM history is not automatic — it only appears when a skill references it." A brief clarifying paragraph could be added in a separate change.
|
||||
@@ -1,629 +0,0 @@
|
||||
# Run Skill Architecture — Tradeoff Analysis
|
||||
|
||||
## The Question
|
||||
|
||||
Skills today are **adopted** — their content is injected into the agent's context window for every matching trigger. This is "always on" and costs context tokens on every LLM call, even when the skill's capability isn't needed.
|
||||
|
||||
What if an agent could **use a skill once and be done with it** — like calling a tool?
|
||||
|
||||
Two approaches exist. This document analyzes both.
|
||||
|
||||
---
|
||||
|
||||
## Approach A: Sub-Invocation (Separate LLM Call)
|
||||
|
||||
**Concept:** A `skill_run` tool that spins up an entirely separate LLM execution with the skill's instructions as the system prompt, runs its own tool loop to completion, and returns the result to the calling context.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Admin
|
||||
participant Agent as Agent - Main Context
|
||||
participant SkillRun as skill_run Executor
|
||||
participant LLM2 as LLM - Skill Context
|
||||
|
||||
Admin->>Agent: Hey, summarize this thread
|
||||
Agent->>Agent: I need the summarize-thread skill
|
||||
Agent->>SkillRun: skill_run d_tag=summarize-thread, args=thread_id
|
||||
SkillRun->>SkillRun: Fetch skill content from Nostr
|
||||
SkillRun->>SkillRun: Build isolated context window
|
||||
SkillRun->>LLM2: System: skill instructions, User: args
|
||||
LLM2->>SkillRun: Tool calls + reasoning
|
||||
SkillRun->>SkillRun: Execute tool loop to completion
|
||||
LLM2->>SkillRun: Final text response
|
||||
SkillRun->>Agent: Return result as tool output
|
||||
Agent->>Admin: Here is the summary...
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. The LLM in the main conversation calls `skill_run` with a `d_tag` (or full `kind:pubkey:d_tag` address) and `args`
|
||||
2. The runtime fetches the skill event from Nostr (or adopted skills cache)
|
||||
3. A **new, isolated context window** is built:
|
||||
- System prompt = skill content (with `{{...}}` template variables resolved)
|
||||
- User message = the `args` parameter
|
||||
- Tools = the skill's `requires_tool` tags (or full tool set for own skills)
|
||||
- LLM config = the skill's `llm`, `temperature`, `max_tokens` tags (or agent defaults)
|
||||
4. A **separate tool loop** runs (same pattern as [`agent_on_trigger()`](src/agent.c:1696))
|
||||
5. The final LLM text response is returned as the `skill_run` tool result
|
||||
6. The calling context continues with that result
|
||||
|
||||
### Advantages
|
||||
|
||||
| Advantage | Detail |
|
||||
|-----------|--------|
|
||||
| **Zero context cost when idle** | The skill's instructions never enter the main context window unless invoked |
|
||||
| **Isolation** | The skill runs in its own context — it can't see or interfere with the main conversation |
|
||||
| **Per-skill model** | Each skill can use a different LLM model/provider via its `llm` tag |
|
||||
| **Sandboxing** | External skills can be restricted to safe tools only |
|
||||
| **Composability** | Skills can call other skills via nested `skill_run` (with depth limits) |
|
||||
| **Try-before-adopt** | Run someone else's skill once without permanently adopting it |
|
||||
| **Clean separation** | The skill is a black box — input in, result out |
|
||||
|
||||
### Disadvantages
|
||||
|
||||
| Disadvantage | Detail |
|
||||
|--------------|--------|
|
||||
| **Extra LLM call** | Every `skill_run` invocation costs at least one additional LLM API call (possibly more if the skill uses tools) |
|
||||
| **Latency** | The main conversation blocks while the sub-invocation runs its tool loop |
|
||||
| **No shared context** | The skill doesn't know about the ongoing conversation — it only sees its `args` |
|
||||
| **Complexity** | Requires managing nested execution contexts, preventing infinite recursion, handling timeouts |
|
||||
| **Token overhead** | The skill's system prompt is paid for separately — no sharing with the main context |
|
||||
|
||||
### Existing Design
|
||||
|
||||
This approach is already designed in detail in [`plans/tool_orchestration.md`](plans/tool_orchestration.md) as the `skill_run` tool (Section 2B), including:
|
||||
- Schema definition
|
||||
- Execution flow with sandbox for external skills
|
||||
- Tool safety classification
|
||||
- `/run` slash command for admin direct invocation
|
||||
|
||||
---
|
||||
|
||||
## Approach B: Lazy Injection (On-Demand Context Loading)
|
||||
|
||||
**Concept:** A `skill_load` tool that fetches a skill's content and injects it into the **current** context window mid-conversation. The LLM then uses those instructions in subsequent reasoning within the same call.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Admin
|
||||
participant Agent as Agent - Single Context
|
||||
participant LLM as LLM
|
||||
|
||||
Admin->>Agent: Hey, summarize this thread
|
||||
Agent->>LLM: System + User message
|
||||
LLM->>Agent: I need the summarize-thread skill. Call skill_load.
|
||||
Agent->>Agent: Fetch skill content, append to messages
|
||||
Agent->>LLM: ...previous messages + skill content as new system message
|
||||
LLM->>Agent: Now executing with skill instructions in context...
|
||||
Agent->>Admin: Here is the summary...
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. The LLM calls `skill_load` with a `d_tag`
|
||||
2. The runtime fetches the skill content
|
||||
3. The skill content is returned as the tool result (or injected as a new system message)
|
||||
4. The LLM continues in the **same context window**, now with the skill instructions available
|
||||
5. No separate LLM call — the skill instructions become part of the ongoing conversation
|
||||
|
||||
### Advantages
|
||||
|
||||
| Advantage | Detail |
|
||||
|-----------|--------|
|
||||
| **No extra LLM call** | The skill instructions are loaded into the current context — no sub-invocation overhead |
|
||||
| **Shared context** | The skill has full access to the conversation history and can reason about it |
|
||||
| **Lower latency** | No separate tool loop — the LLM just continues with more information |
|
||||
| **Simpler implementation** | Just fetch content and return it as a tool result — no nested execution management |
|
||||
| **Natural flow** | The LLM decides when it needs more instructions and loads them dynamically |
|
||||
|
||||
### Disadvantages
|
||||
|
||||
| Disadvantage | Detail |
|
||||
|--------------|--------|
|
||||
| **Context window cost** | Once loaded, the skill content stays in the context for the rest of the conversation |
|
||||
| **No isolation** | The skill instructions mix with everything else — potential for instruction conflicts |
|
||||
| **No per-skill model** | The skill runs on whatever model the current conversation is using |
|
||||
| **No sandboxing** | The skill's instructions execute with full tool access (same as the main context) |
|
||||
| **Accumulation** | Loading multiple skills grows the context window — could hit token limits |
|
||||
| **No clean boundary** | The LLM might partially follow skill instructions or blend them with other context |
|
||||
|
||||
### Implementation
|
||||
|
||||
This is essentially what `skill_get` already does — it returns skill content. The difference would be:
|
||||
- `skill_get` returns metadata + content as a JSON blob for the LLM to read
|
||||
- `skill_load` would inject the content as a system-level instruction that the LLM should follow
|
||||
|
||||
In practice, the LLM can already do this today by calling `skill_get` and then reasoning about the returned content. The question is whether a dedicated `skill_load` tool that injects content at the system level would be meaningfully different.
|
||||
|
||||
---
|
||||
|
||||
## Approach C: Hybrid — Both, With Different Use Cases
|
||||
|
||||
The two approaches serve different needs and are not mutually exclusive:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Agent needs a skill capability] --> B{What kind of need?}
|
||||
B -->|One-shot task with clear input/output| C[skill_run - Sub-invocation]
|
||||
B -->|Need skill knowledge in ongoing conversation| D[skill_load - Lazy injection]
|
||||
B -->|Always need this skill| E[skill_adopt - Permanent adoption]
|
||||
|
||||
C --> F[Isolated execution, result returned]
|
||||
D --> G[Instructions loaded into current context]
|
||||
E --> H[Instructions in every matching trigger context]
|
||||
|
||||
style C fill:#e1f5fe
|
||||
style D fill:#fff3e0
|
||||
style E fill:#e8f5e9
|
||||
```
|
||||
|
||||
### When to Use Each
|
||||
|
||||
| Scenario | Best Approach | Why |
|
||||
|----------|--------------|-----|
|
||||
| Run a friend's skill to try it | `skill_run` | Isolation + sandbox for untrusted code |
|
||||
| Spell-check this message | `skill_run` | Clear input/output, no conversation context needed |
|
||||
| Deploy my website | `skill_run` | Task-oriented, uses different tools, benefits from isolation |
|
||||
| I need to know the formatting rules for Nostr posts | `skill_load` | Reference material for the current conversation |
|
||||
| Help me write a skill (load the skill-authoring guide) | `skill_load` | The LLM needs the instructions as ongoing context |
|
||||
| Always respond in a certain personality | `skill_adopt` | Needed in every conversation |
|
||||
|
||||
### The Key Distinction
|
||||
|
||||
- **`skill_run`** = "Do this task for me and give me the result" (function call semantics)
|
||||
- **`skill_load`** = "Teach me how to do this so I can do it myself" (knowledge injection)
|
||||
- **`skill_adopt`** = "I always need to know this" (permanent context)
|
||||
|
||||
---
|
||||
|
||||
## Relationship to Existing Architecture
|
||||
|
||||
### Chain Triggers vs skill_run
|
||||
|
||||
[Chain triggers](plans/multi_model_skill_pipelines.md) already provide sequential skill execution — Skill A completes, then Skill B fires. But chains are **pre-configured** (declared in skill tags) and **event-driven** (fire automatically).
|
||||
|
||||
`skill_run` is **dynamic** — the LLM decides at runtime which skill to invoke based on the conversation. It's the difference between a cron job and a function call.
|
||||
|
||||
### Maturity Levels
|
||||
|
||||
The [`tool_orchestration.md`](plans/tool_orchestration.md) plan defines three maturity levels that apply to `skill_run`:
|
||||
|
||||
| Level | Execution | LLM? |
|
||||
|-------|-----------|------|
|
||||
| `draft` | LLM interprets skill instructions | Yes |
|
||||
| `guided` | LLM with forced tool_choice | Yes, constrained |
|
||||
| `hardened` | Deterministic step executor | No |
|
||||
|
||||
A `hardened` skill via `skill_run` is essentially a **tool** — no LLM call, just sequential tool execution. This is the ultimate "skill as tool" pattern.
|
||||
|
||||
### Recursive skill_run
|
||||
|
||||
`skill_run` can call `skill_run` (a skill invokes another skill). This needs a depth limit (like the chain depth limit of 5 in [`trigger_manager_fire_chains()`](src/trigger_manager.c)). Each level gets its own context window and tool loop.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Considerations
|
||||
|
||||
### For skill_run (Sub-Invocation)
|
||||
|
||||
The core implementation reuses the existing [`agent_on_trigger()`](src/agent.c:1696) pattern:
|
||||
|
||||
1. **New function:** `execute_skill_run()` in [`tool_skill.c`](src/tools/tool_skill.c)
|
||||
2. **Context building:** Reuse [`build_context_from_triggers()`](src/agent.c) pattern but for a single skill
|
||||
3. **Tool loop:** Same pattern as `agent_on_trigger()` — call LLM, execute tools, repeat
|
||||
4. **LLM config:** Save/restore pattern from [`apply_trigger_runtime_to_llm_config()`](src/trigger_manager.c)
|
||||
5. **Sandbox:** Filter tools array based on skill origin (own vs external)
|
||||
6. **Depth tracking:** Static or thread-local counter to prevent infinite recursion
|
||||
7. **Result capture:** Capture the final LLM text response and return it as the tool result
|
||||
|
||||
### For skill_load (Lazy Injection)
|
||||
|
||||
Minimal new code needed:
|
||||
|
||||
1. **New tool:** `skill_load` in [`tool_skill.c`](src/tools/tool_skill.c)
|
||||
2. **Fetch:** Same as `skill_get` — fetch skill content from cache or Nostr
|
||||
3. **Return:** Return the skill content as the tool result with a wrapper indicating these are instructions to follow
|
||||
4. **Alternative:** Inject as a system message in the messages array (requires access to the messages array from within tool execution, which the current architecture doesn't support)
|
||||
|
||||
The simpler version (return as tool result) works today with minimal changes. The LLM receives the skill content as a tool response and can reason about it. The more sophisticated version (system message injection) would require architectural changes to how tools interact with the message array.
|
||||
|
||||
---
|
||||
|
||||
## Approach D: Modes — Skill Packages as Agent Personalities
|
||||
|
||||
### The Roo Code Parallel
|
||||
|
||||
Roo Code has **modes** — Architect, Code, Ask, Debug — each with its own:
|
||||
- System prompt / personality
|
||||
- Set of allowed tools
|
||||
- File access restrictions
|
||||
- Model preferences
|
||||
|
||||
A mode is essentially a **curated bundle of capabilities** that the agent switches into. You don't load the Architect's planning instructions into the Code mode's context — you *switch modes entirely*.
|
||||
|
||||
This maps directly onto Didactyl's skill system.
|
||||
|
||||
### Modes as Skill Packages
|
||||
|
||||
A **mode** is a named collection of skills that, when activated, replaces the agent's current context window composition. It's a different adoption list — a different set of Layer 1 skills, different tools, potentially a different LLM model.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Agent receives /mode architect] --> B[Look up mode: architect]
|
||||
B --> C[Mode defines skills: planning, analysis, design-patterns]
|
||||
C --> D[Mode defines model: best]
|
||||
C --> E[Mode defines tools: memory_*, nostr_query, skill_search]
|
||||
D --> F[Reconfigure agent context for this session]
|
||||
E --> F
|
||||
F --> G[Agent now operates as Architect]
|
||||
G --> H[All subsequent messages use architect context]
|
||||
```
|
||||
|
||||
### Mode Definition as a Skill
|
||||
|
||||
A mode is itself a skill — a meta-skill that declares which other skills compose it:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": 31123,
|
||||
"content": "You are operating in Architect mode.\n\nYour role is to plan, design, and analyze before implementation.\n- Break down complex problems into clear steps\n- Create technical specifications\n- Design system architecture\n- Do NOT write implementation code\n\n{{identity}}\n{{planning-guidelines}}",
|
||||
"tags": [
|
||||
["d", "mode-architect"],
|
||||
["description", "Architect mode - planning and design"],
|
||||
["mode", "architect"],
|
||||
["llm", "best"],
|
||||
["temperature", "0.7"],
|
||||
["requires_skill", "identity"],
|
||||
["requires_skill", "planning-guidelines"],
|
||||
["requires_tool", "memory_save"],
|
||||
["requires_tool", "memory_recall"],
|
||||
["requires_tool", "nostr_query"],
|
||||
["requires_tool", "skill_search"],
|
||||
["requires_tool", "skill_list"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": 31123,
|
||||
"content": "You are operating in Cheap mode.\n\nYou are a fast, cost-effective assistant.\n- Answer questions directly and concisely\n- Use minimal tokens\n- Do not over-explain\n\n{{identity}}",
|
||||
"tags": [
|
||||
["d", "mode-cheap"],
|
||||
["description", "Cheap mode - fast answers with inexpensive model"],
|
||||
["mode", "cheap"],
|
||||
["llm", "openai/gpt-4o-mini, cheap"],
|
||||
["temperature", "0.3"],
|
||||
["max_tokens", "500"],
|
||||
["requires_skill", "identity"],
|
||||
["requires_tool", "nostr_query"],
|
||||
["requires_tool", "memory_recall"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### How Modes Interact with skill_run
|
||||
|
||||
Your example — `/skill_run cheap "how many files are in the directory"` — reveals two distinct use cases:
|
||||
|
||||
**1. One-shot mode invocation (`skill_run`):**
|
||||
Run a single task using a mode's configuration, then return to normal. The mode's model, tools, and personality apply for just that one execution.
|
||||
|
||||
```
|
||||
/skill_run mode-cheap "how many files are in the directory"
|
||||
```
|
||||
|
||||
This uses the `skill_run` sub-invocation pattern: spin up an isolated context with the `mode-cheap` skill's instructions, use its `llm` tag (cheap model), execute the tool loop, return the result. The main conversation continues unchanged.
|
||||
|
||||
**2. Persistent mode switch (`/mode`):**
|
||||
Switch the agent's operating mode for all subsequent messages until switched again.
|
||||
|
||||
```
|
||||
/mode architect
|
||||
> Now operating in Architect mode. Using best model. Planning tools available.
|
||||
|
||||
/mode cheap
|
||||
> Now operating in Cheap mode. Using gpt-4o-mini. Minimal tool set.
|
||||
|
||||
/mode default
|
||||
> Restored default mode.
|
||||
```
|
||||
|
||||
This changes which skills compose the agent's context window for DM conversations. Instead of the normal adoption-list-order composition, the mode's skill set takes over.
|
||||
|
||||
### Mode Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Current Architecture
|
||||
A[Adoption List - kind 10123] --> B[All adopted skills with dm trigger]
|
||||
B --> C[Context window for every DM]
|
||||
end
|
||||
|
||||
subgraph Mode Architecture
|
||||
D[Adoption List - kind 10123] --> E{Active mode?}
|
||||
E -->|No mode| F[Default: all adopted dm-triggered skills]
|
||||
E -->|Mode active| G[Mode skill + its requires_skill dependencies]
|
||||
F --> H[Context window]
|
||||
G --> H
|
||||
end
|
||||
|
||||
subgraph Mode Lifecycle
|
||||
I[/mode architect] --> J[Set active mode = mode-architect]
|
||||
J --> K[Subsequent DMs use architect context]
|
||||
K --> L[/mode default]
|
||||
L --> M[Clear active mode, restore normal]
|
||||
end
|
||||
```
|
||||
|
||||
### The Three Invocation Patterns (Updated)
|
||||
|
||||
| Pattern | Command | Context | Persists? | Model |
|
||||
|---------|---------|---------|-----------|-------|
|
||||
| **Adopted skills** | *(automatic)* | All matching skills in adoption list | Always | Agent default |
|
||||
| **One-shot skill** | `/skill_run d_tag args` | Isolated sub-invocation | No | Skill's `llm` tag |
|
||||
| **Mode switch** | `/mode name` | Mode's skill set replaces DM context | Until changed | Mode's `llm` tag |
|
||||
| **One-shot mode** | `/skill_run mode-name args` | Isolated sub-invocation with mode config | No | Mode's `llm` tag |
|
||||
|
||||
### Mode Storage
|
||||
|
||||
The active mode is ephemeral runtime state — not persisted to Nostr. It's a session concept:
|
||||
|
||||
```c
|
||||
// In agent.c or a new mode_manager
|
||||
static char g_active_mode_d_tag[TRIGGER_SKILL_D_TAG_MAX] = {0}; // empty = default mode
|
||||
|
||||
void agent_set_mode(const char* mode_d_tag); // NULL or empty = clear mode
|
||||
const char* agent_get_mode(void);
|
||||
```
|
||||
|
||||
When a mode is active, [`build_context_from_triggers()`](src/agent.c) checks the mode skill first instead of scanning all DM-triggered skills. The mode skill's `requires_skill` tags define which other skills are included (Layer 2).
|
||||
|
||||
### Mode Discovery
|
||||
|
||||
Modes are just skills with a `["mode", "name"]` tag. They're discoverable the same way:
|
||||
|
||||
```
|
||||
/skill_search mode # find all mode skills
|
||||
/skill_run mode-architect "plan X" # try a mode one-shot
|
||||
/mode architect # switch to it persistently
|
||||
```
|
||||
|
||||
Since modes are Nostr events, they're **shareable**. Someone publishes a "code-reviewer" mode with specific skills, model preferences, and tool restrictions. You adopt it and `/mode code-reviewer` to activate it.
|
||||
|
||||
### Relationship to Roo Code Modes
|
||||
|
||||
| Roo Code | Didactyl Equivalent |
|
||||
|----------|-------------------|
|
||||
| Mode definition (`.roomodes`) | Skill event with `["mode", "name"]` tag |
|
||||
| Mode's system prompt | Skill content (markdown instructions) |
|
||||
| Mode's allowed tools | `["requires_tool", "..."]` tags |
|
||||
| Mode's file restrictions | `["requires_tool", "local_file_read"]` presence/absence |
|
||||
| Mode's model | `["llm", "..."]` tag |
|
||||
| `/mode architect` | `/mode architect` |
|
||||
| Mode switching | `/mode name` slash command |
|
||||
| Custom modes | Create a skill with `["mode", "name"]` tag |
|
||||
|
||||
The key difference: Roo Code modes are local config files. Didactyl modes are **Nostr events** — portable, shareable, discoverable, and adoptable across agents.
|
||||
|
||||
---
|
||||
|
||||
## Modes vs. Multiple Agents
|
||||
|
||||
### The Tradeoff
|
||||
|
||||
Instead of switching one agent between Architect and Coder modes, you could run two separate agents — one Architect, one Coder. Each has its own nsec, its own skills, its own model. This is already possible today with no code changes.
|
||||
|
||||
| Dimension | Modes (one agent) | Multiple Agents (separate binaries) |
|
||||
|-----------|-------------------|-------------------------------------|
|
||||
| **Resource cost** | One process, one relay connection, one LLM config | N processes, N relay connections, N LLM configs |
|
||||
| **Context isolation** | Mode switch replaces context; previous mode's state is lost | Each agent has its own persistent context and memory |
|
||||
| **Shared memory** | Same agent = same memory store. Architect's notes are available to Coder. | Separate agents = separate memory. Must explicitly share via Nostr events. |
|
||||
| **Conversation continuity** | Mode switch mid-conversation is seamless — same DM thread | Different agents = different DM threads. Admin must context-switch. |
|
||||
| **Identity** | One npub. External observers see one entity. | N npubs. Each agent is a distinct Nostr identity. |
|
||||
| **Specialization depth** | Mode is a context overlay — personality + tools + model. Shallow specialization. | Full agent is deeply specialized — its own memories, its own learned patterns, its own social graph. |
|
||||
| **Operational complexity** | One binary, one systemd service, one genesis config | N binaries, N services, N configs, N API keys |
|
||||
| **Failure blast radius** | Agent crashes = all modes unavailable | One agent crashes = others still running |
|
||||
| **Skill sharing** | All modes share the same adoption list (modes just filter it) | Each agent has its own adoption list |
|
||||
| **Admin UX** | `/mode architect` — instant switch | DM a different npub — requires client support or manual switching |
|
||||
|
||||
### When Modes Win
|
||||
|
||||
- **Quick context switching** — "plan this, then code it" in one conversation
|
||||
- **Shared state** — the Architect's plan is in memory when you switch to Coder mode
|
||||
- **Low overhead** — one process, one set of relay connections
|
||||
- **Simple admin UX** — `/mode architect`, `/mode coder`, done
|
||||
- **Cost control** — `/mode cheap` for quick questions, `/mode deep` for complex analysis
|
||||
|
||||
### When Separate Agents Win
|
||||
|
||||
- **Deep specialization** — an agent that has spent weeks learning about your codebase vs. one that knows your financial data. These are fundamentally different knowledge bases.
|
||||
- **Different trust boundaries** — one agent has shell access, another only has Nostr access
|
||||
- **Different LLM providers** — one on Anthropic, one on OpenAI, one on local Ollama
|
||||
- **Concurrent operation** — both agents working simultaneously on different tasks
|
||||
- **Fault isolation** — one agent's crash doesn't affect the other
|
||||
- **Team simulation** — the admin can DM the Architect to plan, then DM the Coder to implement, and each agent maintains its own persistent context about its domain
|
||||
|
||||
### The Hybrid: Modes + Clones
|
||||
|
||||
The most powerful pattern combines both:
|
||||
|
||||
1. **Start with one agent** with modes for quick context switching
|
||||
2. **Clone it** (via [`agent_clone`](plans/agent_clone.md)) when you need deep specialization
|
||||
3. **Specialize the clone** — different skills, different model, different tools
|
||||
4. **Use modes within each clone** for further flexibility
|
||||
|
||||
```
|
||||
Admin's Agent Fleet:
|
||||
├── Agent Alpha (generalist)
|
||||
│ ├── /mode architect — planning
|
||||
│ ├── /mode coder — implementation
|
||||
│ └── /mode cheap — quick questions
|
||||
├── Agent Beta (clone, specialized: infrastructure)
|
||||
│ ├── /mode deploy — deployment workflows
|
||||
│ └── /mode monitor — system monitoring
|
||||
└── Agent Gamma (clone, specialized: content)
|
||||
├── /mode writer — long-form content
|
||||
└── /mode social — Nostr social engagement
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-Agent Didactyl: What Would It Take?
|
||||
|
||||
### Current Single-Agent Architecture
|
||||
|
||||
The codebase is deeply single-agent. Global state is everywhere:
|
||||
|
||||
| Global | File | Purpose |
|
||||
|--------|------|---------|
|
||||
| `static didactyl_config_t* g_cfg` | [`agent.c`](src/agent.c:27), [`nostr_handler.c`](src/nostr_handler.c:22) | Agent config including keys |
|
||||
| `static tools_context_t g_tools_ctx` | [`agent.c`](src/agent.c:28) | Tool execution context |
|
||||
| `static trigger_manager* g_trigger_manager` | [`agent.c`](src/agent.c:29) | Trigger state |
|
||||
| `static nostr_relay_pool_t* g_pool` | [`nostr_handler.c`](src/nostr_handler.c:23) | Single relay pool |
|
||||
| `static llm_config_t g_cfg` | [`llm.c`](src/llm.c:20) | LLM configuration |
|
||||
| `static char* g_admin_kind0_json` | [`nostr_handler.c`](src/nostr_handler.c:34) | Admin context cache |
|
||||
| `static char* g_agent_kind0_json` | [`nostr_handler.c`](src/nostr_handler.c:47) | Agent self-context cache |
|
||||
| `static char* g_memory_plain` | [`tool_memory.c`](src/tools/tool_memory.c:20) | Agent memory |
|
||||
| 20+ `pthread_mutex_t` globals | Various | Thread safety for all the above |
|
||||
|
||||
Every module assumes there is exactly one agent identity, one relay pool, one LLM config, one admin, one set of keys. The keys are used directly via `g_cfg->keys.private_key` in ~40 places across [`nostr_handler.c`](src/nostr_handler.c) for signing, encrypting, and decrypting.
|
||||
|
||||
### Three Paths to Multi-Agent
|
||||
|
||||
#### Path 1: Multiple Processes (Current — No Code Changes)
|
||||
|
||||
Run N separate Didactyl binaries, each with its own genesis config and nsec. This is what [`DECENTRALIZED_DIDACTYL.md`](plans/DECENTRALIZED_DIDACTYL.md) describes.
|
||||
|
||||
**Pros:** Works today. Zero code changes. Full isolation.
|
||||
**Cons:** N × resource cost. N × operational complexity. No shared state.
|
||||
|
||||
```
|
||||
Server
|
||||
├── /home/didactyl-architect/ (systemd: didactyl-architect.service)
|
||||
│ ├── genesis.jsonc (nsec1..., model: best)
|
||||
│ └── didactyl (binary)
|
||||
├── /home/didactyl-coder/ (systemd: didactyl-coder.service)
|
||||
│ ├── genesis.jsonc (nsec2..., model: fast)
|
||||
│ └── didactyl (binary)
|
||||
└── /home/didactyl-cheap/ (systemd: didactyl-cheap.service)
|
||||
├── genesis.jsonc (nsec3..., model: cheap)
|
||||
└── didactyl (binary)
|
||||
```
|
||||
|
||||
#### Path 2: Multi-Agent Single Process (Major Refactor)
|
||||
|
||||
Refactor all global state into an `agent_instance_t` struct. Run multiple agent instances in one process, sharing the relay pool and HTTP server.
|
||||
|
||||
**What changes:**
|
||||
|
||||
```c
|
||||
typedef struct agent_instance {
|
||||
didactyl_config_t config;
|
||||
tools_context_t tools_ctx;
|
||||
trigger_manager_t trigger_manager;
|
||||
llm_config_t llm_config;
|
||||
// ... all per-agent state
|
||||
} agent_instance_t;
|
||||
|
||||
// Instead of:
|
||||
static didactyl_config_t* g_cfg;
|
||||
// Becomes:
|
||||
agent_instance_t* agents;
|
||||
int agent_count;
|
||||
```
|
||||
|
||||
**Scope of refactor:**
|
||||
- Every function that touches `g_cfg`, `g_tools_ctx`, `g_trigger_manager`, or any global state needs an `agent_instance_t*` parameter
|
||||
- [`nostr_handler.c`](src/nostr_handler.c) — the largest file (~4500 lines) — uses `g_cfg->keys` in ~40 places. Every signing, encryption, and subscription call needs to know which agent's keys to use.
|
||||
- [`llm.c`](src/llm.c) — the global `g_cfg` LLM config needs to become per-instance
|
||||
- [`agent.c`](src/agent.c) — all context building, message handling, tool loops need per-instance state
|
||||
- DM routing — incoming DMs need to be dispatched to the correct agent based on the `#p` tag
|
||||
- The HTTP API needs agent-scoped endpoints (`/api/agents/:id/prompt`, etc.)
|
||||
|
||||
**Estimated scope:** Touch every `.c` file. ~2000-3000 lines of refactoring. High risk of regressions.
|
||||
|
||||
**Pros:** Shared relay pool (fewer connections). Shared HTTP server. Single process to manage.
|
||||
**Cons:** Massive refactor. High regression risk. Shared-process failure mode (one agent's crash kills all).
|
||||
|
||||
#### Path 3: Multi-Agent via Modes (Minimal Code — Recommended First Step)
|
||||
|
||||
Modes give you 80% of the multi-agent benefit with 5% of the effort:
|
||||
|
||||
- **Different personality per mode** — mode skill content
|
||||
- **Different model per mode** — mode's `llm` tag
|
||||
- **Different tools per mode** — mode's `requires_tool` tags
|
||||
- **Different temperature per mode** — mode's `temperature` tag
|
||||
- **Instant switching** — `/mode architect`
|
||||
- **Shared memory** — same agent, same memory store
|
||||
- **Shared identity** — same npub, same social graph
|
||||
|
||||
What you lose vs. true multi-agent:
|
||||
- No concurrent operation (one mode at a time)
|
||||
- No independent persistent context per mode
|
||||
- No fault isolation between modes
|
||||
|
||||
**This is the Roo Code model** — and it works extremely well for the single-admin use case.
|
||||
|
||||
### Recommendation
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Start here] --> B[Implement modes - skill packages]
|
||||
B --> C{Need concurrent agents?}
|
||||
C -->|No| D[Modes are sufficient]
|
||||
C -->|Yes| E{How many?}
|
||||
E -->|2-3| F[Multiple processes - Path 1]
|
||||
E -->|Many| G{Shared resources matter?}
|
||||
G -->|No| F
|
||||
G -->|Yes| H[Multi-agent refactor - Path 2]
|
||||
|
||||
style B fill:#e8f5e9
|
||||
style D fill:#e8f5e9
|
||||
style F fill:#fff3e0
|
||||
style H fill:#ffebee
|
||||
```
|
||||
|
||||
1. **Now:** Implement modes (skill packages). This covers the "Architect vs Coder" use case with minimal code.
|
||||
2. **When needed:** Run multiple Didactyl processes for true concurrent agents. Already works, just operational overhead.
|
||||
3. **If justified:** Multi-agent single-process refactor. Only if you're running 5+ agents on one server and the resource overhead matters.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Layers
|
||||
|
||||
The features build on each other in this order:
|
||||
|
||||
### Layer 1: `skill_run` (Sub-Invocation)
|
||||
Already designed in [`tool_orchestration.md`](plans/tool_orchestration.md). This is the foundation — an isolated LLM execution of any skill. Enables one-shot skill usage, external skill testing, and sandboxing.
|
||||
|
||||
**Enables:** `/skill_run cheap "how many files are in the directory"`
|
||||
|
||||
### Layer 2: Slash Commands
|
||||
Also in [`tool_orchestration.md`](plans/tool_orchestration.md). Direct tool execution via `/tool_name args`. Required for `/mode` and `/skill_run` as admin commands.
|
||||
|
||||
**Enables:** `/skill_run`, `/mode`, `/help`
|
||||
|
||||
### Layer 3: Modes
|
||||
Mode skills with `["mode", "name"]` tags. `/mode` slash command to switch. Runtime tracks active mode and adjusts context composition.
|
||||
|
||||
**Enables:** `/mode architect`, `/mode cheap`, persistent mode switching
|
||||
|
||||
### Layer 4: Hardened Skills
|
||||
Deterministic step executor for skills that don't need LLM reasoning. A mode could include hardened skills as tools.
|
||||
|
||||
**Enables:** Skills that execute as pure tool sequences — zero LLM cost
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
|
||||
Implement in this order:
|
||||
|
||||
1. **`skill_run`** (sub-invocation) — the foundation. Already designed. Enables one-shot skill execution with isolated context, per-skill model, and sandboxing.
|
||||
|
||||
2. **Slash commands** (`/skill_run`, `/help`) — admin direct invocation. Required for the mode UX.
|
||||
|
||||
3. **Modes** (`/mode name`) — skill packages that reconfigure the agent's context. Builds on `skill_run` for one-shot mode usage and slash commands for persistent switching.
|
||||
|
||||
4. **Hardened skill execution** — deterministic tool sequences. The ultimate "skill as tool" pattern with zero LLM cost.
|
||||
@@ -1,285 +0,0 @@
|
||||
# Startup Failure Admin DM Notification
|
||||
|
||||
> **STATUS UPDATE (follow-up phase):** The base feature (send a failure DM) is
|
||||
> implemented and verified on VM410. However, because the failure happens inside the
|
||||
> systemd `Restart=on-failure` loop (`RestartSec=10`), the admin now receives a *new*
|
||||
> failure DM roughly every ~25 seconds, indefinitely (observed in the live message box).
|
||||
> This follow-up adds **throttling/de-duplication** so the failure DM is sent **once per
|
||||
> failure type until the next successful startup**, and makes the message **more
|
||||
> descriptive** (per-relay connect state + the event kinds that were needed).
|
||||
> See the "Follow-up: Throttling + Descriptive Failure DM" section at the end.
|
||||
|
||||
# Startup Failure Admin DM Notification (original plan)
|
||||
|
||||
## Goal
|
||||
|
||||
When the agent fails to start, send a best-effort encrypted DM to the administrator
|
||||
describing which startup step failed and why, so the operator learns about the failure
|
||||
without having to SSH in and read `journalctl`.
|
||||
|
||||
This directly addresses the real-world incident on VM410 where both `didactyl.service`
|
||||
and `simon.service` were stuck restart-looping at startup step 14
|
||||
(`Subscribe self-skill cache: failed (no skill events found after EOSE)`) and silently
|
||||
stopped answering DMs.
|
||||
|
||||
## Background / Code References
|
||||
|
||||
- Startup is a linear checklist in [`main()`](src/main.c:1012). Each step uses helpers:
|
||||
- [`startup_step_begin()`](src/main.c:303)
|
||||
- [`startup_step_ok()`](src/main.c:309)
|
||||
- [`startup_step_fail()`](src/main.c:320)
|
||||
- On any fatal failure, the current pattern is:
|
||||
`startup_step_fail(...)` -> `fprintf(stderr, ...)` -> cleanup calls -> `return 1;`
|
||||
(e.g. the step-14 self-skill abort at [`src/main.c:1520`](src/main.c:1520)).
|
||||
- The success path already sends an admin "started up" DM at
|
||||
[`src/main.c:1663`](src/main.c:1663) via
|
||||
[`nostr_handler_send_dm_auto()`](src/nostr_handler.c:3264).
|
||||
- DM delivery requires (see [`nostr_handler_send_dm_with_role()`](src/nostr_handler.c:3133)):
|
||||
- `g_cfg` and `g_pool` set (relay pool initialized),
|
||||
- at least one **connected** relay (otherwise the kind-4 event is dropped:
|
||||
"kind 4 event not queued: no connected relays" at [`src/nostr_handler.c:3203`](src/nostr_handler.c:3203)),
|
||||
- a valid `cfg.admin.pubkey` (validated at step 4, [`src/main.c:1310`](src/main.c:1310)),
|
||||
- the private key derived (always done before the checklist).
|
||||
|
||||
### Delivery-window reality
|
||||
|
||||
A failure DM is only *deliverable* from roughly step 11 onward (relay pool created +
|
||||
relays connected + admin known). The step-14 failure we actually hit is inside that
|
||||
window, so it will be delivered. Earlier failures (bad nsec, missing admin, LLM config,
|
||||
no relay connection) have no working channel; per decision, those are **logged only**
|
||||
(best-effort, no blocking retries).
|
||||
|
||||
## Design Decision (confirmed)
|
||||
|
||||
**Best-effort only.** Attempt the failure DM whenever relays + admin are available; if
|
||||
the relay pool isn't connected or admin pubkey is unknown, skip the DM and rely on
|
||||
existing stderr/journal logging. No blocking retry loop, no self-healing of the
|
||||
underlying failure (that can be a separate plan).
|
||||
|
||||
## Approach
|
||||
|
||||
Add a single helper that builds and sends the failure DM, then call it from every fatal
|
||||
startup-abort site (the points that currently do `startup_step_fail(...) ... return 1;`).
|
||||
|
||||
### 1. New helper in [`src/main.c`](src/main.c)
|
||||
|
||||
Add a static function near the other startup helpers (after
|
||||
[`startup_step_fail()`](src/main.c:320)):
|
||||
|
||||
```c
|
||||
static void notify_admin_startup_failure(const didactyl_config_t* cfg,
|
||||
int step,
|
||||
const char* label,
|
||||
const char* detail);
|
||||
```
|
||||
|
||||
Behavior:
|
||||
- Guard clauses (skip + DEBUG_WARN if any precondition is missing):
|
||||
- `cfg == NULL` or `cfg->admin.pubkey[0] == '\0'` -> log "admin unknown; cannot notify".
|
||||
- `nostr_handler_connected_relay_count() <= 0` -> log "no connected relay; cannot notify".
|
||||
- Build a concise human-readable message, e.g.:
|
||||
```
|
||||
<DisplayName> FAILED to start at <YYYY-MM-DD HH:MM:SS>.
|
||||
Startup step [14] Subscribe self-skill cache failed: no skill events found after EOSE.
|
||||
Version <X>. Connected relays: <n>/<m>. The agent is NOT online and will not answer DMs until fixed.
|
||||
```
|
||||
- Reuse `nostr_handler_get_startup_display_name()` and `DIDACTYL_VERSION` exactly like
|
||||
the success DM at [`src/main.c:1645`](src/main.c:1645) for naming/version consistency.
|
||||
- Include `step`, `label`, and `detail` so each failure site is self-describing.
|
||||
- Send via `nostr_handler_send_dm_auto(cfg->admin.pubkey, msg)`; if it returns non-zero,
|
||||
`DEBUG_WARN` that the failure DM could not be delivered (do not change the exit code).
|
||||
- Pump the poll loop briefly after sending so the async publish can flush before process
|
||||
exit: call `nostr_handler_poll(...)` a few times (e.g. ~300--500 ms total) so the
|
||||
queued kind-4 event is actually written to the relay sockets before cleanup/`return 1`.
|
||||
(Without this, async publish may be discarded on immediate exit.)
|
||||
|
||||
### 2. Wire the helper into fatal abort sites
|
||||
|
||||
For each fatal `startup_step_fail(...)` that ends in `return 1;`, insert a call to
|
||||
`notify_admin_startup_failure(&cfg, <step>, "<label>", "<detail>")` immediately after the
|
||||
`startup_step_fail(...)` line and before the cleanup/`return 1`.
|
||||
|
||||
Priority sites (inside the deliverable window — relay pool up + admin known):
|
||||
- Step 11 — discover self relay list failures
|
||||
([`src/main.c:1420`](src/main.c:1420), [`src/main.c:1437`](src/main.c:1437)).
|
||||
- Step 14 — self-skill cache failures (the incident we hit):
|
||||
- subscribe failed ([`src/main.c:1481`](src/main.c:1481)),
|
||||
- EOSE timeout ([`src/main.c:1496`](src/main.c:1496)),
|
||||
- no skill events after EOSE ([`src/main.c:1521`](src/main.c:1521)).
|
||||
- Step 15 — Subscribe DMs failed ([`src/main.c:1552`](src/main.c:1552)).
|
||||
|
||||
Earlier-window sites (relay/admin may not be ready — helper will self-skip and just log,
|
||||
but we still call it uniformly for completeness):
|
||||
- Step 1/3/4/5/6/7/8/9 fatal aborts. The helper's guard clauses make these safe no-ops
|
||||
when delivery isn't possible.
|
||||
|
||||
Keep the existing `fprintf(stderr, ...)` and cleanup unchanged; the DM is purely additive
|
||||
and must never alter the exit code or cleanup ordering.
|
||||
|
||||
### 3. Message size / safety
|
||||
|
||||
- Use a fixed stack buffer (e.g. `char msg[768];`) like the success DM at
|
||||
[`src/main.c:1644`](src/main.c:1644).
|
||||
- `snprintf` with truncation is acceptable; `detail` strings are short and controlled.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- No blocking retry / wait-for-relay loop (explicitly declined).
|
||||
- No auto-remediation of the step-14 self-skill cause (e.g. auto-publishing the default
|
||||
skill). Tracked separately if desired.
|
||||
- No change to `deploy_lt.sh` post-deploy health checks (could be a follow-up: have the
|
||||
deploy script verify each service reaches `READY` after restart).
|
||||
|
||||
## Verification
|
||||
|
||||
1. Build the static binary (`build_static.sh`) and confirm no warnings.
|
||||
2. Local reproduction of step-14 failure: run an `--nsec`-only agent for an identity that
|
||||
has connected relays but **no** published skill events, confirm:
|
||||
- startup still aborts with exit code 1 (unchanged behavior),
|
||||
- the admin receives a DM naming step 14 and the "no skill events found after EOSE"
|
||||
detail.
|
||||
3. Negative case: kill relay connectivity (or use an unreachable relay) and confirm the
|
||||
helper logs "no connected relay; cannot notify" and does not crash or hang.
|
||||
4. Confirm the success-path startup DM at [`src/main.c:1663`](src/main.c:1663) is
|
||||
unaffected.
|
||||
5. Deploy to VM410, restart `didactyl.service` / `simon.service`, and confirm the admin
|
||||
npub receives the failure DM while the skill events are still missing; then publish a
|
||||
skill and confirm normal `READY` + startup DM resumes.
|
||||
|
||||
---
|
||||
|
||||
# Follow-up: Throttling + Descriptive Failure DM
|
||||
|
||||
## Problems observed in production
|
||||
|
||||
1. **DM flood.** With `Restart=on-failure` / `RestartSec=10`, the step-14 failure recurs
|
||||
on every restart and the agent reaches the deliverable window each time, so the admin
|
||||
gets a fresh failure DM every ~25s indefinitely (confirmed in the live message box).
|
||||
2. **Insufficient detail.** The current message names the step + detail, but not which
|
||||
relays were attempted, whether each connected, nor which event kinds were required.
|
||||
|
||||
## Decisions (confirmed with user)
|
||||
|
||||
- **Throttle semantics:** send exactly once per failure type (keyed by step) until the
|
||||
agent next starts successfully. The marker is cleared on `READY`. No periodic
|
||||
reminders. A different failing step is a different "type" and notifies once.
|
||||
- **Descriptive content:** include the attempted relay list with per-relay connect state,
|
||||
and the event kinds that step 14 needs (31123 / 31124 skill events; 10123 adoption list).
|
||||
|
||||
## Part A -- Throttle / de-duplicate via on-disk marker
|
||||
|
||||
### Marker file location (systemd-sandbox safe)
|
||||
|
||||
- The unit runs with `WorkingDirectory=/home/<user>` and (hardened variant)
|
||||
`ReadWritePaths=/home/<user>`, so only the agent home is writable.
|
||||
- `cfg.tools.shell.working_directory` defaults to `"."` (see
|
||||
[`config.c`](src/config.c:1459)), which resolves to the home dir at runtime.
|
||||
- Use a hidden marker in the working directory: `./.didactyl_startup_failure`.
|
||||
- For `didactyl` -> `/home/didactyl/.didactyl_startup_failure`
|
||||
- For `simon` -> `/home/simon/.didactyl_startup_failure`
|
||||
- Both inside the writable `ReadWritePaths`, so writes succeed under the sandbox.
|
||||
- Resolve the directory the same way the local-file tools do: prefer
|
||||
`cfg->tools.shell.working_directory` when non-empty, else `"."`
|
||||
(mirror [`tool_local.c`](src/tools/tool_local.c:49)).
|
||||
|
||||
### Marker contents
|
||||
|
||||
Plain text, one record, easy to parse and inspect manually:
|
||||
|
||||
- `step=14`
|
||||
- `label=Subscribe self-skill cache`
|
||||
- `detail=no skill events found after EOSE`
|
||||
- `last_sent_epoch=1781314692`
|
||||
|
||||
(Only `step` is strictly needed for the de-dup key; the rest aids debugging.)
|
||||
|
||||
### New helpers in [`src/main.c`](src/main.c)
|
||||
|
||||
Add small static helpers near
|
||||
[`notify_admin_startup_failure()`](src/main.c:331):
|
||||
|
||||
- `startup_failure_marker_path(cfg, out, out_size)` -- builds
|
||||
`<workdir>/.didactyl_startup_failure`.
|
||||
- `startup_failure_already_notified(cfg, step)` -- returns 1 if the marker exists AND its
|
||||
`step=` matches the current `step` (suppress); 0 otherwise (allow). Missing/unreadable
|
||||
marker -> 0 (allow).
|
||||
- `startup_failure_marker_write(cfg, step, label, detail)` -- writes/overwrites the marker
|
||||
after an attempted send.
|
||||
- `startup_failure_marker_clear(cfg)` -- `remove()` the marker; ignore ENOENT.
|
||||
|
||||
### Wiring
|
||||
|
||||
- In [`notify_admin_startup_failure()`](src/main.c:331): after the existing guard
|
||||
clauses (config/admin/relay), check `startup_failure_already_notified(cfg, step)`.
|
||||
If it returns 1, log a DEBUG_INFO suppression line and return without sending.
|
||||
- On a successful send, call `startup_failure_marker_write(cfg, step, label, detail)`.
|
||||
- If the same step keeps failing, only the first restart in the episode sends a DM;
|
||||
subsequent restarts find the matching marker and stay silent.
|
||||
- If a different step fails later, the `step=` mismatch allows exactly one new DM and
|
||||
rewrites the marker with the new step.
|
||||
- On the success path, clear the marker so the next failure episode notifies again. Add
|
||||
`startup_failure_marker_clear(&cfg);` next to the READY success DM at
|
||||
[`src/main.c:1663`](src/main.c:1663).
|
||||
|
||||
### Edge cases
|
||||
|
||||
- Marker write failure (e.g. read-only FS): log DEBUG_WARN and continue. Worst case we
|
||||
fall back to current behavior (may re-send on next restart) -- acceptable, never fatal.
|
||||
- Two services share the binary but use different home dirs, so markers never collide.
|
||||
|
||||
## Part B -- Descriptive failure message
|
||||
|
||||
Enhance the message built in
|
||||
[`notify_admin_startup_failure()`](src/main.c:331) to append:
|
||||
|
||||
1. **Attempted relays + per-relay state.** Add a compact accessor to the nostr handler:
|
||||
- New `nostr_handler_relay_state_summary(void)` in
|
||||
[`nostr_handler.c`](src/nostr_handler.c) / [`nostr_handler.h`](src/nostr_handler.h)
|
||||
returning a caller-freed compact string like
|
||||
`relay.laantungir.net=connected; relay.damus.io=connected; relay.primal.net=connecting`.
|
||||
- Implement by reusing the existing loop over `g_cfg->relays[]` +
|
||||
`nostr_relay_pool_get_relay_status()` + `relay_status_str()` (patterns at
|
||||
[`src/nostr_handler.c:3183`](src/nostr_handler.c:3183) and
|
||||
[`src/nostr_handler.c:419`](src/nostr_handler.c:419)).
|
||||
- Preferred over embedding raw JSON from
|
||||
[`nostr_handler_relay_status_json()`](src/nostr_handler.c:3971) to keep the DM short.
|
||||
2. **Needed event kinds (step-specific).** For step 14 append a fixed clause naming the
|
||||
required events: kind 31123/31124 (skills) and kind 10123 (adoption list). Implement as
|
||||
a small `startup_step_needed_events(step)` lookup in [`src/main.c`](src/main.c) that
|
||||
returns a clause for known steps (14, 11, ...) and empty for others.
|
||||
|
||||
### Resulting message shape (example)
|
||||
|
||||
- `Didactyl FAILED to start at 2026-06-13 10:38:13.`
|
||||
- `Startup step [14] Subscribe self-skill cache failed: no skill events found after EOSE.`
|
||||
- `Version v0.2.48. Connected relays: 3/3.`
|
||||
- `Relays: relay.laantungir.net=connected; relay.damus.io=connected; relay.primal.net=connected.`
|
||||
- `Needed events: kind 31123/31124 (skills) and kind 10123 (adoption list) for this agent's pubkey.`
|
||||
- `The agent is NOT online and will not answer DMs until fixed.`
|
||||
|
||||
Keep within the existing `failure_dm` buffer (bump to ~1536 if needed); `snprintf`
|
||||
truncation remains safe.
|
||||
|
||||
## Implementation order
|
||||
|
||||
1. Add `nostr_handler_relay_state_summary()` to handler `.c`/`.h`.
|
||||
2. Add marker helpers + `startup_step_needed_events()` to [`src/main.c`](src/main.c).
|
||||
3. Update [`notify_admin_startup_failure()`](src/main.c:331): de-dup check, richer
|
||||
message, marker write on send.
|
||||
4. Add `startup_failure_marker_clear(&cfg)` on the READY success path
|
||||
([`src/main.c:1663`](src/main.c:1663)).
|
||||
5. Build (`make`), then `build_static.sh`, deploy, verify.
|
||||
|
||||
## Verification (follow-up)
|
||||
|
||||
1. **Throttle:** reproduce step-14 failure; confirm exactly one DM on the first failing
|
||||
restart and no further DMs across subsequent restart-loop iterations (journal shows a
|
||||
suppression line).
|
||||
2. **Clear-on-success:** publish a skill so startup reaches `READY`; confirm marker is
|
||||
removed and the normal startup DM is sent.
|
||||
3. **Re-arm:** break startup again and confirm a single new DM is delivered.
|
||||
4. **Descriptive content:** confirm the DM includes the per-relay state line and the
|
||||
needed-events clause.
|
||||
5. **Sandbox write:** on VM410 confirm the marker is created at
|
||||
`/home/didactyl/.didactyl_startup_failure` (and `/home/simon/...`) without permission
|
||||
errors under the systemd sandbox.
|
||||
@@ -1,248 +0,0 @@
|
||||
# Full n_signer Transport Support in the Setup Wizard
|
||||
|
||||
## Context
|
||||
|
||||
`n_signer` (upgraded to v0.1.5) now exposes four transports and a new
|
||||
`nostr_mine_event` verb. `nostr_core_lib` (v0.6.6, the client lib didactyl links
|
||||
against) already exposes all four transport factories and discovery helpers, but
|
||||
didactyl's interactive setup wizard and config/main wiring only recognize
|
||||
`nsigner_unix` and `nsigner_tcp`. The existing-agent recovery path still
|
||||
requires an nsec and uses raw-key NIP-44, which defeats the point of remote
|
||||
signer modes.
|
||||
|
||||
This plan closes those gaps so an operator can stand up a new agent or recover
|
||||
an existing one using any n_signer transport, with the nsec never entering the
|
||||
agent process.
|
||||
|
||||
## Current State (verified)
|
||||
|
||||
- `nostr_core_lib/nostr_core/nostr_signer.h` exposes:
|
||||
- `nostr_signer_nsigner_unix`, `nostr_signer_nsigner_tcp`,
|
||||
`nostr_signer_nsigner_serial`, `nostr_signer_nsigner_fds`
|
||||
- `nostr_signer_nsigner_set_auth` (TCP auth envelope)
|
||||
- `nostr_core_lib/nostr_core/nsigner_transport.h` exposes:
|
||||
- `nsigner_transport_list_unix` (enumerate `@nsigner*` abstract sockets)
|
||||
- `nsigner_transport_list_serial` (enumerate `/dev/ttyACM*`)
|
||||
- `nostr_core_lib/nostr_core/nostr_signer.c` routes the four core verbs
|
||||
(get_public_key, sign_event, nip04/nip44 encrypt/decrypt) through
|
||||
`nostr_signer_t` for all backends. **`nostr_mine_event` is NOT wired.**
|
||||
- `src/config.h` `signer_config_t` has fields for `mode`, `socket_name`,
|
||||
`role`, `timeout_ms`, `auth_privkey_hex`, `tcp_host`, `tcp_port`,
|
||||
`emergency_local_nsec`. **No serial device or fds fields.**
|
||||
- `src/config.c` `signer_mode_is_known()` / `signer_mode_is_remote()` only
|
||||
accept `local`, `nsigner_unix`, `nsigner_tcp`.
|
||||
- `src/main.c` `apply_signer_overrides()` and `construct_signer()` only handle
|
||||
`local`, `nsigner_unix`, `nsigner_tcp`.
|
||||
- `src/setup_wizard.c` `new_agent_identity_step()` option `s` hardcodes
|
||||
`nsigner_unix`, prompts for socket/role/timeout, does a connectivity check,
|
||||
and offers no auto-discovery or TCP/serial/fds choice.
|
||||
- `src/setup_wizard.c` `existing_agent_flow()` requires an nsec and uses
|
||||
`nostr_nip44_decrypt`/`nostr_nip44_encrypt` with the raw private key for
|
||||
kind-30078 config recovery (Phase 3 item 13 DEFERRED notes).
|
||||
- `src/setup_wizard.c` `install_system_service_with_dedicated_user()` builds
|
||||
`key_args` for `nsigner_unix` and `nsigner_tcp` only.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Wizard Main Menu] --> B{New or Existing?}
|
||||
B -->|New| C[Identity Step]
|
||||
B -->|Existing| D[Signer-or-nsec Step]
|
||||
C --> E[Signer Transport Picker]
|
||||
D --> E
|
||||
E --> F{Transport}
|
||||
F -->|Unix| G[List @nsigner sockets via nsigner_transport_list_unix]
|
||||
F -->|TCP| H[Prompt host:port + optional auth privkey]
|
||||
F -->|Serial| I[List /dev/ttyACM* via nsigner_transport_list_serial]
|
||||
F -->|FDs| J[Prompt read_fd/write_fd]
|
||||
F -->|Local nsec| K[Legacy raw-key path]
|
||||
G --> L[Connectivity check via nostr_signer_get_public_key]
|
||||
H --> L
|
||||
I --> L
|
||||
J --> L
|
||||
K --> M[Derive keys from nsec]
|
||||
L --> N[Populate cfg.signer + cfg.keys.public_key_hex]
|
||||
M --> N
|
||||
N --> O[Continue wizard flow]
|
||||
```
|
||||
|
||||
## Changes by File
|
||||
|
||||
### 1. `src/config.h` — extend `signer_config_t`
|
||||
|
||||
- Update the `mode` comment to list all five modes:
|
||||
`local | nsigner_unix | nsigner_tcp | nsigner_serial | nsigner_fds`.
|
||||
- Add fields:
|
||||
- `char serial_device[OW_MAX_URL_LEN]` — path for `nsigner_serial`.
|
||||
- `int fds_read_fd`, `int fds_write_fd` — for `nsigner_fds` (CLI-only; not
|
||||
persisted to genesis since fds are runtime-only).
|
||||
- Add a new `OW_MAX_SIGNER_DEVICE_LEN` constant (reuse `OW_MAX_URL_LEN`).
|
||||
|
||||
### 2. `src/config.c` — accept the new modes
|
||||
|
||||
- `signer_mode_is_known()`: add `nsigner_serial`, `nsigner_fds`.
|
||||
- `signer_mode_is_remote()`: add `nsigner_serial`, `nsigner_fds`.
|
||||
- `parse_signer_config()`: parse `serial_device` string. Do NOT parse
|
||||
`fds_read_fd`/`fds_write_fd` from JSON (they are CLI-only); document this.
|
||||
- Update the error message string to list all five modes.
|
||||
- `config_set_defaults()` (signer block): leave `mode="local"`, zero the new
|
||||
fields.
|
||||
|
||||
### 3. `src/main.c` — construct signer for all transports
|
||||
|
||||
- `signer_mode_is_remote_local()`: add `nsigner_serial`, `nsigner_fds`.
|
||||
- `apply_signer_overrides()`:
|
||||
- Add `--signer-serial <device>` CLI flag → sets mode `nsigner_serial` and
|
||||
`cfg->signer.serial_device`.
|
||||
- Add `--signer-fds <read_fd:write_fd>` CLI flag → sets mode `nsigner_fds`
|
||||
and parses the two fds.
|
||||
- Update the validation block and error message to list all five modes.
|
||||
- `construct_signer()`:
|
||||
- Add `nsigner_serial` branch → `nostr_signer_nsigner_serial(
|
||||
cfg->signer.serial_device, role, timeout_ms)`.
|
||||
- Add `nsigner_fds` branch → `nostr_signer_nsigner_fds(
|
||||
cfg->signer.fds_read_fd, cfg->signer.fds_write_fd, role, timeout_ms)`.
|
||||
- Update the "Unknown signer mode" fallback message.
|
||||
- The existing connectivity-check + pubkey-discovery block already works for
|
||||
all remote backends (it calls `nostr_signer_get_public_key`), so no change
|
||||
needed there.
|
||||
- Update `print_usage()` help text to document `--signer-serial` and
|
||||
`--signer-fds`.
|
||||
|
||||
### 4. `src/setup_wizard.c` — new signer transport picker
|
||||
|
||||
Replace the hardcoded `nsigner_unix` block in `new_agent_identity_step()`
|
||||
option `s` with a new helper `prompt_signer_transport(cfg)` that:
|
||||
|
||||
1. Renders a sub-menu:
|
||||
- `u` nix socket (auto-discover or named)
|
||||
- `t` cp (host:port + optional auth privkey)
|
||||
- `s` erial USB device (auto-discover or path)
|
||||
- `f` d pair (read_fd:write_fd — advanced)
|
||||
- `b` ack
|
||||
2. For `u`: call `nsigner_transport_list_unix()` to enumerate running n_signer
|
||||
abstract sockets; if any are found, list them numbered and let the operator
|
||||
pick one or type a custom name; if none found, prompt for a name (empty =
|
||||
auto-discover at boot).
|
||||
3. For `t`: prompt `host:port` and an optional 64-char hex auth privkey
|
||||
(echo-suppressed). Store into `cfg->signer.tcp_host/tcp_port/
|
||||
auth_privkey_hex`.
|
||||
4. For `s`: call `nsigner_transport_list_serial()` to enumerate
|
||||
`/dev/ttyACM*`; list them and let the operator pick or type a custom path.
|
||||
Store into `cfg->signer.serial_device`.
|
||||
5. For `f`: prompt `read_fd:write_fd` (advanced; mainly for qrexec-style
|
||||
deployments). Store into `cfg->signer.fds_read_fd/fds_write_fd`.
|
||||
6. Common: prompt `role` (default `main`) and `timeout_ms` (default `15000`).
|
||||
7. Set `cfg->signer.mode` to the chosen mode.
|
||||
8. Connectivity check: construct an ephemeral `nostr_signer_t*` via the
|
||||
matching factory, call `nostr_signer_get_public_key()` to populate
|
||||
`cfg->keys.public_key_hex`, then `nostr_signer_free()`. On failure, print
|
||||
the error and loop back to the transport picker.
|
||||
9. Guard all nsigner_* branches with `#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)`
|
||||
and emit the existing "compiled without n_signer client support" warning
|
||||
otherwise.
|
||||
|
||||
Add a parallel entry point to `existing_agent_flow()` so the operator can
|
||||
choose "recover via a running n_signer" instead of entering an nsec. See item 5.
|
||||
|
||||
### 5. `src/setup_wizard.c` — existing-agent recovery via signer verbs
|
||||
|
||||
Refactor `existing_agent_flow()`:
|
||||
|
||||
- New first step: choose identity source:
|
||||
- `n` sec (legacy)
|
||||
- `s` ign with a running n_signer (calls `prompt_signer_transport(cfg)`)
|
||||
- When the signer path is chosen:
|
||||
- The connectivity check populates `cfg->keys.public_key_hex` from
|
||||
`nostr_signer_get_public_key()`.
|
||||
- Keep the ephemeral signer handle alive for the recovery queries instead
|
||||
of freeing it immediately (pass it through to the recovery helpers).
|
||||
- Replace the raw-key NIP-44 decrypt in `fetch_and_decrypt_self_config_wizard()`
|
||||
with a signer-aware variant: when a signer is available, call
|
||||
`nostr_signer_nip44_decrypt(signer, cfg->keys.public_key_hex, ciphertext,
|
||||
&plaintext)`. Fall back to the raw-key path only when `signer == NULL`
|
||||
(local mode). The peer pubkey for self-encrypt is the agent's own pubkey.
|
||||
- Replace the raw-key NIP-44 encrypt in
|
||||
`publish_encrypted_self_config_wizard()` the same way.
|
||||
- Remove/replace the Phase 3 item 13 DEFERRED comments at those two sites
|
||||
since the signer path is now implemented for the wizard.
|
||||
- The `nostr_handler_init(&tmp)` calls in the recovery helpers currently copy
|
||||
the config and re-derive keys; ensure they pick up `cfg->signer` so the
|
||||
handler uses the process signer for its own encrypt/decrypt. (Verify
|
||||
`nostr_handler` already threads the signer through; if not, pass the
|
||||
ephemeral wizard signer via a new `nostr_handler_set_signer()` call for the
|
||||
duration of the recovery queries, then clear it before `cleanup()`.)
|
||||
|
||||
### 6. `src/setup_wizard.c` — systemd unit key_args for new modes
|
||||
|
||||
In `install_system_service_with_dedicated_user()`, extend the `key_args`
|
||||
builder:
|
||||
- `nsigner_serial`: `--signer nsigner_serial --signer-serial <device>
|
||||
--signer-role <role> --signer-timeout <ms>`
|
||||
- `nsigner_fds`: skip systemd install (fds are runtime-only and cannot be
|
||||
embedded in ExecStart). Print a clear message that fds mode is not
|
||||
installable as a systemd service and the operator must wire the fd-passing
|
||||
themselves (e.g. via a wrapper unit with `Sockets=` or qrexec). Return
|
||||
non-fatal error so the wizard offers "boot now" instead.
|
||||
- Update the summary print block (`Signer: %s ...`) to mention serial/fds.
|
||||
|
||||
### 7. `src/main.c` — `--signer-serial` / `--signer-fds` arg parsing
|
||||
|
||||
Already covered in item 3. Ensure `argv` parsing loop handles the two new
|
||||
flags and their values, and that `--signer-fds` parses `read:write` with
|
||||
`atoi` validation (both >= 0).
|
||||
|
||||
### 8. `nostr_core_lib` — wire `nostr_mine_event` (optional, stretch)
|
||||
|
||||
This is a library-side change. If we want didactyl to be able to mine PoW
|
||||
events through a remote signer:
|
||||
- Add `int nostr_signer_mine_event(nostr_signer_t*, const cJSON* unsigned_event,
|
||||
int difficulty, int threads, int timeout_sec, cJSON** signed_event_out)` to
|
||||
`nostr_signer.h`.
|
||||
- Implement the remote branch in `nostr_signer.c` calling
|
||||
`nsigner_client_call(client, "nostr_mine_event", params, &result)` with the
|
||||
options object `{difficulty, threads, timeout_sec}`.
|
||||
- Local branch: call the existing `nostr_mine_event` helper in `nip013.c`.
|
||||
- This is **not required** for the wizard task; include only if the operator
|
||||
wants PoW through n_signer. Recommend deferring to a follow-up.
|
||||
|
||||
### 9. Docs
|
||||
|
||||
- Update `docs/GENESIS.md` signer block example to show all five modes and the
|
||||
new `serial_device` field.
|
||||
- Update `README.md` quick-start example #5 to mention TCP/serial variants.
|
||||
- Add a short section to `docs/CONTEXT.md` (or a new `docs/SIGNER.md`)
|
||||
describing the five modes, when to use each, and the security tradeoff
|
||||
(local keeps nsec in-process; remote modes keep it in n_signer).
|
||||
|
||||
## Verification
|
||||
|
||||
1. `make deps && make` — must compile cleanly with
|
||||
`-DNOSTR_ENABLE_NSIGNER_CLIENT=1`.
|
||||
2. Run `./didactyl --help` — confirm `--signer-serial` and `--signer-fds`
|
||||
appear.
|
||||
3. Start a local n_signer on an abstract unix socket; run `./didactyl`
|
||||
(interactive), choose New agent → sign with n_signer → unix → pick the
|
||||
discovered socket → confirm connectivity check prints the pubkey.
|
||||
4. Repeat with `--signer-tcp 127.0.0.1:11111` style flow via the wizard TCP
|
||||
option.
|
||||
5. Existing-agent flow: with a running n_signer, choose Existing → sign with
|
||||
n_signer → confirm kind-30078 config is decrypted via the signer (no nsec
|
||||
entered). Verify the recovered LLM/admin fields appear.
|
||||
6. Negative: kill n_signer mid-wizard → connectivity check fails → wizard
|
||||
loops back to the transport picker.
|
||||
7. `nsigner_serial` / `nsigner_fds` modes: validate config load via a
|
||||
genesis file with `signer.mode: "nsigner_serial"` and
|
||||
`serial_device: "/dev/ttyACM0"`; confirm `construct_signer()` attempts the
|
||||
serial factory (will fail without hardware, but the mode must be accepted
|
||||
and reach the factory, not be rejected as "unknown signer mode").
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Wiring `nostr_mine_event` into didactyl call sites (only the lib hook in
|
||||
item 8, and only if explicitly requested).
|
||||
- Changing the `nostr_handler` signer threading model beyond what the wizard
|
||||
recovery path needs.
|
||||
- Windows support for abstract sockets / serial enumeration (Linux-only, as
|
||||
today).
|
||||
File diff suppressed because one or more lines are too long
+107
-240
@@ -35,6 +35,8 @@ static pthread_mutex_t g_context_part_names_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
#define AGENT_DEBOUNCE_WINDOW_SECONDS 5
|
||||
#define AGENT_DEBOUNCE_CACHE_SIZE 256
|
||||
#define AGENT_HISTORY_TURNS 12
|
||||
#define AGENT_HISTORY_QUERY_LIMIT 200
|
||||
#define AGENT_ADOPTED_SKILLS_CACHE_TTL_SECONDS 300
|
||||
#define AGENT_ADOPTED_SKILLS_MAX 24
|
||||
#define AGENT_ADOPTED_SKILL_CONTENT_MAX_CHARS 1800
|
||||
@@ -250,38 +252,6 @@ static int append_tool_result_message(cJSON* messages, const char* tool_call_id,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int maybe_append_loaded_skill_instructions(cJSON* messages,
|
||||
const char* tool_name,
|
||||
const char* tool_result_json) {
|
||||
if (!messages || !tool_name || strcmp(tool_name, "skill_load") != 0 || !tool_result_json) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_Parse(tool_result_json);
|
||||
if (!root || !cJSON_IsObject(root)) {
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* ok = cJSON_GetObjectItemCaseSensitive(root, "success");
|
||||
cJSON* type = cJSON_GetObjectItemCaseSensitive(root, "type");
|
||||
cJSON* instructions = cJSON_GetObjectItemCaseSensitive(root, "instructions");
|
||||
|
||||
int should_apply = (ok && cJSON_IsBool(ok) && cJSON_IsTrue(ok) &&
|
||||
type && cJSON_IsString(type) && type->valuestring &&
|
||||
strcmp(type->valuestring, "skill_instructions") == 0 &&
|
||||
instructions && cJSON_IsString(instructions) && instructions->valuestring &&
|
||||
instructions->valuestring[0] != '\0');
|
||||
|
||||
int rc = 0;
|
||||
if (should_apply) {
|
||||
rc = append_simple_message(messages, "system", instructions->valuestring);
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static char* local_json_error(const char* msg) {
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
@@ -322,51 +292,6 @@ static char* build_slash_args_json(const char* raw_args) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static char* build_run_args_json_local(const char* raw_args) {
|
||||
const char* s = skip_spaces_local(raw_args);
|
||||
if (!s || s[0] == '\0') {
|
||||
return local_json_error("/run requires a skill identifier");
|
||||
}
|
||||
|
||||
const char* ident_start = s;
|
||||
while (*s && !is_space_char(*s)) s++;
|
||||
|
||||
size_t ident_len = (size_t)(s - ident_start);
|
||||
if (ident_len == 0 || ident_len >= 256U) {
|
||||
return local_json_error("/run skill identifier is invalid");
|
||||
}
|
||||
|
||||
char ident[256];
|
||||
memcpy(ident, ident_start, ident_len);
|
||||
ident[ident_len] = '\0';
|
||||
|
||||
const char* remaining = skip_spaces_local(s);
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
|
||||
const char* c1 = strchr(ident, ':');
|
||||
const char* c2 = c1 ? strchr(c1 + 1, ':') : NULL;
|
||||
if (c1 && c2 && c1 != ident && (size_t)(c2 - c1) == 65U && c2[1] != '\0') {
|
||||
size_t pubkey_len = (size_t)(c2 - (c1 + 1));
|
||||
char pubkey[65];
|
||||
memcpy(pubkey, c1 + 1, pubkey_len);
|
||||
pubkey[pubkey_len] = '\0';
|
||||
cJSON_AddStringToObject(out, "pubkey", pubkey);
|
||||
cJSON_AddStringToObject(out, "d_tag", c2 + 1);
|
||||
} else {
|
||||
cJSON_AddStringToObject(out, "d_tag", ident);
|
||||
}
|
||||
|
||||
if (remaining && remaining[0] != '\0') {
|
||||
cJSON_AddStringToObject(out, "args", remaining);
|
||||
}
|
||||
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
return json;
|
||||
}
|
||||
|
||||
static int append_textf_local(char** buf, size_t* cap, size_t* used, const char* fmt, ...) {
|
||||
if (!buf || !cap || !used || !fmt) return -1;
|
||||
|
||||
@@ -679,21 +604,7 @@ static char* build_slash_help_all_json(void) {
|
||||
|
||||
int adopted_count = 0;
|
||||
if (adoption_events && cJSON_IsArray(adoption_events) && cJSON_GetArraySize(adoption_events) > 0) {
|
||||
cJSON* ev0 = NULL;
|
||||
double best_created = -1.0;
|
||||
int an = cJSON_GetArraySize(adoption_events);
|
||||
for (int ai = 0; ai < an; ai++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(adoption_events, ai);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
if (!ev0 || created > best_created) {
|
||||
ev0 = ev;
|
||||
best_created = created;
|
||||
}
|
||||
}
|
||||
cJSON* ev0 = cJSON_GetArrayItem(adoption_events, 0);
|
||||
cJSON* tags = ev0 ? cJSON_GetObjectItemCaseSensitive(ev0, "tags") : NULL;
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
int tn = cJSON_GetArraySize(tags);
|
||||
@@ -883,8 +794,7 @@ static int handle_slash_command(const char* sender_pubkey_hex, const char* messa
|
||||
|
||||
char* result_json = NULL;
|
||||
if (raw_args && (strcmp(raw_args, "-h") == 0 || strcmp(raw_args, "--help") == 0)) {
|
||||
const char* help_name = (strcmp(tool_name, "run") == 0) ? "skill_run" : tool_name;
|
||||
result_json = build_slash_help_tool_json(help_name);
|
||||
result_json = build_slash_help_tool_json(tool_name);
|
||||
} else if (strcmp(tool_name, "help") == 0) {
|
||||
if (!raw_args || raw_args[0] == '\0') {
|
||||
result_json = build_slash_help_all_json();
|
||||
@@ -896,24 +806,14 @@ static int handle_slash_command(const char* sender_pubkey_hex, const char* messa
|
||||
i++;
|
||||
}
|
||||
help_tool[i] = '\0';
|
||||
const char* help_name = (strcmp(help_tool, "run") == 0) ? "skill_run" : help_tool;
|
||||
result_json = build_slash_help_tool_json(help_name);
|
||||
result_json = build_slash_help_tool_json(help_tool);
|
||||
}
|
||||
} else {
|
||||
char* args_json = NULL;
|
||||
const char* exec_tool_name = tool_name;
|
||||
|
||||
if (strcmp(tool_name, "run") == 0) {
|
||||
args_json = build_run_args_json_local(raw_args);
|
||||
exec_tool_name = "skill_run";
|
||||
} else {
|
||||
args_json = build_slash_args_json(raw_args);
|
||||
}
|
||||
|
||||
char* args_json = build_slash_args_json(raw_args);
|
||||
if (!args_json) {
|
||||
result_json = local_json_error("failed to parse slash args");
|
||||
} else {
|
||||
result_json = tools_execute(&g_tools_ctx, exec_tool_name, args_json);
|
||||
result_json = tools_execute(&g_tools_ctx, tool_name, args_json);
|
||||
free(args_json);
|
||||
}
|
||||
}
|
||||
@@ -1060,6 +960,73 @@ static __attribute__((unused)) char* build_sender_verification_text(didactyl_sen
|
||||
}
|
||||
|
||||
|
||||
static __attribute__((unused)) int append_recent_admin_dm_history(cJSON* messages, const char* current_message) {
|
||||
if (!messages || !g_cfg) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* history_json = nostr_handler_get_dm_history_json(g_cfg->admin.pubkey, AGENT_HISTORY_TURNS);
|
||||
if (!history_json) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* items = cJSON_Parse(history_json);
|
||||
free(history_json);
|
||||
if (!items || !cJSON_IsArray(items)) {
|
||||
cJSON_Delete(items);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* last_appended_role = NULL;
|
||||
const char* last_appended_content = NULL;
|
||||
|
||||
int n = cJSON_GetArraySize(items);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* item = cJSON_GetArrayItem(items, i);
|
||||
cJSON* role = item ? cJSON_GetObjectItemCaseSensitive(item, "role") : NULL;
|
||||
cJSON* content = item ? cJSON_GetObjectItemCaseSensitive(item, "content") : NULL;
|
||||
cJSON* created_at = item ? cJSON_GetObjectItemCaseSensitive(item, "created_at") : NULL;
|
||||
|
||||
if (!role || !content || !cJSON_IsString(role) || !cJSON_IsString(content) ||
|
||||
!role->valuestring || !content->valuestring) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* role_s = role->valuestring;
|
||||
const char* content_s = content->valuestring;
|
||||
int role_is_user = (strcmp(role_s, "user") == 0);
|
||||
if (!role_is_user && strcmp(role_s, "assistant") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == n - 1 && role_is_user && current_message && strcmp(content_s, current_message) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last_appended_role && last_appended_content &&
|
||||
strcmp(last_appended_role, role_s) == 0 &&
|
||||
strcmp(last_appended_content, content_s) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (append_simple_message(messages, role_s, content_s) != 0) {
|
||||
cJSON_Delete(items);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* appended = cJSON_GetArrayItem(messages, cJSON_GetArraySize(messages) - 1);
|
||||
if (appended && cJSON_IsObject(appended) && created_at && cJSON_IsNumber(created_at)) {
|
||||
cJSON_AddNumberToObject(appended, "_ts", created_at->valuedouble);
|
||||
}
|
||||
|
||||
last_appended_role = role_s;
|
||||
last_appended_content = content_s;
|
||||
}
|
||||
|
||||
cJSON_Delete(items);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static cJSON* find_tag_value_string_local(cJSON* tags, const char* key) {
|
||||
if (!tags || !key || !cJSON_IsArray(tags)) {
|
||||
return NULL;
|
||||
@@ -1185,28 +1152,6 @@ static char* resolve_skill_references_local(const char* input) {
|
||||
return prompt_template_resolve_inline_variables(input ? input : "", &g_tools_ctx);
|
||||
}
|
||||
|
||||
static char* strip_system_role_prefix_local(const char* input) {
|
||||
const char* s = input ? input : "";
|
||||
|
||||
if (strncmp(s, "system:", 7) != 0) {
|
||||
return strdup(s);
|
||||
}
|
||||
|
||||
const char* p = s + 7;
|
||||
while (*p == ' ' || *p == '\t') {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (*p == '\r') {
|
||||
p++;
|
||||
}
|
||||
if (*p == '\n') {
|
||||
p++;
|
||||
}
|
||||
|
||||
return strdup(p);
|
||||
}
|
||||
|
||||
static char* build_context_from_triggers(trigger_type_t trigger_type,
|
||||
const char* trigger_filter,
|
||||
cJSON* trigger_event,
|
||||
@@ -1282,11 +1227,9 @@ static char* build_context_from_triggers(trigger_type_t trigger_type,
|
||||
for (int i = 0; i < matched_skill_count; i++) {
|
||||
char* expanded = resolve_skill_references_local(matched_skill_contents[i] ? matched_skill_contents[i] : "");
|
||||
const char* skill_text = expanded ? expanded : (matched_skill_contents[i] ? matched_skill_contents[i] : "");
|
||||
char* normalized = strip_system_role_prefix_local(skill_text);
|
||||
const char* normalized_text = normalized ? normalized : skill_text;
|
||||
|
||||
char* bumped = context_bump_headings(normalized_text);
|
||||
const char* final_text = bumped ? bumped : normalized_text;
|
||||
char* bumped = context_bump_headings(skill_text);
|
||||
const char* final_text = bumped ? bumped : skill_text;
|
||||
|
||||
size_t need = strlen("\n\n---\n\n") + strlen(final_text) + 1U;
|
||||
if (used + need >= cap) {
|
||||
@@ -1295,7 +1238,6 @@ static char* build_context_from_triggers(trigger_type_t trigger_type,
|
||||
char* grown = (char*)realloc(out, next);
|
||||
if (!grown) {
|
||||
free(bumped);
|
||||
free(normalized);
|
||||
free(expanded);
|
||||
for (int j = i; j < matched_skill_count; j++) {
|
||||
free(matched_skill_contents[j]);
|
||||
@@ -1318,7 +1260,6 @@ static char* build_context_from_triggers(trigger_type_t trigger_type,
|
||||
matched++;
|
||||
|
||||
free(bumped);
|
||||
free(normalized);
|
||||
free(expanded);
|
||||
free(matched_skill_contents[i]);
|
||||
}
|
||||
@@ -1394,29 +1335,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
free(adoption_json);
|
||||
|
||||
if (adoption_events && cJSON_IsArray(adoption_events) && cJSON_GetArraySize(adoption_events) > 0) {
|
||||
cJSON* list_event = NULL;
|
||||
double list_event_created_at = -1.0;
|
||||
int adoption_n = cJSON_GetArraySize(adoption_events);
|
||||
for (int ai = 0; ai < adoption_n; ai++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(adoption_events, ai);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* ev_pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
|
||||
if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
|
||||
strcmp(ev_pubkey->valuestring, g_cfg->keys.public_key_hex) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
if (!list_event || created > list_event_created_at) {
|
||||
list_event = ev;
|
||||
list_event_created_at = created;
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
@@ -1728,8 +1647,16 @@ static __attribute__((unused)) int append_adopted_skills_context(cJSON* messages
|
||||
return rc;
|
||||
}
|
||||
|
||||
void agent_set_signer(nostr_signer_t* signer) {
|
||||
tools_set_signer(&g_tools_ctx, signer);
|
||||
static __attribute__((unused)) cJSON* build_recent_admin_dm_history_messages(const char* current_message) {
|
||||
cJSON* messages = cJSON_CreateArray();
|
||||
if (!messages) {
|
||||
return NULL;
|
||||
}
|
||||
if (append_recent_admin_dm_history(messages, current_message) != 0) {
|
||||
cJSON_Delete(messages);
|
||||
return NULL;
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
int agent_init(didactyl_config_t* config) {
|
||||
@@ -1911,17 +1838,6 @@ void agent_on_trigger(const char* skill_d_tag,
|
||||
return;
|
||||
}
|
||||
|
||||
if (maybe_append_loaded_skill_instructions(messages, tc->name, tool_result) != 0) {
|
||||
free(tool_result);
|
||||
llm_response_free(&resp);
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
g_tools_ctx.template_trigger_event_json = prev_trigger_event_json;
|
||||
free(event_json);
|
||||
(void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, "Triggered skill execution failed: loaded skill instruction append failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
free(tool_result);
|
||||
}
|
||||
|
||||
@@ -2139,13 +2055,11 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
cJSON_AddNumberToObject(live_user_msg, "_ts", (double)time(NULL));
|
||||
}
|
||||
|
||||
int max_turns = g_cfg->tools.max_turns > 0 ? g_cfg->tools.max_turns : 40;
|
||||
int max_turns = g_cfg->tools.max_turns > 0 ? g_cfg->tools.max_turns : 20;
|
||||
int stall_repeat_threshold = g_cfg->tools.stall_repeat_threshold > 1 ? g_cfg->tools.stall_repeat_threshold : 3;
|
||||
int max_context_bytes = g_cfg->tools.max_context_bytes >= 4096 ? g_cfg->tools.max_context_bytes : (128 * 1024);
|
||||
uint64_t last_tool_fp = 0;
|
||||
int repeated_tool_turns = 0;
|
||||
int exited_on_stall = 0;
|
||||
int exited_on_context_limit = 0;
|
||||
int stall_due_to_length = 0;
|
||||
char* final_answer_owned = NULL;
|
||||
int turns_run = 0;
|
||||
@@ -2157,17 +2071,6 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
break;
|
||||
}
|
||||
|
||||
size_t messages_json_len = strlen(messages_json);
|
||||
if ((int)messages_json_len > max_context_bytes) {
|
||||
DEBUG_WARN("[didactyl] DM tool loop context limit exceeded: bytes=%zu max_context_bytes=%d turn=%d",
|
||||
messages_json_len,
|
||||
max_context_bytes,
|
||||
turns_run);
|
||||
exited_on_context_limit = 1;
|
||||
free(messages_json);
|
||||
break;
|
||||
}
|
||||
|
||||
llm_response_t resp;
|
||||
int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp);
|
||||
free(messages_json);
|
||||
@@ -2232,85 +2135,49 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
(void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to append tool result.");
|
||||
return;
|
||||
}
|
||||
if (maybe_append_loaded_skill_instructions(messages, tc->name, tool_result) != 0) {
|
||||
free(tool_result);
|
||||
llm_response_free(&resp);
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
free(dm_context);
|
||||
(void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to apply loaded skill instructions.");
|
||||
return;
|
||||
}
|
||||
free(tool_result);
|
||||
}
|
||||
|
||||
llm_response_free(&resp);
|
||||
}
|
||||
|
||||
if (!final_answer_owned && !exited_on_context_limit) {
|
||||
if (!final_answer_owned) {
|
||||
char* messages_json = cJSON_PrintUnformatted(messages);
|
||||
if (messages_json) {
|
||||
size_t messages_json_len = strlen(messages_json);
|
||||
if ((int)messages_json_len <= max_context_bytes) {
|
||||
llm_response_t final_resp;
|
||||
int final_rc = llm_chat_with_tools_messages(messages_json, tools_json, "none", &final_resp);
|
||||
if (final_rc == 0) {
|
||||
const char* forced_answer = final_resp.content ? final_resp.content : "";
|
||||
if (forced_answer[0] != '\0') {
|
||||
final_answer_owned = strdup(forced_answer);
|
||||
}
|
||||
llm_response_free(&final_resp);
|
||||
}
|
||||
} else {
|
||||
exited_on_context_limit = 1;
|
||||
}
|
||||
llm_response_t final_resp;
|
||||
int final_rc = llm_chat_with_tools_messages(messages_json, tools_json, "none", &final_resp);
|
||||
free(messages_json);
|
||||
if (final_rc == 0) {
|
||||
const char* forced_answer = final_resp.content ? final_resp.content : "";
|
||||
if (forced_answer[0] != '\0') {
|
||||
final_answer_owned = strdup(forced_answer);
|
||||
}
|
||||
llm_response_free(&final_resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int exhausted_on_max_turns = (!final_answer_owned && !exited_on_stall && !exited_on_context_limit && turns_run >= max_turns);
|
||||
int exhausted_on_max_turns = (!final_answer_owned && !exited_on_stall && turns_run >= max_turns);
|
||||
|
||||
if (!final_answer_owned) {
|
||||
final_answer_owned = strdup(exited_on_context_limit
|
||||
? "I paused because the working context became too large while using tools. Please narrow the scope and ask me to continue from a smaller context."
|
||||
: (exited_on_stall
|
||||
? "I stopped repeated tool calls after detecting a loop and could not produce a final summary."
|
||||
: "I reached the tool-turn limit before getting a final response from the model."));
|
||||
final_answer_owned = strdup(exited_on_stall
|
||||
? "I stopped repeated tool calls after detecting a loop and could not produce a final summary."
|
||||
: "I hit my tool-use limit for this request.");
|
||||
}
|
||||
|
||||
if (exhausted_on_max_turns) {
|
||||
const char* base = final_answer_owned ? final_answer_owned : "I reached the tool-turn limit before getting a final response from the model.";
|
||||
const char* suffix = "\n\nI have paused to avoid running forever. Reply with 'continue' and I will continue from the current context.";
|
||||
size_t need = strlen(base) + strlen(suffix) + 1U;
|
||||
char* with_prompt = (char*)malloc(need);
|
||||
if (with_prompt) {
|
||||
snprintf(with_prompt, need, "%s%s", base, suffix);
|
||||
free(final_answer_owned);
|
||||
final_answer_owned = with_prompt;
|
||||
}
|
||||
}
|
||||
const char* final_answer = final_answer_owned ? final_answer_owned : "I hit my tool-use limit for this request.";
|
||||
|
||||
const char* final_answer = final_answer_owned ? final_answer_owned : "I reached the tool-turn limit before getting a final response from the model.";
|
||||
|
||||
if (exited_on_stall || exhausted_on_max_turns || exited_on_context_limit) {
|
||||
if (exited_on_stall || exhausted_on_max_turns) {
|
||||
notify_admin_limit_diagnostic("dm_agent_loop",
|
||||
exited_on_context_limit
|
||||
? "max_context_bytes_exceeded"
|
||||
: (exited_on_stall ? "stall_repetition_detected" : "max_turns_exhausted"),
|
||||
exited_on_stall ? "stall_repetition_detected" : "max_turns_exhausted",
|
||||
sender_pubkey_hex,
|
||||
max_turns,
|
||||
turns_run,
|
||||
stall_repeat_threshold,
|
||||
repeated_tool_turns,
|
||||
g_cfg->llm.max_tokens,
|
||||
exited_on_context_limit
|
||||
? "tool_context_growth"
|
||||
: ((exited_on_stall && stall_due_to_length) ? "max_tokens_truncation" : NULL),
|
||||
exited_on_context_limit
|
||||
? "Reduce tool-loop breadth or lower max_turns; context exceeded max_context_bytes."
|
||||
: ((exited_on_stall && stall_due_to_length)
|
||||
? "Increase max_tokens (example: /model_set {\"max_tokens\": 4096})"
|
||||
: NULL),
|
||||
(exited_on_stall && stall_due_to_length) ? "max_tokens_truncation" : NULL,
|
||||
(exited_on_stall && stall_due_to_length) ? "Increase max_tokens (example: /model_set {\"max_tokens\": 4096})" : NULL,
|
||||
final_answer);
|
||||
}
|
||||
fprintf(stdout, "[didactyl] final response: %.240s%s\n",
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
struct trigger_manager;
|
||||
|
||||
int agent_init(didactyl_config_t* config);
|
||||
/* Wire a process-lifetime signer handle (non-owning) into the agent's tools
|
||||
* context so NIP-04/44 encrypt/decrypt tool call sites route through the
|
||||
* signer. Must be called after agent_init. Caller retains ownership. */
|
||||
void agent_set_signer(nostr_signer_t* signer);
|
||||
void agent_set_trigger_manager(struct trigger_manager* trigger_manager);
|
||||
void agent_on_trigger(const char* skill_d_tag,
|
||||
const char* skill_content,
|
||||
|
||||
+5
-46
@@ -19,13 +19,6 @@
|
||||
#define CASHU_WALLET_QUERY_TIMEOUT_MS 6000
|
||||
#define CASHU_WALLET_MAX_SPLIT_PARTS 64
|
||||
|
||||
/* Optional process-lifetime signer for NIP-60/NIP-61 cashu event creation.
|
||||
* Owned by main.c; non-owning here. When present, event creation is routed
|
||||
* through the *_with_signer variants so the raw private key stays out of the
|
||||
* agent process in remote-signer modes. When NULL, the legacy raw-key path is
|
||||
* used (local-mode compatibility fallback). */
|
||||
static nostr_signer_t* g_cashu_signer = NULL;
|
||||
|
||||
typedef struct {
|
||||
char event_id[65];
|
||||
nostr_nip60_token_data_t token;
|
||||
@@ -128,12 +121,7 @@ static int publish_from_signed_event_template(cJSON* ev_template, char out_event
|
||||
}
|
||||
|
||||
static int publish_wallet_event_and_store_locked(const nostr_nip60_wallet_data_t* wallet_data, char out_event_id[65]) {
|
||||
cJSON* ev = NULL;
|
||||
if (g_cashu_signer) {
|
||||
ev = nostr_nip60_create_wallet_event_with_signer(wallet_data, g_cashu_signer, time(NULL));
|
||||
} else {
|
||||
ev = nostr_nip60_create_wallet_event(wallet_data, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
}
|
||||
cJSON* ev = nostr_nip60_create_wallet_event(wallet_data, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
if (!ev) return -1;
|
||||
|
||||
int rc = publish_from_signed_event_template(ev, out_event_id);
|
||||
@@ -168,12 +156,7 @@ static int publish_wallet_event_and_store_locked(const nostr_nip60_wallet_data_t
|
||||
static int publish_token_event_and_store_locked(nostr_nip60_token_data_t* token_data, char out_event_id[65]) {
|
||||
if (!token_data) return -1;
|
||||
|
||||
cJSON* ev = NULL;
|
||||
if (g_cashu_signer) {
|
||||
ev = nostr_nip60_create_token_event_with_signer(token_data, g_cashu_signer, time(NULL));
|
||||
} else {
|
||||
ev = nostr_nip60_create_token_event(token_data, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
}
|
||||
cJSON* ev = nostr_nip60_create_token_event(token_data, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
if (!ev) return -1;
|
||||
|
||||
char event_id[65] = {0};
|
||||
@@ -211,12 +194,7 @@ static int publish_history_event(nostr_nip60_direction_t direction,
|
||||
history.refs = &ref;
|
||||
history.ref_count = (ref.event_id[0] != '\0') ? 1 : 0;
|
||||
|
||||
cJSON* ev = NULL;
|
||||
if (g_cashu_signer) {
|
||||
ev = nostr_nip60_create_history_event_with_signer(&history, g_cashu_signer, time(NULL));
|
||||
} else {
|
||||
ev = nostr_nip60_create_history_event(&history, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
}
|
||||
cJSON* ev = nostr_nip60_create_history_event(&history, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
if (!ev) return -1;
|
||||
|
||||
int rc = publish_from_signed_event_template(ev, NULL);
|
||||
@@ -225,12 +203,7 @@ static int publish_history_event(nostr_nip60_direction_t direction,
|
||||
}
|
||||
|
||||
static int publish_token_deletion_event(const char* event_id) {
|
||||
cJSON* del = NULL;
|
||||
if (g_cashu_signer) {
|
||||
del = nostr_nip60_create_token_deletion_with_signer(event_id, g_cashu_signer, time(NULL));
|
||||
} else {
|
||||
del = nostr_nip60_create_token_deletion(event_id, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
}
|
||||
cJSON* del = nostr_nip60_create_token_deletion(event_id, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
if (!del) return -1;
|
||||
int rc = publish_from_signed_event_template(del, NULL);
|
||||
cJSON_Delete(del);
|
||||
@@ -435,15 +408,6 @@ int cashu_wallet_init(didactyl_config_t* cfg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cashu_wallet_set_signer(nostr_signer_t* signer) {
|
||||
/* Non-owning: the signer handle is constructed and freed by main.c.
|
||||
* Stored under the wallet mutex for visibility/tearing safety even
|
||||
* though the handle itself is process-lifetime and immutable. */
|
||||
pthread_mutex_lock(&g_wallet.mutex);
|
||||
g_cashu_signer = signer;
|
||||
pthread_mutex_unlock(&g_wallet.mutex);
|
||||
}
|
||||
|
||||
void cashu_wallet_cleanup(void) {
|
||||
if (!g_wallet.initialized) return;
|
||||
|
||||
@@ -2791,12 +2755,7 @@ int cashu_wallet_mints_set(const char** wallet_mints,
|
||||
pthread_mutex_unlock(&g_wallet.mutex);
|
||||
if (rc != 0) return -1;
|
||||
|
||||
cJSON* ev = NULL;
|
||||
if (g_cashu_signer) {
|
||||
ev = nostr_nip61_create_nutzap_info_event_with_signer(&info, g_cashu_signer, time(NULL));
|
||||
} else {
|
||||
ev = nostr_nip61_create_nutzap_info_event(&info, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
}
|
||||
cJSON* ev = nostr_nip61_create_nutzap_info_event(&info, g_wallet.cfg->keys.private_key, time(NULL));
|
||||
nostr_nip61_free_nutzap_info(&info);
|
||||
if (!ev) return -1;
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "cjson/cJSON.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_signer.h"
|
||||
|
||||
int cashu_wallet_init(didactyl_config_t* cfg);
|
||||
void cashu_wallet_set_signer(nostr_signer_t* signer);
|
||||
void cashu_wallet_cleanup(void);
|
||||
|
||||
int cashu_wallet_load_from_relays(void);
|
||||
|
||||
+3
-179
@@ -214,136 +214,6 @@ static int decode_pubkey_hex_or_npub(const char* in, char out_hex[65]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Returns 1 if mode is a known signer mode, 0 otherwise. */
|
||||
static int signer_mode_is_known(const char* mode) {
|
||||
if (!mode || mode[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
return strcmp(mode, "local") == 0 ||
|
||||
strcmp(mode, "nsigner_unix") == 0 ||
|
||||
strcmp(mode, "nsigner_tcp") == 0 ||
|
||||
strcmp(mode, "nsigner_serial") == 0 ||
|
||||
strcmp(mode, "nsigner_fds") == 0 ||
|
||||
strcmp(mode, "nsigner_qrexec") == 0;
|
||||
}
|
||||
|
||||
/* Returns 1 if mode is a remote (nsigner_*) mode that does not require an
|
||||
* in-process nsec, 0 otherwise. */
|
||||
static int signer_mode_is_remote(const char* mode) {
|
||||
if (!mode || mode[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
return strcmp(mode, "nsigner_unix") == 0 ||
|
||||
strcmp(mode, "nsigner_tcp") == 0 ||
|
||||
strcmp(mode, "nsigner_serial") == 0 ||
|
||||
strcmp(mode, "nsigner_fds") == 0 ||
|
||||
strcmp(mode, "nsigner_qrexec") == 0;
|
||||
}
|
||||
|
||||
static int parse_signer_config(cJSON* root, didactyl_config_t* config) {
|
||||
cJSON* signer = cJSON_GetObjectItemCaseSensitive(root, "signer");
|
||||
if (!signer || !cJSON_IsObject(signer)) {
|
||||
/* Missing signer block: defaults already applied by caller. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (copy_json_string(signer, "mode", config->signer.mode,
|
||||
sizeof(config->signer.mode), 0) != 0) {
|
||||
config_set_error("signer.mode must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
if (config->signer.mode[0] != '\0' && !signer_mode_is_known(config->signer.mode)) {
|
||||
config_set_error("signer.mode '%s' is not a known signer mode "
|
||||
"(expected: local, nsigner_unix, nsigner_tcp, "
|
||||
"nsigner_serial, nsigner_fds, nsigner_qrexec)",
|
||||
config->signer.mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (copy_json_string(signer, "socket_name", config->signer.socket_name,
|
||||
sizeof(config->signer.socket_name), 0) != 0) {
|
||||
config_set_error("signer.socket_name must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (copy_json_string(signer, "role", config->signer.role,
|
||||
sizeof(config->signer.role), 0) != 0) {
|
||||
config_set_error("signer.role must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* timeout = cJSON_GetObjectItemCaseSensitive(signer, "timeout_ms");
|
||||
if (timeout && cJSON_IsNumber(timeout)) {
|
||||
int t = (int)timeout->valuedouble;
|
||||
if (t > 0) {
|
||||
config->signer.timeout_ms = t;
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional nostr_index: numeric key index selector. When set (>= 0) it
|
||||
* overrides role on the n_signer side. -1 (default) means use role. */
|
||||
cJSON* nostr_index = cJSON_GetObjectItemCaseSensitive(signer, "nostr_index");
|
||||
if (nostr_index && cJSON_IsNumber(nostr_index)) {
|
||||
int idx = (int)nostr_index->valuedouble;
|
||||
if (idx >= 0) {
|
||||
config->signer.nostr_index = idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (copy_json_string(signer, "auth_privkey_hex", config->signer.auth_privkey_hex,
|
||||
sizeof(config->signer.auth_privkey_hex), 0) != 0) {
|
||||
config_set_error("signer.auth_privkey_hex must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Optional tcp host/port inside the signer block (alternative to --signer-tcp). */
|
||||
if (copy_json_string(signer, "tcp_host", config->signer.tcp_host,
|
||||
sizeof(config->signer.tcp_host), 0) != 0) {
|
||||
config_set_error("signer.tcp_host must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
cJSON* tcp_port = cJSON_GetObjectItemCaseSensitive(signer, "tcp_port");
|
||||
if (tcp_port && cJSON_IsNumber(tcp_port)) {
|
||||
int p = (int)tcp_port->valuedouble;
|
||||
if (p > 0 && p <= 65535) {
|
||||
config->signer.tcp_port = p;
|
||||
}
|
||||
}
|
||||
|
||||
/* nsigner_serial: device path (e.g. "/dev/ttyACM0"). fds_read_fd/fds_write_fd
|
||||
* are intentionally NOT parsed from JSON — they are CLI/runtime-only and
|
||||
* cannot be persisted to genesis. */
|
||||
if (copy_json_string(signer, "serial_device", config->signer.serial_device,
|
||||
sizeof(config->signer.serial_device), 0) != 0) {
|
||||
config_set_error("signer.serial_device must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* nsigner_qrexec: target Qubes qube name + qrexec service name.
|
||||
* service_name defaults to "qubes.NsignerRpc" when empty. */
|
||||
if (copy_json_string(signer, "target_qube", config->signer.target_qube,
|
||||
sizeof(config->signer.target_qube), 0) != 0) {
|
||||
config_set_error("signer.target_qube must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
if (copy_json_string(signer, "service_name", config->signer.service_name,
|
||||
sizeof(config->signer.service_name), 0) != 0) {
|
||||
config_set_error("signer.service_name must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Phase 4 item 20: optional emergency local nsec for admin-alert fallback.
|
||||
* Parsed and stored only; no active fallback behavior is wired yet. */
|
||||
if (copy_json_string(signer, "emergency_local_nsec",
|
||||
config->signer.emergency_local_nsec,
|
||||
sizeof(config->signer.emergency_local_nsec), 0) != 0) {
|
||||
config_set_error("signer.emergency_local_nsec must be a string when provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
|
||||
cJSON* tools = cJSON_GetObjectItemCaseSensitive(root, "tools");
|
||||
if (!tools || !cJSON_IsObject(tools)) {
|
||||
@@ -356,7 +226,6 @@ static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
|
||||
cJSON* api_default_max_turns = cJSON_GetObjectItemCaseSensitive(tools, "api_default_max_turns");
|
||||
cJSON* api_max_turns_ceiling = cJSON_GetObjectItemCaseSensitive(tools, "api_max_turns_ceiling");
|
||||
cJSON* stall_repeat_threshold = cJSON_GetObjectItemCaseSensitive(tools, "stall_repeat_threshold");
|
||||
cJSON* max_context_bytes = cJSON_GetObjectItemCaseSensitive(tools, "max_context_bytes");
|
||||
cJSON* local_http_fetch_default_timeout_seconds =
|
||||
cJSON_GetObjectItemCaseSensitive(tools, "local_http_fetch_default_timeout_seconds");
|
||||
cJSON* local_http_fetch_max_timeout_seconds =
|
||||
@@ -383,9 +252,6 @@ static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
|
||||
if (stall_repeat_threshold && cJSON_IsNumber(stall_repeat_threshold)) {
|
||||
config->tools.stall_repeat_threshold = (int)stall_repeat_threshold->valuedouble;
|
||||
}
|
||||
if (max_context_bytes && cJSON_IsNumber(max_context_bytes)) {
|
||||
config->tools.max_context_bytes = (int)max_context_bytes->valuedouble;
|
||||
}
|
||||
if (local_http_fetch_default_timeout_seconds && cJSON_IsNumber(local_http_fetch_default_timeout_seconds)) {
|
||||
config->tools.local_http_fetch_default_timeout_seconds =
|
||||
(int)local_http_fetch_default_timeout_seconds->valuedouble;
|
||||
@@ -428,7 +294,7 @@ static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
|
||||
}
|
||||
|
||||
if (config->tools.max_turns < 1) {
|
||||
config->tools.max_turns = 40;
|
||||
config->tools.max_turns = 8;
|
||||
}
|
||||
if (config->tools.trigger_max_turns < 1) {
|
||||
config->tools.trigger_max_turns = config->tools.max_turns;
|
||||
@@ -445,9 +311,6 @@ static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
|
||||
if (config->tools.stall_repeat_threshold < 2) {
|
||||
config->tools.stall_repeat_threshold = 3;
|
||||
}
|
||||
if (config->tools.max_context_bytes < 4096) {
|
||||
config->tools.max_context_bytes = 128 * 1024;
|
||||
}
|
||||
if (config->tools.local_http_fetch_default_timeout_seconds < 1) {
|
||||
config->tools.local_http_fetch_default_timeout_seconds = 20;
|
||||
}
|
||||
@@ -701,18 +564,6 @@ static int parse_agent_payload_object(cJSON* agent_obj, didactyl_config_t* confi
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* max_turns = cJSON_GetObjectItemCaseSensitive(agent_obj, "max_turns");
|
||||
if (max_turns) {
|
||||
if (!cJSON_IsNumber(max_turns)) {
|
||||
return -1;
|
||||
}
|
||||
int parsed = (int)max_turns->valuedouble;
|
||||
if (parsed < 1) {
|
||||
parsed = 40;
|
||||
}
|
||||
config->tools.max_turns = parsed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1573,12 +1424,11 @@ int config_load(const char* path, didactyl_config_t* config) {
|
||||
config->dm_protocol = DM_PROTOCOL_NIP04;
|
||||
|
||||
config->tools.enabled = 1;
|
||||
config->tools.max_turns = 40;
|
||||
config->tools.max_turns = 20;
|
||||
config->tools.trigger_max_turns = 12;
|
||||
config->tools.api_default_max_turns = 8;
|
||||
config->tools.api_max_turns_ceiling = 32;
|
||||
config->tools.stall_repeat_threshold = 3;
|
||||
config->tools.max_context_bytes = 128 * 1024;
|
||||
config->tools.local_http_fetch_default_timeout_seconds = 20;
|
||||
config->tools.local_http_fetch_max_timeout_seconds = 120;
|
||||
config->tools.blossom_max_upload_bytes = 16 * 1024 * 1024;
|
||||
@@ -1621,24 +1471,6 @@ int config_load(const char* path, didactyl_config_t* config) {
|
||||
config->cashu_wallet.auto_load = 1;
|
||||
config->cashu_wallet.mint_timeout_seconds = 30;
|
||||
|
||||
/* Signer provider defaults: local mode, backward compatible. */
|
||||
snprintf(config->signer.mode, sizeof(config->signer.mode), "%s", "local");
|
||||
config->signer.socket_name[0] = '\0';
|
||||
snprintf(config->signer.role, sizeof(config->signer.role), "%s", "main");
|
||||
config->signer.nostr_index = -1; /* -1 = unset; use role selector */
|
||||
config->signer.timeout_ms = 15000;
|
||||
config->signer.auth_privkey_hex[0] = '\0';
|
||||
config->signer.tcp_host[0] = '\0';
|
||||
config->signer.tcp_port = 0;
|
||||
config->signer.serial_device[0] = '\0';
|
||||
config->signer.fds_read_fd = -1;
|
||||
config->signer.fds_write_fd = -1;
|
||||
config->signer.target_qube[0] = '\0';
|
||||
config->signer.service_name[0] = '\0';
|
||||
/* Phase 4 item 20: emergency local nsec placeholder. Off by default;
|
||||
* no active fallback behavior yet (documentation/feature-flag only). */
|
||||
config->signer.emergency_local_nsec[0] = '\0';
|
||||
|
||||
char* raw_buf = NULL;
|
||||
size_t raw_len = 0;
|
||||
if (read_file_to_buffer(path, &raw_buf, &raw_len) != 0) {
|
||||
@@ -1773,15 +1605,7 @@ int config_load(const char* path, didactyl_config_t* config) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (parse_signer_config(root, config) != 0) {
|
||||
/* parse_signer_config sets a specific error message. */
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Only decode the raw private key in local mode. In remote (nsigner_*)
|
||||
* modes the agent process never holds the nsec; the pubkey is learned
|
||||
* from the signer at startup via nostr_signer_get_public_key(). */
|
||||
if (!signer_mode_is_remote(config->signer.mode) && config->keys.nsec[0] != '\0') {
|
||||
if (config->keys.nsec[0] != '\0') {
|
||||
if (decode_private_key(config->keys.nsec, config->keys.private_key) != 0) {
|
||||
config_set_error("keys.nsec/key.nsec must be valid nsec1... or 64-char hex private key");
|
||||
goto cleanup;
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
#define OW_MAX_URL_LEN 256
|
||||
#define OW_MAX_KEY_LEN 256
|
||||
#define OW_MAX_MODEL_LEN 128
|
||||
#define OW_MAX_SIGNER_MODE_LEN 32
|
||||
#define OW_MAX_SIGNER_SOCKET_LEN 128
|
||||
#define OW_MAX_SIGNER_ROLE_LEN 32
|
||||
#define OW_MAX_SIGNER_HOST_LEN 128
|
||||
#define OW_MAX_SIGNER_AUTH_HEX_LEN 65
|
||||
|
||||
#define OW_MAX_STRANGER_RESPONSE_LEN 512
|
||||
|
||||
@@ -56,7 +51,6 @@ typedef struct {
|
||||
int api_default_max_turns;
|
||||
int api_max_turns_ceiling;
|
||||
int stall_repeat_threshold;
|
||||
int max_context_bytes;
|
||||
int local_http_fetch_default_timeout_seconds;
|
||||
int local_http_fetch_max_timeout_seconds;
|
||||
shell_tools_config_t shell;
|
||||
@@ -121,69 +115,6 @@ typedef struct {
|
||||
int mint_timeout_seconds;
|
||||
} cashu_wallet_config_t;
|
||||
|
||||
/* Signer provider configuration.
|
||||
*
|
||||
* mode selects how signing/encryption operations are routed:
|
||||
* "local" - raw 32-byte key held in cfg.keys.private_key (default,
|
||||
* backward compatible, output-equivalent to legacy path).
|
||||
* "nsigner_unix" - delegate to a running n_signer over an AF_UNIX abstract
|
||||
* socket. The agent process holds no nsec.
|
||||
* "nsigner_tcp" - delegate to a running n_signer over TCP (host:port).
|
||||
* Requires auth_privkey_hex for the kind-27235 auth envelope.
|
||||
* "nsigner_serial" - delegate to a running n_signer over a USB CDC-ACM serial
|
||||
* device (e.g. /dev/ttyACM0). The agent process holds no nsec.
|
||||
* "nsigner_fds" - delegate to a running n_signer over a pre-connected fd
|
||||
* pair (read_fd, write_fd). CLI/runtime-only: the fds cannot
|
||||
* be persisted to genesis and must be supplied via --signer-fds
|
||||
* at boot. Intended for qrexec-style fd-passing deployments.
|
||||
* "nsigner_qrexec" - delegate to a running n_signer in another Qubes OS qube
|
||||
* via qrexec (spawns `qrexec-client-vm <target_qube>
|
||||
* <service_name>`). No network; hypervisor-vouched caller
|
||||
* identity. The agent process holds no nsec.
|
||||
*
|
||||
* Missing signer block behaves as mode="local" using keys.nsec.
|
||||
* In any nsigner_* mode, keys.nsec is optional and ignored; the agent pubkey
|
||||
* is learned from the signer via nostr_signer_get_public_key().
|
||||
*/
|
||||
typedef struct {
|
||||
char mode[OW_MAX_SIGNER_MODE_LEN]; /* "local" | "nsigner_unix" | "nsigner_tcp" | "nsigner_serial" | "nsigner_fds" | "nsigner_qrexec" */
|
||||
char socket_name[OW_MAX_SIGNER_SOCKET_LEN]; /* nsigner_unix: abstract socket name (without @); "" = auto-discover */
|
||||
char role[OW_MAX_SIGNER_ROLE_LEN]; /* n_signer role selector (default "main") */
|
||||
int nostr_index; /* n_signer key index selector (-1 = unset; use role). When set, overrides role. */
|
||||
int timeout_ms; /* per-call timeout (default 15000) */
|
||||
char auth_privkey_hex[OW_MAX_SIGNER_AUTH_HEX_LEN]; /* optional, TCP auth envelope only */
|
||||
/* nsigner_tcp parsed host/port (populated from --signer-tcp or config) */
|
||||
char tcp_host[OW_MAX_SIGNER_HOST_LEN];
|
||||
int tcp_port;
|
||||
/* nsigner_serial: device path (e.g. "/dev/ttyACM0"). Populated from
|
||||
* --signer-serial or the signer.serial_device config field. */
|
||||
char serial_device[OW_MAX_URL_LEN];
|
||||
/* nsigner_fds: pre-connected file descriptors. CLI/runtime-only — NOT
|
||||
* persisted to genesis. Populated from --signer-fds <read:write>. */
|
||||
int fds_read_fd;
|
||||
int fds_write_fd;
|
||||
/* nsigner_qrexec: target Qubes qube name + qrexec service name.
|
||||
* Populated from --signer-qrexec <target_qube> or the signer.target_qube /
|
||||
* signer.service_name config fields. service_name defaults to
|
||||
* "qubes.NsignerRpc" when empty. */
|
||||
char target_qube[OW_MAX_SIGNER_HOST_LEN];
|
||||
char service_name[OW_MAX_SIGNER_SOCKET_LEN];
|
||||
/* Optional emergency local nsec for admin-alert fallback (Phase 4 item 20).
|
||||
*
|
||||
* OFF by default (empty string). When populated AND a remote signer is
|
||||
* unreachable, a future phase may use this in-process key ONLY to sign a
|
||||
* minimal admin alert DM reporting the signer outage. This reintroduces
|
||||
* the nsec into the agent process for that one purpose, which is a
|
||||
* deliberate operator-accepted tradeoff: better to leak the nsec briefly
|
||||
* to notify the admin than to fail silently.
|
||||
*
|
||||
* CURRENT STATUS: parsed and stored only. No active fallback behavior is
|
||||
* implemented yet (feature flag / documentation placeholder). The field
|
||||
* exists so operators can pre-stage the value; the fallback path is
|
||||
* deferred until the supervisor/health surface (item 19) is proven. */
|
||||
char emergency_local_nsec[OW_MAX_KEY_LEN]; /* "" = disabled (default) */
|
||||
} signer_config_t;
|
||||
|
||||
typedef struct {
|
||||
agent_keys_t keys;
|
||||
admin_config_t admin;
|
||||
@@ -197,7 +128,6 @@ typedef struct {
|
||||
triggers_config_t triggers;
|
||||
api_config_t api;
|
||||
cashu_wallet_config_t cashu_wallet;
|
||||
signer_config_t signer;
|
||||
startup_event_t* startup_events;
|
||||
int startup_event_count;
|
||||
encrypted_event_t* encrypted_events;
|
||||
|
||||
@@ -12,13 +12,11 @@
|
||||
#define DIDACTYL_STARTUP_KIND_PROFILE 0
|
||||
#define DIDACTYL_STARTUP_KIND_CONTACTS 3
|
||||
#define DIDACTYL_STARTUP_KIND_RELAYS 10002
|
||||
#define DIDACTYL_STARTUP_KIND_DM_RELAYS 10050
|
||||
|
||||
static const int DIDACTYL_DEFAULT_STARTUP_EVENT_KINDS[] = {
|
||||
DIDACTYL_STARTUP_KIND_PROFILE,
|
||||
DIDACTYL_STARTUP_KIND_CONTACTS,
|
||||
DIDACTYL_STARTUP_KIND_RELAYS,
|
||||
DIDACTYL_STARTUP_KIND_DM_RELAYS
|
||||
DIDACTYL_STARTUP_KIND_RELAYS
|
||||
};
|
||||
|
||||
#define DIDACTYL_DEFAULT_STARTUP_EVENT_KIND_COUNT ((int)(sizeof(DIDACTYL_DEFAULT_STARTUP_EVENT_KINDS) / sizeof(DIDACTYL_DEFAULT_STARTUP_EVENT_KINDS[0])))
|
||||
@@ -27,7 +25,6 @@ static const int DIDACTYL_DEFAULT_STARTUP_EVENT_KINDS[] = {
|
||||
#define DIDACTYL_DEFAULT_KIND3_CONTENT ""
|
||||
#define DIDACTYL_DEFAULT_KIND3_TAGS_JSON_TEMPLATE "[[\"p\",\"%s\"]]"
|
||||
#define DIDACTYL_DEFAULT_KIND10002_CONTENT ""
|
||||
#define DIDACTYL_DEFAULT_KIND10050_CONTENT ""
|
||||
|
||||
static const char* DIDACTYL_DEFAULT_RELAYS[] = {
|
||||
"wss://relay.damus.io",
|
||||
|
||||
+5
-150
@@ -17,7 +17,6 @@
|
||||
#include "cjson/cJSON.h"
|
||||
#include "debug.h"
|
||||
#include "mongoose.h"
|
||||
#include "signer_health.h"
|
||||
|
||||
static http_api_context_t g_api_ctx;
|
||||
static int g_api_initialized = 0;
|
||||
@@ -401,38 +400,6 @@ static int append_tool_result_message_local(cJSON* messages, const char* tool_ca
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int maybe_append_loaded_skill_instructions_local(cJSON* messages,
|
||||
const char* tool_name,
|
||||
const char* tool_result_json) {
|
||||
if (!messages || !tool_name || strcmp(tool_name, "skill_load") != 0 || !tool_result_json) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_Parse(tool_result_json);
|
||||
if (!root || !cJSON_IsObject(root)) {
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* ok = cJSON_GetObjectItemCaseSensitive(root, "success");
|
||||
cJSON* type = cJSON_GetObjectItemCaseSensitive(root, "type");
|
||||
cJSON* instructions = cJSON_GetObjectItemCaseSensitive(root, "instructions");
|
||||
|
||||
int should_apply = (ok && cJSON_IsBool(ok) && cJSON_IsTrue(ok) &&
|
||||
type && cJSON_IsString(type) && type->valuestring &&
|
||||
strcmp(type->valuestring, "skill_instructions") == 0 &&
|
||||
instructions && cJSON_IsString(instructions) && instructions->valuestring &&
|
||||
instructions->valuestring[0] != '\0');
|
||||
|
||||
int rc = 0;
|
||||
if (should_apply) {
|
||||
rc = append_simple_message_local(messages, "system", instructions->valuestring);
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int is_space_char_local(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
||||
}
|
||||
@@ -473,51 +440,6 @@ static char* build_slash_args_json_local(const char* raw_args) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static char* build_run_args_json_local(const char* raw_args) {
|
||||
const char* s = skip_spaces_local(raw_args);
|
||||
if (!s || s[0] == '\0') {
|
||||
return json_error_string_local("/run requires a skill identifier");
|
||||
}
|
||||
|
||||
const char* ident_start = s;
|
||||
while (*s && !is_space_char_local(*s)) s++;
|
||||
|
||||
size_t ident_len = (size_t)(s - ident_start);
|
||||
if (ident_len == 0 || ident_len >= 256U) {
|
||||
return json_error_string_local("/run skill identifier is invalid");
|
||||
}
|
||||
|
||||
char ident[256];
|
||||
memcpy(ident, ident_start, ident_len);
|
||||
ident[ident_len] = '\0';
|
||||
|
||||
const char* remaining = skip_spaces_local(s);
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
|
||||
const char* c1 = strchr(ident, ':');
|
||||
const char* c2 = c1 ? strchr(c1 + 1, ':') : NULL;
|
||||
if (c1 && c2 && c1 != ident && (size_t)(c2 - c1) == 65U && c2[1] != '\0') {
|
||||
size_t pubkey_len = (size_t)(c2 - (c1 + 1));
|
||||
char pubkey[65];
|
||||
memcpy(pubkey, c1 + 1, pubkey_len);
|
||||
pubkey[pubkey_len] = '\0';
|
||||
cJSON_AddStringToObject(out, "pubkey", pubkey);
|
||||
cJSON_AddStringToObject(out, "d_tag", c2 + 1);
|
||||
} else {
|
||||
cJSON_AddStringToObject(out, "d_tag", ident);
|
||||
}
|
||||
|
||||
if (remaining && remaining[0] != '\0') {
|
||||
cJSON_AddStringToObject(out, "args", remaining);
|
||||
}
|
||||
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
return json;
|
||||
}
|
||||
|
||||
static int append_textf_http(char** buf, size_t* cap, size_t* used, const char* fmt, ...) {
|
||||
if (!buf || !cap || !used || !fmt) return -1;
|
||||
|
||||
@@ -816,21 +738,7 @@ static char* build_slash_help_all_json_local(void) {
|
||||
|
||||
int adopted_count = 0;
|
||||
if (adoption_events && cJSON_IsArray(adoption_events) && cJSON_GetArraySize(adoption_events) > 0) {
|
||||
cJSON* ev0 = NULL;
|
||||
double best_created = -1.0;
|
||||
int an = cJSON_GetArraySize(adoption_events);
|
||||
for (int ai = 0; ai < an; ai++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(adoption_events, ai);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
if (!ev0 || created > best_created) {
|
||||
ev0 = ev;
|
||||
best_created = created;
|
||||
}
|
||||
}
|
||||
cJSON* ev0 = cJSON_GetArrayItem(adoption_events, 0);
|
||||
cJSON* tags = ev0 ? cJSON_GetObjectItemCaseSensitive(ev0, "tags") : NULL;
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
int tn = cJSON_GetArraySize(tags);
|
||||
@@ -945,8 +853,7 @@ static char* execute_slash_command_http(const char* message) {
|
||||
|
||||
char* result_json = NULL;
|
||||
if (raw_args && (strcmp(raw_args, "-h") == 0 || strcmp(raw_args, "--help") == 0)) {
|
||||
const char* help_name = (strcmp(tool_name, "run") == 0) ? "skill_run" : tool_name;
|
||||
result_json = build_slash_help_tool_json_local(help_name);
|
||||
result_json = build_slash_help_tool_json_local(tool_name);
|
||||
} else if (strcmp(tool_name, "help") == 0) {
|
||||
if (!raw_args || raw_args[0] == '\0') {
|
||||
result_json = build_slash_help_all_json_local();
|
||||
@@ -958,24 +865,14 @@ static char* execute_slash_command_http(const char* message) {
|
||||
i++;
|
||||
}
|
||||
help_tool[i] = '\0';
|
||||
const char* help_name = (strcmp(help_tool, "run") == 0) ? "skill_run" : help_tool;
|
||||
result_json = build_slash_help_tool_json_local(help_name);
|
||||
result_json = build_slash_help_tool_json_local(help_tool);
|
||||
}
|
||||
} else {
|
||||
char* args_json = NULL;
|
||||
const char* exec_tool_name = tool_name;
|
||||
|
||||
if (strcmp(tool_name, "run") == 0) {
|
||||
args_json = build_run_args_json_local(raw_args);
|
||||
exec_tool_name = "skill_run";
|
||||
} else {
|
||||
args_json = build_slash_args_json_local(raw_args);
|
||||
}
|
||||
|
||||
char* args_json = build_slash_args_json_local(raw_args);
|
||||
if (!args_json) {
|
||||
result_json = json_error_string_local("failed to parse slash args");
|
||||
} else {
|
||||
result_json = tools_execute(g_api_ctx.tools_ctx, exec_tool_name, args_json);
|
||||
result_json = tools_execute(g_api_ctx.tools_ctx, tool_name, args_json);
|
||||
free(args_json);
|
||||
}
|
||||
}
|
||||
@@ -1107,15 +1004,6 @@ static cJSON* run_prompt_with_tools_convo(cJSON* convo,
|
||||
final_response = strdup("Failed to append tool result.");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (maybe_append_loaded_skill_instructions_local(convo,
|
||||
tc->name,
|
||||
tool_result) != 0) {
|
||||
free(tool_result);
|
||||
llm_response_free(&resp);
|
||||
final_response = strdup("Failed to apply loaded skill instructions.");
|
||||
goto done;
|
||||
}
|
||||
free(tool_result);
|
||||
}
|
||||
|
||||
@@ -1229,39 +1117,6 @@ static void handle_status(struct mg_connection* c) {
|
||||
if (g_api_ctx.cfg) {
|
||||
cJSON_AddStringToObject(root, "pubkey", g_api_ctx.cfg->keys.public_key_hex);
|
||||
cJSON_AddNumberToObject(root, "relay_count", g_api_ctx.cfg->relay_count);
|
||||
/* Phase 4 item 19: out-of-band signer health surface. Expose the
|
||||
* configured signer mode and runtime health classification so an
|
||||
* operator can detect a signing outage from the HTTP status endpoint
|
||||
* without requiring a signed admin DM to come back. */
|
||||
cJSON_AddStringToObject(root, "signer_mode",
|
||||
g_api_ctx.cfg->signer.mode[0] ? g_api_ctx.cfg->signer.mode : "local");
|
||||
}
|
||||
|
||||
/* Signer runtime health (process-global, lock-protected snapshot). */
|
||||
signer_health_snapshot_t hs;
|
||||
if (signer_health_snapshot(&hs) == 0) {
|
||||
const char* state_str = "unknown";
|
||||
if (hs.state == SIGNER_HEALTH_HEALTHY) {
|
||||
state_str = "healthy";
|
||||
} else if (hs.state == SIGNER_HEALTH_UNHEALTHY) {
|
||||
state_str = "unhealthy";
|
||||
}
|
||||
cJSON_AddStringToObject(root, "signer_healthy",
|
||||
hs.state == SIGNER_HEALTH_HEALTHY ? "true" :
|
||||
hs.state == SIGNER_HEALTH_UNHEALTHY ? "false" : "unknown");
|
||||
cJSON_AddStringToObject(root, "signer_health_state", state_str);
|
||||
cJSON_AddStringToObject(root, "signer_last_error",
|
||||
hs.last_reason[0] ? hs.last_reason : "");
|
||||
if (hs.last_error_ts > 0) {
|
||||
cJSON_AddNumberToObject(root, "signer_last_error_ts", (double)hs.last_error_ts);
|
||||
} else {
|
||||
cJSON_AddNullToObject(root, "signer_last_error_ts");
|
||||
}
|
||||
if (hs.last_ok_ts > 0) {
|
||||
cJSON_AddNumberToObject(root, "signer_last_ok_ts", (double)hs.last_ok_ts);
|
||||
} else {
|
||||
cJSON_AddNullToObject(root, "signer_last_ok_ts");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_AddNumberToObject(root, "connected_relays", nostr_handler_connected_relay_count());
|
||||
|
||||
@@ -22,63 +22,36 @@ static int g_initialized = 0;
|
||||
static pthread_mutex_t g_llm_request_log_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static unsigned long g_llm_request_log_seq = 0;
|
||||
|
||||
static int provider_log_next_stamp_seq_local(char* stamp, size_t stamp_size, unsigned long* seq_out) {
|
||||
if (!stamp || stamp_size == 0 || !seq_out) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
stamp[0] = '\0';
|
||||
*seq_out = 0;
|
||||
|
||||
if (mkdir("context.logs", 0755) != 0 && errno != EEXIST) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm tm_info;
|
||||
localtime_r(&now, &tm_info);
|
||||
strftime(stamp, stamp_size, "%Y%m%dT%H%M%S", &tm_info);
|
||||
|
||||
pthread_mutex_lock(&g_llm_request_log_mutex);
|
||||
*seq_out = ++g_llm_request_log_seq;
|
||||
pthread_mutex_unlock(&g_llm_request_log_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void log_provider_payload_local(const char* payload,
|
||||
const char* stamp,
|
||||
unsigned long seq,
|
||||
const char* phase) {
|
||||
if (!payload || payload[0] == '\0' || !stamp || stamp[0] == '\0' || !phase || phase[0] == '\0') {
|
||||
static void log_provider_request_body_local(const char* body) {
|
||||
if (!body || body[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mkdir("context.logs", 0755) != 0 && errno != EEXIST) {
|
||||
return;
|
||||
}
|
||||
|
||||
char stamp[32] = {0};
|
||||
time_t now = time(NULL);
|
||||
struct tm tm_info;
|
||||
localtime_r(&now, &tm_info);
|
||||
strftime(stamp, sizeof(stamp), "%Y%m%dT%H%M%S", &tm_info);
|
||||
|
||||
pthread_mutex_lock(&g_llm_request_log_mutex);
|
||||
unsigned long seq = ++g_llm_request_log_seq;
|
||||
pthread_mutex_unlock(&g_llm_request_log_mutex);
|
||||
|
||||
char path[256] = {0};
|
||||
snprintf(path, sizeof(path), "context.logs/%s_%s%04lu.json", stamp, phase, seq);
|
||||
snprintf(path, sizeof(path), "context.logs/%s_req%04lu.json", stamp, seq);
|
||||
|
||||
FILE* out = fopen(path, "wb");
|
||||
if (!out) {
|
||||
return;
|
||||
}
|
||||
(void)fwrite(payload, 1, strlen(payload), out);
|
||||
(void)fwrite(body, 1, strlen(body), out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
/*
|
||||
* Provider payload logging (context.logs/) is gated behind DIDACTYL_CONTEXT_DEBUG.
|
||||
* Modes: "off" (or unset) = no logging, "init" or "full" = logging enabled.
|
||||
* This prevents full LLM conversation transcripts (system prompts, user messages,
|
||||
* tool calls, responses) from being written to disk in production.
|
||||
*/
|
||||
static int provider_log_enabled(void) {
|
||||
const char* mode = getenv("DIDACTYL_CONTEXT_DEBUG");
|
||||
if (!mode || mode[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
return strcmp(mode, "off") != 0;
|
||||
}
|
||||
|
||||
static int url_looks_like_websocket(const char* url) {
|
||||
if (!url) return 0;
|
||||
return (strncmp(url, "ws://", 5) == 0) || (strncmp(url, "wss://", 6) == 0);
|
||||
@@ -145,18 +118,8 @@ static char* perform_http_request(const char* url, const char* body, int is_post
|
||||
DEBUG_INFO("[didactyl] llm request: method=GET url=%s", url);
|
||||
}
|
||||
|
||||
char provider_log_stamp[32] = {0};
|
||||
unsigned long provider_log_seq = 0;
|
||||
int provider_log_has_pair = 0;
|
||||
const int do_provider_log = provider_log_enabled();
|
||||
|
||||
if (do_provider_log && is_post && body && body[0] != '\0') {
|
||||
if (provider_log_next_stamp_seq_local(provider_log_stamp,
|
||||
sizeof(provider_log_stamp),
|
||||
&provider_log_seq) == 0) {
|
||||
log_provider_payload_local(body, provider_log_stamp, provider_log_seq, "req");
|
||||
provider_log_has_pair = 1;
|
||||
}
|
||||
if (is_post && body) {
|
||||
log_provider_request_body_local(body);
|
||||
}
|
||||
|
||||
nostr_http_response_t resp;
|
||||
@@ -166,19 +129,6 @@ static char* perform_http_request(const char* url, const char* body, int is_post
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (do_provider_log && resp.body && resp.body_len > 0) {
|
||||
if (!provider_log_has_pair) {
|
||||
if (provider_log_next_stamp_seq_local(provider_log_stamp,
|
||||
sizeof(provider_log_stamp),
|
||||
&provider_log_seq) == 0) {
|
||||
provider_log_has_pair = 1;
|
||||
}
|
||||
}
|
||||
if (provider_log_has_pair) {
|
||||
log_provider_payload_local(resp.body, provider_log_stamp, provider_log_seq, "res");
|
||||
}
|
||||
}
|
||||
|
||||
if (resp.status_code < 200 || resp.status_code >= 300) {
|
||||
DEBUG_ERROR("[didactyl] llm http request failed: status=%ld", resp.status_code);
|
||||
if (resp.status_code == 101) {
|
||||
|
||||
+94
-971
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -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 2
|
||||
#define DIDACTYL_VERSION_PATCH 55
|
||||
#define DIDACTYL_VERSION "v0.2.55"
|
||||
#define DIDACTYL_VERSION_PATCH 27
|
||||
#define DIDACTYL_VERSION "v0.2.27"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
+14
-63
@@ -10,10 +10,6 @@
|
||||
#include "debug.h"
|
||||
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
|
||||
/* Optional process-lifetime signer for NIP-44 encrypt/decrypt of private
|
||||
* block-list content. Owned by main.c; non-owning here. */
|
||||
static nostr_signer_t* g_block_signer = NULL;
|
||||
|
||||
typedef struct {
|
||||
didactyl_config_t* cfg;
|
||||
int initialized;
|
||||
@@ -190,43 +186,22 @@ static int decrypt_private_tags_array(const char* encrypted_content, cJSON** out
|
||||
return *out_array ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Phase 3 item 13: route NIP-44 decrypt through the process signer when
|
||||
* available so remote signer modes keep the nsec out of the agent. The
|
||||
* signer verb returns a heap string via out-param; free it after parse.
|
||||
* Falls back to the legacy raw-key path when no signer is set. */
|
||||
const char* plain_src = NULL;
|
||||
char* plain_to_free = NULL;
|
||||
char* plaintext = NULL;
|
||||
size_t cap = strlen(encrypted_content) + 4096;
|
||||
char* plaintext = (char*)calloc(1, cap);
|
||||
if (!plaintext) return -1;
|
||||
|
||||
if (g_block_signer) {
|
||||
int rc = nostr_signer_nip44_decrypt(g_block_signer,
|
||||
g_block.cfg->keys.public_key_hex,
|
||||
encrypted_content,
|
||||
&plaintext);
|
||||
if (rc != NOSTR_SUCCESS || !plaintext) {
|
||||
return -1;
|
||||
}
|
||||
plain_src = plaintext;
|
||||
plain_to_free = plaintext;
|
||||
} else {
|
||||
size_t cap = strlen(encrypted_content) + 4096;
|
||||
plaintext = (char*)calloc(1, cap);
|
||||
if (!plaintext) return -1;
|
||||
int rc = nostr_nip44_decrypt(g_block.cfg->keys.private_key,
|
||||
g_block.cfg->keys.public_key,
|
||||
encrypted_content,
|
||||
plaintext,
|
||||
cap);
|
||||
if (rc != 0) {
|
||||
free(plaintext);
|
||||
return -1;
|
||||
}
|
||||
plain_src = plaintext;
|
||||
plain_to_free = plaintext;
|
||||
int rc = nostr_nip44_decrypt(g_block.cfg->keys.private_key,
|
||||
g_block.cfg->keys.public_key,
|
||||
encrypted_content,
|
||||
plaintext,
|
||||
cap);
|
||||
if (rc != 0) {
|
||||
free(plaintext);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* arr = cJSON_Parse(plain_src);
|
||||
free(plain_to_free);
|
||||
cJSON* arr = cJSON_Parse(plaintext);
|
||||
free(plaintext);
|
||||
if (!arr || !cJSON_IsArray(arr)) {
|
||||
cJSON_Delete(arr);
|
||||
return -1;
|
||||
@@ -243,25 +218,8 @@ static int encrypt_private_tags_array(cJSON* private_tags_array, char** out_cont
|
||||
char* plain = cJSON_PrintUnformatted(private_tags_array);
|
||||
if (!plain) return -1;
|
||||
|
||||
/* Phase 3 item 13: route NIP-44 encrypt through the process signer when
|
||||
* available. The signer verb returns a heap string via out-param. Falls
|
||||
* back to the legacy raw-key path when no signer is set. */
|
||||
char* cipher = NULL;
|
||||
if (g_block_signer) {
|
||||
int rc = nostr_signer_nip44_encrypt(g_block_signer,
|
||||
g_block.cfg->keys.public_key_hex,
|
||||
plain,
|
||||
&cipher);
|
||||
free(plain);
|
||||
if (rc != NOSTR_SUCCESS || !cipher) {
|
||||
return -1;
|
||||
}
|
||||
*out_content = cipher;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t cap = strlen(plain) * 4 + 4096;
|
||||
cipher = (char*)calloc(1, cap);
|
||||
char* cipher = (char*)calloc(1, cap);
|
||||
if (!cipher) {
|
||||
free(plain);
|
||||
return -1;
|
||||
@@ -353,12 +311,6 @@ static int publish_kind10000(cJSON* public_tags, cJSON* private_tags, char* out_
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nostr_block_list_set_signer(nostr_signer_t* signer) {
|
||||
pthread_mutex_lock(&g_block.mutex);
|
||||
g_block_signer = signer;
|
||||
pthread_mutex_unlock(&g_block.mutex);
|
||||
}
|
||||
|
||||
int nostr_block_list_init(didactyl_config_t* cfg) {
|
||||
if (!cfg) return -1;
|
||||
|
||||
@@ -413,7 +365,6 @@ void nostr_block_list_cleanup(void) {
|
||||
g_block.cfg = NULL;
|
||||
g_block.loaded = 0;
|
||||
g_block.initialized = 0;
|
||||
g_block_signer = NULL;
|
||||
pthread_mutex_unlock(&g_block.mutex);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,19 +3,11 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "cjson/cJSON.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_signer.h"
|
||||
|
||||
int nostr_block_list_init(didactyl_config_t* cfg);
|
||||
int nostr_block_list_load_from_relays(void);
|
||||
void nostr_block_list_cleanup(void);
|
||||
|
||||
/* Wire a process-lifetime signer handle (non-owning) so NIP-44 encrypt/decrypt
|
||||
* of the private block-list content routes through the signer verbs in remote
|
||||
* signer modes. Falls back to the legacy raw-key path when NULL. The caller
|
||||
* must keep the signer alive for the module's lifetime and free it after
|
||||
* nostr_block_list_cleanup. */
|
||||
void nostr_block_list_set_signer(nostr_signer_t* signer);
|
||||
|
||||
int nostr_block_list_is_pubkey_blocked(const char* pubkey_hex);
|
||||
int nostr_block_list_is_event_blocked(const char* event_id_hex);
|
||||
int nostr_block_list_is_hashtag_blocked(const char* hashtag);
|
||||
|
||||
+36
-460
@@ -15,28 +15,12 @@
|
||||
#include "nostr_block_list.h"
|
||||
#include "cashu_wallet.h"
|
||||
#include "agent.h"
|
||||
#include "signer_health.h"
|
||||
|
||||
/*
|
||||
* Older nostr_core_lib versions only expose:
|
||||
* - NOSTR_POOL_EOSE_MOST_RECENT
|
||||
* - NOSTR_POOL_EOSE_FIRST
|
||||
* Newer versions may add NOSTR_POOL_EOSE_ANY.
|
||||
*
|
||||
* Provide a compatibility alias so code using NOSTR_POOL_EOSE_ANY
|
||||
* still compiles against older libraries.
|
||||
*/
|
||||
#ifndef NOSTR_POOL_EOSE_ANY
|
||||
#define NOSTR_POOL_EOSE_ANY NOSTR_POOL_EOSE_FIRST
|
||||
#endif
|
||||
|
||||
#define NIP17_MAX_RELAYS 32
|
||||
#define NIP17_MAX_GIFT_WRAPS 8
|
||||
|
||||
static didactyl_config_t* g_cfg = NULL;
|
||||
static nostr_relay_pool_t* g_pool = NULL;
|
||||
/* Optional process-lifetime signer for relay pool AUTH. Owned by main.c. */
|
||||
static nostr_signer_t* g_auth_signer = NULL;
|
||||
static dm_callback_t g_dm_callback = NULL;
|
||||
static void* g_dm_user_data = NULL;
|
||||
static int g_poll_counter = 0;
|
||||
@@ -1051,26 +1035,6 @@ static int nip44_encrypt_self_local(const char* plaintext, char** out_ciphertext
|
||||
}
|
||||
|
||||
const char* plain = plaintext ? plaintext : "";
|
||||
|
||||
/* Phase 3 item 13: route self-encrypt through the process signer when
|
||||
* available so remote signer modes keep the nsec out of the agent. The
|
||||
* signer verb returns a heap string via out-param. Falls back to the
|
||||
* legacy raw-key path when no signer is set (local mode / pre-Phase-3). */
|
||||
if (g_auth_signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_encrypt(g_auth_signer,
|
||||
g_cfg->keys.public_key_hex,
|
||||
plain,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) {
|
||||
signer_health_record_failure(rc, "nip44_encrypt(self)");
|
||||
return -1;
|
||||
}
|
||||
signer_health_record_ok();
|
||||
*out_ciphertext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = (strlen(plain) * 4U) + 1024U;
|
||||
char* ciphertext = (char*)malloc(out_cap);
|
||||
if (!ciphertext) {
|
||||
@@ -1096,23 +1060,6 @@ static int nip44_decrypt_self_local(const char* ciphertext, char** out_plaintext
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Phase 3 item 13: route self-decrypt through the process signer when
|
||||
* available. Falls back to the legacy raw-key path when no signer is set. */
|
||||
if (g_auth_signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_decrypt(g_auth_signer,
|
||||
g_cfg->keys.public_key_hex,
|
||||
ciphertext,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) {
|
||||
signer_health_record_failure(rc, "nip44_decrypt(self)");
|
||||
return -1;
|
||||
}
|
||||
signer_health_record_ok();
|
||||
*out_plaintext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = strlen(ciphertext) + 1024U;
|
||||
char* plaintext = (char*)malloc(out_cap);
|
||||
if (!plaintext) {
|
||||
@@ -1550,7 +1497,6 @@ static void register_trigger_from_self_skill_event(cJSON* event) {
|
||||
|
||||
static void self_skill_cache_upsert_event_locked(cJSON* event) {
|
||||
if (!event || !cJSON_IsObject(event)) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: event missing or not object");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1558,51 +1504,34 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) {
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
|
||||
cJSON* ev_id = cJSON_GetObjectItemCaseSensitive(event, "id");
|
||||
|
||||
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1;
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
|
||||
DEBUG_INFO("[didactyl] self-skill upsert candidate: kind=%d author=%.16s... id=%.16s... created_at=%.0f",
|
||||
kind_val,
|
||||
(pubkey && cJSON_IsString(pubkey) && pubkey->valuestring) ? pubkey->valuestring : "?",
|
||||
(ev_id && cJSON_IsString(ev_id) && ev_id->valuestring) ? ev_id->valuestring : "?",
|
||||
created);
|
||||
|
||||
if (!kind || !cJSON_IsNumber(kind) ||
|
||||
!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring ||
|
||||
!tags || !cJSON_IsArray(tags)) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: missing required kind/pubkey/tags");
|
||||
return;
|
||||
}
|
||||
|
||||
int kind_val = (int)kind->valuedouble;
|
||||
if (kind_val != 31123 && kind_val != 31124 && kind_val != 10123) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_cfg) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: config unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
int is_agent_author = (strcmp(pubkey->valuestring, g_cfg->keys.public_key_hex) == 0);
|
||||
int is_admin_author = (g_cfg->admin.pubkey[0] != '\0' && strcmp(pubkey->valuestring, g_cfg->admin.pubkey) == 0);
|
||||
if (kind_val == 10123) {
|
||||
if (!is_agent_author) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: kind=10123 author is not self");
|
||||
return;
|
||||
}
|
||||
if (!is_agent_author && !is_admin_author) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_admin_author && kind_val == 31124) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: admin cannot own private skill (31124)");
|
||||
if (is_admin_author && kind_val != 31123) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* d_tag = find_tag_value_local(tags, "d");
|
||||
if ((kind_val == 31123 || kind_val == 31124) && (!d_tag || d_tag[0] == '\0')) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: parameterized kind missing d-tag");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1610,11 +1539,12 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) {
|
||||
cJSON_Delete(g_self_skill_events);
|
||||
g_self_skill_events = cJSON_CreateArray();
|
||||
if (!g_self_skill_events) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: failed to create cache array");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
|
||||
int n = cJSON_GetArraySize(g_self_skill_events);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* existing = cJSON_GetArrayItem(g_self_skill_events, i);
|
||||
@@ -1646,56 +1576,23 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) {
|
||||
cJSON* ex_created_at = cJSON_GetObjectItemCaseSensitive(existing, "created_at");
|
||||
double ex_created = (ex_created_at && cJSON_IsNumber(ex_created_at)) ? ex_created_at->valuedouble : 0.0;
|
||||
if (created < ex_created) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: stale (existing created_at=%.0f >= new created_at=%.0f)",
|
||||
ex_created,
|
||||
created);
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON* dup = cJSON_Duplicate(event, 1);
|
||||
if (!dup) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: failed to duplicate event");
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON_ReplaceItemInArray(g_self_skill_events, i, dup);
|
||||
DEBUG_INFO("[didactyl] self-skill upsert accepted: replaced existing (old created_at=%.0f)", ex_created);
|
||||
if (kind_val == 10123) {
|
||||
int a_count = 0;
|
||||
cJSON* t;
|
||||
cJSON_ArrayForEach(t, tags) {
|
||||
if (cJSON_IsArray(t) && cJSON_GetArraySize(t) >= 2) {
|
||||
cJSON* t0 = cJSON_GetArrayItem(t, 0);
|
||||
if (t0 && cJSON_IsString(t0) && t0->valuestring && strcmp(t0->valuestring, "a") == 0) {
|
||||
a_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_INFO("[didactyl] kind 10123 accepted with %d 'a' tags", a_count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON* dup = cJSON_Duplicate(event, 1);
|
||||
if (!dup) {
|
||||
DEBUG_INFO("[didactyl] self-skill upsert rejected: failed to duplicate event");
|
||||
return;
|
||||
}
|
||||
cJSON_AddItemToArray(g_self_skill_events, dup);
|
||||
DEBUG_INFO("[didactyl] self-skill upsert accepted: new entry added");
|
||||
if (kind_val == 10123) {
|
||||
int a_count = 0;
|
||||
cJSON* t;
|
||||
cJSON_ArrayForEach(t, tags) {
|
||||
if (cJSON_IsArray(t) && cJSON_GetArraySize(t) >= 2) {
|
||||
cJSON* t0 = cJSON_GetArrayItem(t, 0);
|
||||
if (t0 && cJSON_IsString(t0) && t0->valuestring && strcmp(t0->valuestring, "a") == 0) {
|
||||
a_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_INFO("[didactyl] kind 10123 accepted with %d 'a' tags", a_count);
|
||||
}
|
||||
}
|
||||
|
||||
static int hex_to_pubkey(const char* hex, unsigned char out_pubkey[32]) {
|
||||
@@ -1958,38 +1855,19 @@ static void on_event(cJSON* event, const char* relay_url, void* user_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
decrypted = (char*)malloc(NOSTR_NIP04_MAX_PLAINTEXT_SIZE);
|
||||
if (!decrypted) {
|
||||
fprintf(stderr, "[didactyl] failed to allocate DM decrypt buffer\n");
|
||||
return;
|
||||
}
|
||||
decrypted[0] = '\0';
|
||||
|
||||
trace_event_json("received encrypted DM event:", event);
|
||||
|
||||
/* Phase 3 item 13: route NIP-04 decrypt through the process signer
|
||||
* when available so remote signer modes keep the nsec out of the
|
||||
* agent. The signer verb returns a heap string via out-param. Falls
|
||||
* back to the legacy raw-key path when no signer is set. */
|
||||
if (g_auth_signer) {
|
||||
char* signer_out = NULL;
|
||||
int dec_rc = nostr_signer_nip04_decrypt(g_auth_signer,
|
||||
sender_pubkey_hex,
|
||||
content->valuestring,
|
||||
&signer_out);
|
||||
if (dec_rc != NOSTR_SUCCESS || !signer_out) {
|
||||
signer_health_record_failure(dec_rc, "nip04_decrypt(incoming DM)");
|
||||
fprintf(stdout, "[didactyl] failed to decrypt incoming DM from %.16s...\n", sender_pubkey_hex);
|
||||
return;
|
||||
}
|
||||
signer_health_record_ok();
|
||||
decrypted = signer_out;
|
||||
} else {
|
||||
decrypted = (char*)malloc(NOSTR_NIP04_MAX_PLAINTEXT_SIZE);
|
||||
if (!decrypted) {
|
||||
fprintf(stderr, "[didactyl] failed to allocate DM decrypt buffer\n");
|
||||
return;
|
||||
}
|
||||
decrypted[0] = '\0';
|
||||
|
||||
if (nostr_nip04_decrypt(g_cfg->keys.private_key, sender_pubkey, content->valuestring, decrypted, NOSTR_NIP04_MAX_PLAINTEXT_SIZE) != NOSTR_SUCCESS) {
|
||||
fprintf(stdout, "[didactyl] failed to decrypt incoming DM from %.16s...\n", sender_pubkey_hex);
|
||||
free(decrypted);
|
||||
return;
|
||||
}
|
||||
if (nostr_nip04_decrypt(g_cfg->keys.private_key, sender_pubkey, content->valuestring, decrypted, NOSTR_NIP04_MAX_PLAINTEXT_SIZE) != NOSTR_SUCCESS) {
|
||||
fprintf(stdout, "[didactyl] failed to decrypt incoming DM from %.16s...\n", sender_pubkey_hex);
|
||||
free(decrypted);
|
||||
return;
|
||||
}
|
||||
|
||||
trace_plaintext_dm("received decrypted DM content:", decrypted);
|
||||
@@ -2000,16 +1878,7 @@ static void on_event(cJSON* event, const char* relay_url, void* user_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Phase 3 item 14: prefer signer-aware NIP-17 unwrap when a process
|
||||
* signer is available; fall back to the legacy raw-key path in local
|
||||
* mode / pre-Phase-3. Gift-wrap event ownership is unchanged: the
|
||||
* returned rumor cJSON is owned by us and freed below. */
|
||||
cJSON* rumor = NULL;
|
||||
if (g_auth_signer) {
|
||||
rumor = nostr_nip17_receive_dm_with_signer(event, g_auth_signer);
|
||||
} else {
|
||||
rumor = nostr_nip17_receive_dm(event, g_cfg->keys.private_key);
|
||||
}
|
||||
cJSON* rumor = nostr_nip17_receive_dm(event, g_cfg->keys.private_key);
|
||||
if (!rumor) {
|
||||
DEBUG_TRACE("[didactyl] DEBUG on_event: failed to unwrap/decrypt NIP-17 gift wrap %.16s...",
|
||||
event_id_hex ? event_id_hex : "<no-id>");
|
||||
@@ -2588,10 +2457,6 @@ static void on_wallet_event(cJSON* event, const char* relay_url, void* user_data
|
||||
(void)cashu_wallet_load_from_relays();
|
||||
}
|
||||
|
||||
void nostr_handler_set_signer(nostr_signer_t* signer) {
|
||||
g_auth_signer = signer;
|
||||
}
|
||||
|
||||
int nostr_handler_init(didactyl_config_t* config) {
|
||||
if (!config) {
|
||||
return -1;
|
||||
@@ -2634,28 +2499,16 @@ int nostr_handler_init(didactyl_config_t* config) {
|
||||
nostr_pool_reconnect_config_t reconnect = *nostr_pool_reconnect_config_default();
|
||||
reconnect.enable_auto_reconnect = 1;
|
||||
reconnect.ping_interval_seconds = 20;
|
||||
reconnect.pong_timeout_seconds = 30;
|
||||
reconnect.pong_timeout_seconds = 10;
|
||||
|
||||
g_pool = nostr_relay_pool_create(&reconnect);
|
||||
if (!g_pool) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Relay pool NIP-42 AUTH. When a process-lifetime signer is available
|
||||
* (set via nostr_handler_set_signer), route AUTH signing through it so
|
||||
* remote signer modes can keep the nsec out of the agent process. In
|
||||
* local mode the signer wraps the same raw key, so behavior is identical.
|
||||
* Falls back to the legacy raw-key path when no signer is set. */
|
||||
if (g_auth_signer) {
|
||||
if (nostr_relay_pool_set_auth_with_signer(g_pool, g_auth_signer, 1) != NOSTR_SUCCESS) {
|
||||
fprintf(stderr, "[didactyl] failed to enable relay pool auth (signer path)\n");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (nostr_relay_pool_set_auth(g_pool, g_cfg->keys.private_key, 1) != NOSTR_SUCCESS) {
|
||||
fprintf(stderr, "[didactyl] failed to enable relay pool auth\n");
|
||||
return -1;
|
||||
}
|
||||
if (nostr_relay_pool_set_auth(g_pool, g_cfg->keys.private_key, 1) != NOSTR_SUCCESS) {
|
||||
fprintf(stderr, "[didactyl] failed to enable relay pool auth\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < g_cfg->relay_count; i++) {
|
||||
@@ -2907,10 +2760,6 @@ int nostr_handler_subscribe_self_skills(void) {
|
||||
cJSON_AddItemToObject(filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(filter, "limit", 300);
|
||||
|
||||
char* filter_str = cJSON_PrintUnformatted(filter);
|
||||
DEBUG_INFO("[didactyl] self-skill subscription filter: %s", filter_str ? filter_str : "(null)");
|
||||
free(filter_str);
|
||||
|
||||
pthread_mutex_lock(&g_subscription_mutex);
|
||||
managed_subscriptions_init_defaults();
|
||||
if (managed_subscription_register_locked(MANAGED_SUB_SELF_SKILLS,
|
||||
@@ -2920,7 +2769,7 @@ int nostr_handler_subscribe_self_skills(void) {
|
||||
NULL,
|
||||
0,
|
||||
0,
|
||||
NOSTR_POOL_EOSE_ANY,
|
||||
NOSTR_POOL_EOSE_FIRST,
|
||||
12,
|
||||
14,
|
||||
1) != 0) {
|
||||
@@ -2986,9 +2835,6 @@ int nostr_handler_subscribe_dms(dm_callback_t callback, void* user_data) {
|
||||
const int need_kind4 = (g_cfg->dm_protocol == DM_PROTOCOL_NIP04 || g_cfg->dm_protocol == DM_PROTOCOL_BOTH) ? 1 : 0;
|
||||
const int need_kind1059 = (g_cfg->dm_protocol == DM_PROTOCOL_NIP17 || g_cfg->dm_protocol == DM_PROTOCOL_BOTH) ? 1 : 0;
|
||||
|
||||
DEBUG_INFO("[didactyl] DM subscribe: dm_protocol=%d need_kind4=%d need_kind1059=%d g_start_time=%ld",
|
||||
(int)g_cfg->dm_protocol, need_kind4, need_kind1059, (long)g_start_time);
|
||||
|
||||
if (!need_kind4 && !need_kind1059) {
|
||||
DEBUG_WARN("[didactyl] DM subscription skipped: no protocol selected");
|
||||
return -1;
|
||||
@@ -3039,6 +2885,7 @@ int nostr_handler_subscribe_dms(dm_callback_t callback, void* user_data) {
|
||||
cJSON_AddItemToObject(filter1059, "kinds", kinds1059);
|
||||
cJSON_AddItemToArray(p_values1059, cJSON_CreateString(g_cfg->keys.public_key_hex));
|
||||
cJSON_AddItemToObject(filter1059, "#p", p_values1059);
|
||||
cJSON_AddNumberToObject(filter1059, "since", (double)g_start_time);
|
||||
cJSON_AddNumberToObject(filter1059, "limit", 400);
|
||||
}
|
||||
|
||||
@@ -3228,33 +3075,16 @@ int nostr_handler_send_dm_with_role(const char* recipient_pubkey_hex,
|
||||
|
||||
trace_plaintext_dm("sending plaintext DM content:", message);
|
||||
|
||||
/* Phase 3 item 13: route NIP-04 encrypt through the process signer when
|
||||
* available so remote signer modes keep the nsec out of the agent. The
|
||||
* signer verb returns a heap string via out-param. Falls back to the
|
||||
* legacy raw-key path when no signer is set. */
|
||||
char* encrypted = NULL;
|
||||
if (g_auth_signer) {
|
||||
int enc_rc = nostr_signer_nip04_encrypt(g_auth_signer,
|
||||
recipient_pubkey_hex,
|
||||
message,
|
||||
&encrypted);
|
||||
if (enc_rc != NOSTR_SUCCESS || !encrypted) {
|
||||
signer_health_record_failure(enc_rc, "nip04_encrypt(outgoing DM)");
|
||||
return -1;
|
||||
}
|
||||
signer_health_record_ok();
|
||||
} else {
|
||||
encrypted = (char*)malloc(NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
||||
if (!encrypted) {
|
||||
fprintf(stderr, "[didactyl] failed to allocate DM encrypt buffer\n");
|
||||
return -1;
|
||||
}
|
||||
encrypted[0] = '\0';
|
||||
char* encrypted = (char*)malloc(NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
||||
if (!encrypted) {
|
||||
fprintf(stderr, "[didactyl] failed to allocate DM encrypt buffer\n");
|
||||
return -1;
|
||||
}
|
||||
encrypted[0] = '\0';
|
||||
|
||||
if (nostr_nip04_encrypt(g_cfg->keys.private_key, recipient_pubkey, message, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE) != NOSTR_SUCCESS) {
|
||||
free(encrypted);
|
||||
return -1;
|
||||
}
|
||||
if (nostr_nip04_encrypt(g_cfg->keys.private_key, recipient_pubkey, message, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE) != NOSTR_SUCCESS) {
|
||||
free(encrypted);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* tags = create_dm_tags_for_recipient(recipient_pubkey_hex);
|
||||
@@ -3263,17 +3093,7 @@ int nostr_handler_send_dm_with_role(const char* recipient_pubkey_hex,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Phase 3 item 12: route kind-4 DM event signing through the process
|
||||
* signer handle when available so remote signer modes can keep the nsec
|
||||
* out of the agent process. Falls back to the legacy raw-key path when
|
||||
* no signer is set (local mode / pre-Phase-2 binaries) so behavior is
|
||||
* unchanged. */
|
||||
cJSON* event = NULL;
|
||||
if (g_auth_signer) {
|
||||
event = nostr_create_and_sign_event_with_signer(4, encrypted, tags, g_auth_signer, time(NULL));
|
||||
} else {
|
||||
event = nostr_create_and_sign_event(4, encrypted, tags, g_cfg->keys.private_key, time(NULL));
|
||||
}
|
||||
cJSON* event = nostr_create_and_sign_event(4, encrypted, tags, g_cfg->keys.private_key, time(NULL));
|
||||
cJSON_Delete(tags);
|
||||
free(encrypted);
|
||||
if (!event) {
|
||||
@@ -3412,17 +3232,7 @@ static int publish_kind_event_to_relays(int kind,
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 3 item 12: route generic kind event signing through the process
|
||||
* signer handle when available (covers kind 30078 config publish, kind
|
||||
* 31124 private skill events, startup adoption events, etc.). Falls back
|
||||
* to the legacy raw-key path when no signer is set so local mode is
|
||||
* unchanged. */
|
||||
cJSON* event = NULL;
|
||||
if (g_auth_signer) {
|
||||
event = nostr_create_and_sign_event_with_signer(kind, content_to_publish, tags_copy, g_auth_signer, time(NULL));
|
||||
} else {
|
||||
event = nostr_create_and_sign_event(kind, content_to_publish, tags_copy, g_cfg->keys.private_key, time(NULL));
|
||||
}
|
||||
cJSON* event = nostr_create_and_sign_event(kind, content_to_publish, tags_copy, g_cfg->keys.private_key, time(NULL));
|
||||
if (tags_copy) {
|
||||
cJSON_Delete(tags_copy);
|
||||
}
|
||||
@@ -3869,180 +3679,6 @@ char* nostr_handler_get_self_skill_events_json(void) {
|
||||
return json ? json : strdup("[]");
|
||||
}
|
||||
|
||||
int nostr_handler_refresh_self_skill_cache_from_relays(int timeout_ms) {
|
||||
if (!g_cfg || !g_pool) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int effective_timeout_ms = timeout_ms > 0 ? timeout_ms : 5000;
|
||||
|
||||
cJSON* filter = cJSON_CreateObject();
|
||||
cJSON* kinds = cJSON_CreateArray();
|
||||
cJSON* authors = cJSON_CreateArray();
|
||||
if (!filter || !kinds || !authors) {
|
||||
cJSON_Delete(filter);
|
||||
cJSON_Delete(kinds);
|
||||
cJSON_Delete(authors);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31123));
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31124));
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
|
||||
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||
|
||||
cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
|
||||
cJSON_AddItemToObject(filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(filter, "limit", 500);
|
||||
|
||||
char* events_json = nostr_handler_query_json(filter, effective_timeout_ms);
|
||||
cJSON_Delete(filter);
|
||||
if (!events_json) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* events = cJSON_Parse(events_json);
|
||||
free(events_json);
|
||||
if (!events || !cJSON_IsArray(events)) {
|
||||
cJSON_Delete(events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* adopted_authors = cJSON_CreateObject();
|
||||
if (adopted_authors) {
|
||||
cJSON* latest_self_adoption = NULL;
|
||||
double latest_self_adoption_created = -1.0;
|
||||
int n0 = cJSON_GetArraySize(events);
|
||||
for (int i = 0; i < n0; i++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(events, i);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(ev, "kind");
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
|
||||
if (!kind || !cJSON_IsNumber(kind) || (int)kind->valuedouble != 10123 ||
|
||||
!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring ||
|
||||
strcmp(pubkey->valuestring, g_cfg->keys.public_key_hex) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
if (!latest_self_adoption || created > latest_self_adoption_created) {
|
||||
latest_self_adoption = ev;
|
||||
latest_self_adoption_created = created;
|
||||
}
|
||||
}
|
||||
|
||||
if (latest_self_adoption) {
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(latest_self_adoption, "tags");
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
int tn = cJSON_GetArraySize(tags);
|
||||
for (int ti = 0; ti < tn; ti++) {
|
||||
cJSON* tag = cJSON_GetArrayItem(tags, ti);
|
||||
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
cJSON* k = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* v = cJSON_GetArrayItem(tag, 1);
|
||||
if (!k || !v || !cJSON_IsString(k) || !k->valuestring ||
|
||||
!cJSON_IsString(v) || !v->valuestring || strcmp(k->valuestring, "a") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int adopted_kind = 0;
|
||||
char adopted_pubkey[65] = {0};
|
||||
char adopted_d_tag[65] = {0};
|
||||
if (parse_skill_address_local(v->valuestring, &adopted_kind, adopted_pubkey, adopted_d_tag) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (adopted_pubkey[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
if (!cJSON_GetObjectItemCaseSensitive(adopted_authors, adopted_pubkey)) {
|
||||
cJSON_AddBoolToObject(adopted_authors, adopted_pubkey, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int adopted_author_count = 0;
|
||||
cJSON* adopted_it = NULL;
|
||||
cJSON_ArrayForEach(adopted_it, adopted_authors) {
|
||||
adopted_author_count++;
|
||||
}
|
||||
|
||||
if (adopted_author_count > 0) {
|
||||
cJSON* adopted_filter = cJSON_CreateObject();
|
||||
cJSON* adopted_kinds = cJSON_CreateArray();
|
||||
cJSON* adopted_author_arr = cJSON_CreateArray();
|
||||
if (adopted_filter && adopted_kinds && adopted_author_arr) {
|
||||
cJSON_AddItemToArray(adopted_kinds, cJSON_CreateNumber(31123));
|
||||
cJSON_AddItemToArray(adopted_kinds, cJSON_CreateNumber(31124));
|
||||
cJSON_AddItemToObject(adopted_filter, "kinds", adopted_kinds);
|
||||
|
||||
cJSON_ArrayForEach(adopted_it, adopted_authors) {
|
||||
if (adopted_it->string && adopted_it->string[0] != '\0') {
|
||||
cJSON_AddItemToArray(adopted_author_arr, cJSON_CreateString(adopted_it->string));
|
||||
}
|
||||
}
|
||||
cJSON_AddItemToObject(adopted_filter, "authors", adopted_author_arr);
|
||||
cJSON_AddNumberToObject(adopted_filter, "limit", 1000);
|
||||
|
||||
char* adopted_json = nostr_handler_query_json(adopted_filter, effective_timeout_ms);
|
||||
if (adopted_json) {
|
||||
cJSON* adopted_events = cJSON_Parse(adopted_json);
|
||||
free(adopted_json);
|
||||
if (adopted_events && cJSON_IsArray(adopted_events)) {
|
||||
int an = cJSON_GetArraySize(adopted_events);
|
||||
for (int ai = 0; ai < an; ai++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(adopted_events, ai);
|
||||
cJSON* dup = ev ? cJSON_Duplicate(ev, 1) : NULL;
|
||||
if (dup) {
|
||||
cJSON_AddItemToArray(events, dup);
|
||||
}
|
||||
}
|
||||
}
|
||||
cJSON_Delete(adopted_events);
|
||||
}
|
||||
} else {
|
||||
cJSON_Delete(adopted_filter);
|
||||
cJSON_Delete(adopted_kinds);
|
||||
cJSON_Delete(adopted_author_arr);
|
||||
}
|
||||
cJSON_Delete(adopted_filter);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(adopted_authors);
|
||||
|
||||
int merged = 0;
|
||||
pthread_mutex_lock(&g_self_skill_mutex);
|
||||
int n = cJSON_GetArraySize(events);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(events, i);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(ev, "kind");
|
||||
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1;
|
||||
if (kind_val == 31124) {
|
||||
(void)normalize_private_skill_event_local(ev);
|
||||
}
|
||||
|
||||
self_skill_cache_upsert_event_locked(ev);
|
||||
merged++;
|
||||
}
|
||||
pthread_mutex_unlock(&g_self_skill_mutex);
|
||||
|
||||
cJSON_Delete(events);
|
||||
|
||||
agent_invalidate_adopted_skills_cache();
|
||||
DEBUG_INFO("[didactyl] skill_refresh relay reload merged %d self-skill/adoption events", merged);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nostr_handler_is_event_in_self_cache(const char* event_id_hex) {
|
||||
if (!event_id_hex || strlen(event_id_hex) != 64U) {
|
||||
return 0;
|
||||
@@ -4089,50 +3725,6 @@ int nostr_handler_connected_relay_count(void) {
|
||||
return connected;
|
||||
}
|
||||
|
||||
char* nostr_handler_relay_state_summary(void) {
|
||||
if (!g_cfg || !g_pool || g_cfg->relay_count <= 0 || !g_cfg->relays) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t total = 1;
|
||||
for (int i = 0; i < g_cfg->relay_count; i++) {
|
||||
const char* relay = (g_cfg->relays[i] && g_cfg->relays[i][0] != '\0') ? g_cfg->relays[i] : "<unknown>";
|
||||
const char* status = relay_status_str(nostr_relay_pool_get_relay_status(g_pool, relay));
|
||||
total += strlen(relay) + 1 + strlen(status) + 2;
|
||||
}
|
||||
|
||||
char* out = (char*)malloc(total);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out[0] = '\0';
|
||||
size_t used = 0;
|
||||
for (int i = 0; i < g_cfg->relay_count; i++) {
|
||||
const char* relay = (g_cfg->relays[i] && g_cfg->relays[i][0] != '\0') ? g_cfg->relays[i] : "<unknown>";
|
||||
const char* status = relay_status_str(nostr_relay_pool_get_relay_status(g_pool, relay));
|
||||
int n = snprintf(out + used,
|
||||
total - used,
|
||||
"%s%s=%s",
|
||||
(i == 0) ? "" : "; ",
|
||||
relay,
|
||||
status);
|
||||
if (n < 0) {
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t written = (size_t)n;
|
||||
if (written >= (total - used)) {
|
||||
used = total - 1;
|
||||
break;
|
||||
}
|
||||
used += written;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
char* nostr_handler_relay_status_json(void) {
|
||||
if (!g_pool) {
|
||||
return NULL;
|
||||
@@ -4480,29 +4072,14 @@ int nostr_handler_send_dm_nip17_with_role(const char* recipient_pubkey_hex,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Phase 3 item 14: prefer signer-aware NIP-17 gift-wrap creation when a
|
||||
* process signer is available; fall back to the legacy raw-key path in
|
||||
* local mode / pre-Phase-3. Gift-wrap ownership is unchanged: each
|
||||
* non-NULL entry in gift_wraps[] is freed by cJSON_Delete below. */
|
||||
cJSON* gift_wraps[NIP17_MAX_GIFT_WRAPS] = {0};
|
||||
int gift_count = 0;
|
||||
if (g_auth_signer) {
|
||||
gift_count = nostr_nip17_send_dm_with_signer(chat_event,
|
||||
recipients,
|
||||
1,
|
||||
g_auth_signer,
|
||||
gift_wraps,
|
||||
NIP17_MAX_GIFT_WRAPS,
|
||||
0);
|
||||
} else {
|
||||
gift_count = nostr_nip17_send_dm(chat_event,
|
||||
int gift_count = nostr_nip17_send_dm(chat_event,
|
||||
recipients,
|
||||
1,
|
||||
g_cfg->keys.private_key,
|
||||
gift_wraps,
|
||||
NIP17_MAX_GIFT_WRAPS,
|
||||
0);
|
||||
}
|
||||
cJSON_Delete(chat_event);
|
||||
if (gift_count <= 0) {
|
||||
DEBUG_WARN("[didactyl] NIP-17 send aborted: gift_wrap creation failed for %.16s...",
|
||||
@@ -5066,7 +4643,6 @@ void nostr_handler_cleanup(void) {
|
||||
|
||||
g_pool = NULL;
|
||||
g_cfg = NULL;
|
||||
g_auth_signer = NULL;
|
||||
g_dm_callback = NULL;
|
||||
g_dm_user_data = NULL;
|
||||
memset(g_seen_dm_ids, 0, sizeof(g_seen_dm_ids));
|
||||
|
||||
@@ -40,15 +40,6 @@ typedef struct {
|
||||
typedef void (*nostr_self_skill_eose_cb_t)(int event_count, void* user_data);
|
||||
|
||||
int nostr_handler_init(didactyl_config_t* config);
|
||||
|
||||
/* Optional: provide a process-lifetime signer handle for relay pool AUTH.
|
||||
* When set, nostr_handler_init routes NIP-42 AUTH through the signer via
|
||||
* nostr_relay_pool_set_auth_with_signer instead of the raw private-key path.
|
||||
* The caller retains ownership of the signer and must free it after
|
||||
* nostr_handler_cleanup. If not called, the legacy raw-key AUTH path is used
|
||||
* (backward compatible). */
|
||||
void nostr_handler_set_signer(nostr_signer_t* signer);
|
||||
|
||||
void nostr_handler_set_trigger_manager(struct trigger_manager* trigger_manager);
|
||||
int nostr_handler_subscribe_admin_context(void);
|
||||
int nostr_handler_subscribe_agent_context(void);
|
||||
@@ -86,11 +77,9 @@ int nostr_handler_validate_self_skill_cache(int* out_skill_count, int* out_adopt
|
||||
void nostr_handler_refresh_relay_statuses(void);
|
||||
int nostr_handler_sync_relays_from_config(void);
|
||||
char* nostr_handler_get_self_skill_events_json(void);
|
||||
int nostr_handler_refresh_self_skill_cache_from_relays(int timeout_ms);
|
||||
int nostr_handler_is_event_in_self_cache(const char* event_id_hex);
|
||||
const char* nostr_handler_get_startup_display_name(void);
|
||||
int nostr_handler_connected_relay_count(void);
|
||||
char* nostr_handler_relay_state_summary(void);
|
||||
char* nostr_handler_get_admin_kind0_context(void);
|
||||
char* nostr_handler_get_admin_kind3_context(void);
|
||||
char* nostr_handler_get_admin_kind10002_context(void);
|
||||
|
||||
+69
-670
File diff suppressed because it is too large
Load Diff
@@ -1,132 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "signer_health.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "../nostr_core_lib/nostr_core/nostr_common.h"
|
||||
|
||||
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static signer_health_state_t g_state = SIGNER_HEALTH_UNKNOWN;
|
||||
static char g_mode[32] = {0};
|
||||
static char g_last_reason[SIGNER_HEALTH_REASON_LEN] = {0};
|
||||
static time_t g_last_error_ts = 0;
|
||||
static time_t g_last_ok_ts = 0;
|
||||
|
||||
void signer_health_set_mode(const char* mode) {
|
||||
pthread_mutex_lock(&g_lock);
|
||||
if (mode && mode[0]) {
|
||||
snprintf(g_mode, sizeof(g_mode), "%s", mode);
|
||||
} else {
|
||||
g_mode[0] = '\0';
|
||||
}
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
}
|
||||
|
||||
void signer_health_reset(void) {
|
||||
pthread_mutex_lock(&g_lock);
|
||||
g_state = SIGNER_HEALTH_UNKNOWN;
|
||||
g_mode[0] = '\0';
|
||||
g_last_reason[0] = '\0';
|
||||
g_last_error_ts = 0;
|
||||
g_last_ok_ts = 0;
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
}
|
||||
|
||||
/* Classify a nostr error code into a short category label used both for
|
||||
* the health reason string and for log clarity. The categories map to
|
||||
* the failure modes called out in Phase 4 item 18:
|
||||
* transport/IO - network or socket failure talking to n_signer
|
||||
* auth/policy - n_signer rejected the request (auth envelope, policy)
|
||||
* crypto - the signer performed work but the crypto op failed
|
||||
* (e.g. decrypt of malformed ciphertext). This is NOT
|
||||
* necessarily a signer outage, but we still record it
|
||||
* so the operator can see recent signer activity.
|
||||
* unknown - anything else.
|
||||
*
|
||||
* Note: nostr_core_lib does not currently expose dedicated nsigner
|
||||
* transport/auth error codes; remote signer failures surface as
|
||||
* NOSTR_ERROR_IO_FAILED / NOSTR_ERROR_NETWORK_FAILED (transport) or
|
||||
* NOSTR_ERROR_CRYPTO_FAILED (the nsigner client maps server-side
|
||||
* rejections to crypto-failed when it cannot be more specific). We
|
||||
* treat IO/network as the strong "signer unreachable" signal. */
|
||||
static const char* classify_rc(int rc) {
|
||||
if (rc == NOSTR_ERROR_IO_FAILED || rc == NOSTR_ERROR_NETWORK_FAILED) {
|
||||
return "transport/IO";
|
||||
}
|
||||
if (rc == NOSTR_ERROR_CRYPTO_FAILED) {
|
||||
return "crypto/auth";
|
||||
}
|
||||
if (rc == NOSTR_ERROR_MEMORY_FAILED) {
|
||||
return "resource";
|
||||
}
|
||||
if (rc == NOSTR_ERROR_INVALID_INPUT) {
|
||||
return "invalid-input";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void signer_health_record_ok(void) {
|
||||
int transitioned_from_unhealthy = 0;
|
||||
pthread_mutex_lock(&g_lock);
|
||||
time_t now = time(NULL);
|
||||
if (g_state == SIGNER_HEALTH_UNHEALTHY) {
|
||||
transitioned_from_unhealthy = 1;
|
||||
}
|
||||
g_state = SIGNER_HEALTH_HEALTHY;
|
||||
g_last_ok_ts = now;
|
||||
/* Keep last_reason/last_error_ts so the operator can see the most
|
||||
* recent prior failure even after recovery. */
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
|
||||
if (transitioned_from_unhealthy) {
|
||||
fprintf(stderr,
|
||||
"[didactyl] signer health transition: unhealthy -> healthy (mode=%s)\n",
|
||||
g_mode[0] ? g_mode : "local");
|
||||
}
|
||||
}
|
||||
|
||||
void signer_health_record_failure(int nostr_rc, const char* detail) {
|
||||
int transitioned_from_healthy = 0;
|
||||
char reason[SIGNER_HEALTH_REASON_LEN];
|
||||
const char* cls = classify_rc(nostr_rc);
|
||||
if (detail && detail[0]) {
|
||||
snprintf(reason, sizeof(reason), "%s: %s (rc=%d)", cls, detail, nostr_rc);
|
||||
} else {
|
||||
snprintf(reason, sizeof(reason), "%s (rc=%d)", cls, nostr_rc);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_lock);
|
||||
time_t now = time(NULL);
|
||||
if (g_state == SIGNER_HEALTH_HEALTHY) {
|
||||
transitioned_from_healthy = 1;
|
||||
}
|
||||
g_state = SIGNER_HEALTH_UNHEALTHY;
|
||||
snprintf(g_last_reason, sizeof(g_last_reason), "%s", reason);
|
||||
g_last_error_ts = now;
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
|
||||
if (transitioned_from_healthy) {
|
||||
fprintf(stderr,
|
||||
"[didactyl] signer health transition: healthy -> unhealthy "
|
||||
"(mode=%s, reason=%s)\n",
|
||||
g_mode[0] ? g_mode : "local",
|
||||
reason);
|
||||
}
|
||||
}
|
||||
|
||||
int signer_health_snapshot(signer_health_snapshot_t* out) {
|
||||
if (!out) {
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&g_lock);
|
||||
out->state = g_state;
|
||||
snprintf(out->mode, sizeof(out->mode), "%s", g_mode);
|
||||
snprintf(out->last_reason, sizeof(out->last_reason), "%s", g_last_reason);
|
||||
out->last_error_ts = g_last_error_ts;
|
||||
out->last_ok_ts = g_last_ok_ts;
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#ifndef DIDACTYL_SIGNER_HEALTH_H
|
||||
#define DIDACTYL_SIGNER_HEALTH_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
/* Signer runtime health tracking (Phase 4 item 18/19).
|
||||
*
|
||||
* Lightweight process-global state recording whether the active signer
|
||||
* provider (local or remote n_signer) is currently considered healthy,
|
||||
* along with the last error reason and timestamp. This is surfaced via
|
||||
* the HTTP /api/status endpoint so an operator can detect a signing
|
||||
* outage out-of-band (without requiring a signed admin DM to come back).
|
||||
*
|
||||
* The state is intentionally minimal and lock-protected; it is updated
|
||||
* from central signer call paths (nostr_handler self-encrypt/decrypt,
|
||||
* DM encrypt/decrypt, main.c startup connectivity check). It is NOT a
|
||||
* full health-check daemon: it reflects the outcome of the last signer
|
||||
* operation that was attempted.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
SIGNER_HEALTH_UNKNOWN = 0, /* no signer operation attempted yet */
|
||||
SIGNER_HEALTH_HEALTHY = 1,
|
||||
SIGNER_HEALTH_UNHEALTHY = 2
|
||||
} signer_health_state_t;
|
||||
|
||||
#define SIGNER_HEALTH_REASON_LEN 256
|
||||
|
||||
typedef struct {
|
||||
signer_health_state_t state;
|
||||
char mode[32]; /* snapshot of cfg.signer.mode */
|
||||
char last_reason[SIGNER_HEALTH_REASON_LEN]; /* human-readable last error */
|
||||
time_t last_error_ts; /* unix ts of last unhealthy transition */
|
||||
time_t last_ok_ts; /* unix ts of last healthy observation */
|
||||
} signer_health_snapshot_t;
|
||||
|
||||
/* Record the configured signer mode at startup. Call once after the
|
||||
* signer is constructed. Safe to call multiple times. */
|
||||
void signer_health_set_mode(const char* mode);
|
||||
|
||||
/* Mark the signer healthy and record a successful operation timestamp.
|
||||
* Emits a one-line warning if this is a transition from unhealthy -> healthy. */
|
||||
void signer_health_record_ok(void);
|
||||
|
||||
/* Mark the signer unhealthy with a reason string and the nostr error code
|
||||
* (from nostr_common.h). The reason is formatted as "<classification>: <detail>".
|
||||
* Emits a one-line warning if this is a transition from healthy -> unhealthy.
|
||||
* Pass rc=0 only if you do not have a nostr error code (the detail string
|
||||
* should then fully describe the failure). */
|
||||
void signer_health_record_failure(int nostr_rc, const char* detail);
|
||||
|
||||
/* Thread-safe snapshot copy for the HTTP status endpoint. Returns 0 on
|
||||
* success, -1 if out is NULL. */
|
||||
int signer_health_snapshot(signer_health_snapshot_t* out);
|
||||
|
||||
/* Reset state (used by tests / reinit). */
|
||||
void signer_health_reset(void);
|
||||
|
||||
#endif /* DIDACTYL_SIGNER_HEALTH_H */
|
||||
@@ -117,26 +117,12 @@ char* execute_blossom_upload(tools_context_t* ctx, const char* args_json) {
|
||||
}
|
||||
|
||||
blossom_blob_descriptor_t d;
|
||||
/* Phase 3 item 16: route the Blossom upload auth/signature through the
|
||||
* process signer when wired into the tools context so remote signer modes
|
||||
* keep the nsec out of the agent. Falls back to the legacy raw-key path
|
||||
* when the signer is NULL (local mode / pre-Phase-3). */
|
||||
int rc;
|
||||
if (ctx->signer) {
|
||||
rc = blossom_upload_file_with_signer(server->valuestring,
|
||||
full_path,
|
||||
(content_type && cJSON_IsString(content_type) && content_type->valuestring) ? content_type->valuestring : NULL,
|
||||
ctx->signer,
|
||||
30,
|
||||
&d);
|
||||
} else {
|
||||
rc = blossom_upload_file(server->valuestring,
|
||||
int rc = blossom_upload_file(server->valuestring,
|
||||
full_path,
|
||||
(content_type && cJSON_IsString(content_type) && content_type->valuestring) ? content_type->valuestring : NULL,
|
||||
ctx->cfg->keys.private_key,
|
||||
30,
|
||||
&d);
|
||||
}
|
||||
cJSON_Delete(args);
|
||||
if (rc != NOSTR_SUCCESS) return json_error_local("blossom_upload failed");
|
||||
|
||||
@@ -249,16 +235,7 @@ char* execute_blossom_delete(tools_context_t* ctx, const char* args_json) {
|
||||
return json_error_local("blossom_delete requires an https:// server URL");
|
||||
}
|
||||
|
||||
/* Phase 3 item 16: route the Blossom delete auth/signature through the
|
||||
* process signer when wired into the tools context so remote signer modes
|
||||
* keep the nsec out of the agent. Falls back to the legacy raw-key path
|
||||
* when the signer is NULL (local mode / pre-Phase-3). */
|
||||
int rc;
|
||||
if (ctx->signer) {
|
||||
rc = blossom_delete_with_signer(server->valuestring, sha->valuestring, ctx->signer, 15);
|
||||
} else {
|
||||
rc = blossom_delete(server->valuestring, sha->valuestring, ctx->cfg->keys.private_key, 15);
|
||||
}
|
||||
int rc = blossom_delete(server->valuestring, sha->valuestring, ctx->cfg->keys.private_key, 15);
|
||||
cJSON_Delete(args);
|
||||
if (rc != NOSTR_SUCCESS) return json_error_local("blossom_delete failed");
|
||||
|
||||
|
||||
+3
-49
@@ -37,20 +37,6 @@ static int nip44_encrypt_self_local(tools_context_t* ctx, const char* plaintext,
|
||||
if (!ctx || !ctx->cfg || !out_ciphertext) return -1;
|
||||
|
||||
const char* plain = plaintext ? plaintext : "";
|
||||
|
||||
/* Phase 3 item 13: route self-encrypt through the process signer when
|
||||
* wired into the tools context. Falls back to the legacy raw-key path. */
|
||||
if (ctx->signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_encrypt(ctx->signer,
|
||||
ctx->cfg->keys.public_key_hex,
|
||||
plain,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) return -1;
|
||||
*out_ciphertext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = (strlen(plain) * 4U) + 1024U;
|
||||
char* ciphertext = (char*)malloc(out_cap);
|
||||
if (!ciphertext) return -1;
|
||||
@@ -72,19 +58,6 @@ static int nip44_encrypt_self_local(tools_context_t* ctx, const char* plaintext,
|
||||
static int nip44_decrypt_self_local(tools_context_t* ctx, const char* ciphertext, char** out_plaintext) {
|
||||
if (!ctx || !ctx->cfg || !ciphertext || !out_plaintext) return -1;
|
||||
|
||||
/* Phase 3 item 13: route self-decrypt through the process signer when
|
||||
* wired into the tools context. Falls back to the legacy raw-key path. */
|
||||
if (ctx->signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_decrypt(ctx->signer,
|
||||
ctx->cfg->keys.public_key_hex,
|
||||
ciphertext,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) return -1;
|
||||
*out_plaintext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = strlen(ciphertext) + 1024U;
|
||||
char* plaintext = (char*)malloc(out_cap);
|
||||
if (!plaintext) return -1;
|
||||
@@ -127,7 +100,7 @@ static int query_config_ciphertext_local(tools_context_t* ctx, const char* d_tag
|
||||
cJSON_AddItemToArray(d_vals, cJSON_CreateString(d_tag));
|
||||
cJSON_AddItemToObject(filter, "#d", d_vals);
|
||||
|
||||
cJSON_AddNumberToObject(filter, "limit", 20);
|
||||
cJSON_AddNumberToObject(filter, "limit", 1);
|
||||
|
||||
char* events_json = nostr_handler_query_json(filter, 4000);
|
||||
cJSON_Delete(filter);
|
||||
@@ -143,27 +116,8 @@ static int query_config_ciphertext_local(tools_context_t* ctx, const char* d_tag
|
||||
return (*out_ciphertext != NULL) ? 0 : -1;
|
||||
}
|
||||
|
||||
cJSON* newest_ev = NULL;
|
||||
long newest_created_at = -1;
|
||||
int n = cJSON_GetArraySize(arr);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(arr, i);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
cJSON* content = cJSON_GetObjectItemCaseSensitive(ev, "content");
|
||||
if (!content || !cJSON_IsString(content) || !content->valuestring || content->valuestring[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
long ts = (created_at && cJSON_IsNumber(created_at)) ? (long)created_at->valuedouble : 0;
|
||||
if (!newest_ev || ts > newest_created_at) {
|
||||
newest_ev = ev;
|
||||
newest_created_at = ts;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* content = newest_ev ? cJSON_GetObjectItemCaseSensitive(newest_ev, "content") : NULL;
|
||||
cJSON* ev = cJSON_GetArrayItem(arr, 0);
|
||||
cJSON* content = ev ? cJSON_GetObjectItemCaseSensitive(ev, "content") : NULL;
|
||||
const char* cipher = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : "";
|
||||
|
||||
*out_ciphertext = strdup(cipher);
|
||||
|
||||
@@ -58,21 +58,6 @@ static int nip44_encrypt_self_local(tools_context_t* ctx, const char* plaintext,
|
||||
if (!ctx || !ctx->cfg || !out_ciphertext) return -1;
|
||||
|
||||
const char* plain = plaintext ? plaintext : "";
|
||||
|
||||
/* Phase 3 item 13: route self-encrypt through the process signer when
|
||||
* wired into the tools context so remote signer modes keep the nsec out
|
||||
* of the agent. Falls back to the legacy raw-key path otherwise. */
|
||||
if (ctx->signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_encrypt(ctx->signer,
|
||||
ctx->cfg->keys.public_key_hex,
|
||||
plain,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) return -1;
|
||||
*out_ciphertext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = (strlen(plain) * 4U) + 1024U;
|
||||
char* ciphertext = (char*)malloc(out_cap);
|
||||
if (!ciphertext) return -1;
|
||||
@@ -94,19 +79,6 @@ static int nip44_encrypt_self_local(tools_context_t* ctx, const char* plaintext,
|
||||
static int nip44_decrypt_self_local(tools_context_t* ctx, const char* ciphertext, char** out_plaintext) {
|
||||
if (!ctx || !ctx->cfg || !ciphertext || !out_plaintext) return -1;
|
||||
|
||||
/* Phase 3 item 13: route self-decrypt through the process signer when
|
||||
* wired into the tools context. Falls back to the legacy raw-key path. */
|
||||
if (ctx->signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_decrypt(ctx->signer,
|
||||
ctx->cfg->keys.public_key_hex,
|
||||
ciphertext,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) return -1;
|
||||
*out_plaintext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = strlen(ciphertext) + 1024U;
|
||||
char* plaintext = (char*)malloc(out_cap);
|
||||
if (!plaintext) return -1;
|
||||
|
||||
@@ -136,27 +136,6 @@ static int persist_global_llm_user_settings_nostr(tools_context_t* ctx,
|
||||
cJSON_DeleteItemFromObjectCaseSensitive(global_llm, "temperature");
|
||||
cJSON_AddNumberToObject(global_llm, "temperature", cfg->temperature);
|
||||
|
||||
cJSON* didactyl = cJSON_GetObjectItemCaseSensitive(user_settings, "didactyl");
|
||||
if (didactyl && !cJSON_IsObject(didactyl)) {
|
||||
cJSON_DeleteItemFromObjectCaseSensitive(user_settings, "didactyl");
|
||||
didactyl = NULL;
|
||||
}
|
||||
if (!didactyl) {
|
||||
cJSON* new_didactyl = cJSON_CreateObject();
|
||||
if (!new_didactyl) {
|
||||
cJSON_Delete(user_settings);
|
||||
if (out_error) *out_error = strdup("allocation failure while preparing didactyl payload");
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddItemToObject(user_settings, "didactyl", new_didactyl);
|
||||
didactyl = new_didactyl;
|
||||
}
|
||||
|
||||
cJSON_DeleteItemFromObjectCaseSensitive(didactyl, "max_turns");
|
||||
cJSON_AddNumberToObject(didactyl,
|
||||
"max_turns",
|
||||
(ctx->cfg->tools.max_turns > 0) ? ctx->cfg->tools.max_turns : 40);
|
||||
|
||||
cJSON* store_args = cJSON_CreateObject();
|
||||
if (!store_args) {
|
||||
cJSON_Delete(user_settings);
|
||||
|
||||
+40
-69
@@ -83,43 +83,28 @@ char* execute_nostr_encrypt(tools_context_t* ctx, const char* args_json) {
|
||||
return json_error_local("nostr_encrypt requires recipient_pubkey hex and plaintext");
|
||||
}
|
||||
|
||||
/* Phase 3 item 13: route NIP-44 encrypt through the process signer when
|
||||
* wired into the tools context. The signer verb takes a hex peer pubkey
|
||||
* and returns a heap string. Falls back to the legacy raw-key path. */
|
||||
char* ciphertext = NULL;
|
||||
if (ctx->signer) {
|
||||
int rc = nostr_signer_nip44_encrypt(ctx->signer,
|
||||
recipient->valuestring,
|
||||
plaintext->valuestring,
|
||||
&ciphertext);
|
||||
if (rc != NOSTR_SUCCESS || !ciphertext) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_encrypt failed");
|
||||
}
|
||||
} else {
|
||||
unsigned char recipient_pubkey[32];
|
||||
if (nostr_hex_to_bytes(recipient->valuestring, recipient_pubkey, sizeof(recipient_pubkey)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_encrypt invalid recipient_pubkey");
|
||||
}
|
||||
unsigned char recipient_pubkey[32];
|
||||
if (nostr_hex_to_bytes(recipient->valuestring, recipient_pubkey, sizeof(recipient_pubkey)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_encrypt invalid recipient_pubkey");
|
||||
}
|
||||
|
||||
size_t out_cap = (strlen(plaintext->valuestring) * 4U) + 1024U;
|
||||
ciphertext = (char*)malloc(out_cap);
|
||||
if (!ciphertext) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("allocation failure");
|
||||
}
|
||||
size_t out_cap = (strlen(plaintext->valuestring) * 4U) + 1024U;
|
||||
char* ciphertext = (char*)malloc(out_cap);
|
||||
if (!ciphertext) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("allocation failure");
|
||||
}
|
||||
|
||||
int rc = nostr_nip44_encrypt(ctx->cfg->keys.private_key,
|
||||
recipient_pubkey,
|
||||
plaintext->valuestring,
|
||||
ciphertext,
|
||||
out_cap);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
free(ciphertext);
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_encrypt failed");
|
||||
}
|
||||
int rc = nostr_nip44_encrypt(ctx->cfg->keys.private_key,
|
||||
recipient_pubkey,
|
||||
plaintext->valuestring,
|
||||
ciphertext,
|
||||
out_cap);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
free(ciphertext);
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_encrypt failed");
|
||||
}
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
@@ -153,42 +138,28 @@ char* execute_nostr_decrypt(tools_context_t* ctx, const char* args_json) {
|
||||
return json_error_local("nostr_decrypt requires sender_pubkey hex and ciphertext");
|
||||
}
|
||||
|
||||
/* Phase 3 item 13: route NIP-44 decrypt through the process signer when
|
||||
* wired into the tools context. Falls back to the legacy raw-key path. */
|
||||
char* plaintext = NULL;
|
||||
if (ctx->signer) {
|
||||
int rc = nostr_signer_nip44_decrypt(ctx->signer,
|
||||
sender->valuestring,
|
||||
ciphertext->valuestring,
|
||||
&plaintext);
|
||||
if (rc != NOSTR_SUCCESS || !plaintext) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_decrypt failed");
|
||||
}
|
||||
} else {
|
||||
unsigned char sender_pubkey[32];
|
||||
if (nostr_hex_to_bytes(sender->valuestring, sender_pubkey, sizeof(sender_pubkey)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_decrypt invalid sender_pubkey");
|
||||
}
|
||||
unsigned char sender_pubkey[32];
|
||||
if (nostr_hex_to_bytes(sender->valuestring, sender_pubkey, sizeof(sender_pubkey)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_decrypt invalid sender_pubkey");
|
||||
}
|
||||
|
||||
size_t out_cap = strlen(ciphertext->valuestring) + 1024U;
|
||||
plaintext = (char*)malloc(out_cap);
|
||||
if (!plaintext) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("allocation failure");
|
||||
}
|
||||
size_t out_cap = strlen(ciphertext->valuestring) + 1024U;
|
||||
char* plaintext = (char*)malloc(out_cap);
|
||||
if (!plaintext) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("allocation failure");
|
||||
}
|
||||
|
||||
int rc = nostr_nip44_decrypt(ctx->cfg->keys.private_key,
|
||||
sender_pubkey,
|
||||
ciphertext->valuestring,
|
||||
plaintext,
|
||||
out_cap);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
free(plaintext);
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_decrypt failed");
|
||||
}
|
||||
int rc = nostr_nip44_decrypt(ctx->cfg->keys.private_key,
|
||||
sender_pubkey,
|
||||
ciphertext->valuestring,
|
||||
plaintext,
|
||||
out_cap);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
free(plaintext);
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("nostr_decrypt failed");
|
||||
}
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
|
||||
+45
-848
@@ -3,8 +3,6 @@
|
||||
#include "tools_internal.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -14,7 +12,6 @@
|
||||
#include "../nostr_handler.h"
|
||||
#include "../trigger_manager.h"
|
||||
#include "../agent.h"
|
||||
#include "../llm.h"
|
||||
|
||||
static char* json_error_local(const char* msg) {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
@@ -133,52 +130,6 @@ static cJSON* parse_args_local(const char* args_json) {
|
||||
return args;
|
||||
}
|
||||
|
||||
static int append_textf_skill_list_local(char** out, size_t* cap, size_t* used, const char* fmt, ...) {
|
||||
if (!out || !cap || !used || !fmt) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
va_list ap2;
|
||||
va_copy(ap2, ap);
|
||||
int needed = vsnprintf(NULL, 0, fmt, ap);
|
||||
va_end(ap);
|
||||
if (needed < 0) {
|
||||
va_end(ap2);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t need = (size_t)needed;
|
||||
if (*used + need + 1U > *cap) {
|
||||
size_t new_cap = (*cap > 0U) ? *cap : 256U;
|
||||
while (*used + need + 1U > new_cap) {
|
||||
if (new_cap > (SIZE_MAX / 2U)) {
|
||||
va_end(ap2);
|
||||
return -1;
|
||||
}
|
||||
new_cap *= 2U;
|
||||
}
|
||||
|
||||
char* grown = (char*)realloc(*out, new_cap);
|
||||
if (!grown) {
|
||||
va_end(ap2);
|
||||
return -1;
|
||||
}
|
||||
*out = grown;
|
||||
*cap = new_cap;
|
||||
}
|
||||
|
||||
int wrote = vsnprintf((*out) + (*used), *cap - *used, fmt, ap2);
|
||||
va_end(ap2);
|
||||
if (wrote < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*used += (size_t)wrote;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_hex_string_len_local(const char* s, size_t expected_len) {
|
||||
if (!s || strlen(s) != expected_len) return 0;
|
||||
for (size_t i = 0; i < expected_len; i++) {
|
||||
@@ -303,40 +254,6 @@ static int validate_skill_d_tag_local(const char* d_tag) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void apply_llm_spec_local_to_config(const char* llm_spec, llm_config_t* cfg) {
|
||||
if (!llm_spec || !cfg) return;
|
||||
|
||||
const char* spec = llm_spec;
|
||||
while (*spec && isspace((unsigned char)*spec)) spec++;
|
||||
|
||||
const char* comma = strchr(spec, ',');
|
||||
size_t len = comma ? (size_t)(comma - spec) : strlen(spec);
|
||||
while (len > 0 && isspace((unsigned char)spec[len - 1])) len--;
|
||||
|
||||
const char* slash = memchr(spec, '/', len);
|
||||
if (slash) {
|
||||
size_t provider_len = (size_t)(slash - spec);
|
||||
const char* model = slash + 1;
|
||||
size_t model_len = len - (size_t)(model - spec);
|
||||
|
||||
while (provider_len > 0 && isspace((unsigned char)spec[provider_len - 1])) provider_len--;
|
||||
while (model_len > 0 && isspace((unsigned char)*model)) {
|
||||
model++;
|
||||
model_len--;
|
||||
}
|
||||
while (model_len > 0 && isspace((unsigned char)model[model_len - 1])) model_len--;
|
||||
|
||||
if (provider_len > 0 && provider_len < sizeof(cfg->provider)) {
|
||||
snprintf(cfg->provider, sizeof(cfg->provider), "%.*s", (int)provider_len, spec);
|
||||
}
|
||||
if (model_len > 0 && model_len < sizeof(cfg->model)) {
|
||||
snprintf(cfg->model, sizeof(cfg->model), "%.*s", (int)model_len, model);
|
||||
}
|
||||
} else if (len > 0 && len < sizeof(cfg->model)) {
|
||||
snprintf(cfg->model, sizeof(cfg->model), "%.*s", (int)len, spec);
|
||||
}
|
||||
}
|
||||
|
||||
static int ci_contains_local(const char* haystack, const char* needle) {
|
||||
if (!haystack || !needle) return 0;
|
||||
if (needle[0] == '\0') return 1;
|
||||
@@ -381,31 +298,9 @@ static int fetch_adoption_list_tags_local(tools_context_t* ctx, cJSON** out_tags
|
||||
free(events_json);
|
||||
|
||||
if (events && cJSON_IsArray(events) && cJSON_GetArraySize(events) > 0) {
|
||||
cJSON* best = NULL;
|
||||
double best_created = -1.0;
|
||||
int n = cJSON_GetArraySize(events);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(events, i);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* ev_pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
|
||||
if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
|
||||
strcmp(ev_pubkey->valuestring, ctx->cfg->keys.public_key_hex) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
if (!best || created > best_created) {
|
||||
best = ev;
|
||||
best_created = created;
|
||||
}
|
||||
}
|
||||
|
||||
if (best && cJSON_IsObject(best)) {
|
||||
cJSON* ev_content = cJSON_GetObjectItemCaseSensitive(best, "content");
|
||||
cJSON* ev0 = cJSON_GetArrayItem(events, 0);
|
||||
if (ev0 && cJSON_IsObject(ev0)) {
|
||||
cJSON* ev_content = cJSON_GetObjectItemCaseSensitive(ev0, "content");
|
||||
if (ev_content && cJSON_IsString(ev_content) && ev_content->valuestring) {
|
||||
free(content);
|
||||
content = strdup(ev_content->valuestring);
|
||||
@@ -416,7 +311,7 @@ static int fetch_adoption_list_tags_local(tools_context_t* ctx, cJSON** out_tags
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* ev_tags = cJSON_GetObjectItemCaseSensitive(best, "tags");
|
||||
cJSON* ev_tags = cJSON_GetObjectItemCaseSensitive(ev0, "tags");
|
||||
if (ev_tags && cJSON_IsArray(ev_tags)) {
|
||||
cJSON* dup = cJSON_Duplicate(ev_tags, 1);
|
||||
if (!dup) {
|
||||
@@ -550,338 +445,6 @@ static cJSON* extract_skill_summary_local(cJSON* event) {
|
||||
return summary;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char* d_tag;
|
||||
char* author_pubkey;
|
||||
char* content;
|
||||
cJSON* tags;
|
||||
int kind;
|
||||
int is_own;
|
||||
} skill_fetch_result_local_t;
|
||||
|
||||
static int g_skill_run_depth_local = 0;
|
||||
static pthread_mutex_t g_skill_run_depth_mutex_local = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void free_skill_fetch_result_local(skill_fetch_result_local_t* result) {
|
||||
if (!result) return;
|
||||
free(result->d_tag);
|
||||
free(result->author_pubkey);
|
||||
free(result->content);
|
||||
cJSON_Delete(result->tags);
|
||||
memset(result, 0, sizeof(*result));
|
||||
}
|
||||
|
||||
static int set_error_local(char** out_error, const char* msg) {
|
||||
if (!out_error) return -1;
|
||||
*out_error = strdup(msg ? msg : "error");
|
||||
return (*out_error) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int append_simple_message_local(cJSON* messages, const char* role, const char* content) {
|
||||
if (!messages || !role) return -1;
|
||||
|
||||
cJSON* msg = cJSON_CreateObject();
|
||||
if (!msg) return -1;
|
||||
|
||||
cJSON_AddStringToObject(msg, "role", role);
|
||||
cJSON_AddStringToObject(msg, "content", content ? content : "");
|
||||
cJSON_AddItemToArray(messages, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_assistant_tool_calls_message_local(cJSON* messages, const llm_response_t* resp) {
|
||||
if (!messages || !resp || resp->tool_call_count <= 0) return -1;
|
||||
|
||||
cJSON* assistant = cJSON_CreateObject();
|
||||
cJSON* tool_calls = cJSON_CreateArray();
|
||||
if (!assistant || !tool_calls) {
|
||||
cJSON_Delete(assistant);
|
||||
cJSON_Delete(tool_calls);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(assistant, "role", "assistant");
|
||||
if (resp->content) cJSON_AddStringToObject(assistant, "content", resp->content);
|
||||
else cJSON_AddNullToObject(assistant, "content");
|
||||
|
||||
for (int i = 0; i < resp->tool_call_count; i++) {
|
||||
const llm_tool_call_t* tc = &resp->tool_calls[i];
|
||||
cJSON* tc_obj = cJSON_CreateObject();
|
||||
cJSON* fn_obj = cJSON_CreateObject();
|
||||
if (!tc_obj || !fn_obj) {
|
||||
cJSON_Delete(tc_obj);
|
||||
cJSON_Delete(fn_obj);
|
||||
cJSON_Delete(assistant);
|
||||
cJSON_Delete(tool_calls);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(tc_obj, "id", tc->id ? tc->id : "");
|
||||
cJSON_AddStringToObject(tc_obj, "type", "function");
|
||||
cJSON_AddStringToObject(fn_obj, "name", tc->name ? tc->name : "");
|
||||
cJSON_AddStringToObject(fn_obj, "arguments", tc->arguments_json ? tc->arguments_json : "{}");
|
||||
cJSON_AddItemToObject(tc_obj, "function", fn_obj);
|
||||
cJSON_AddItemToArray(tool_calls, tc_obj);
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(assistant, "tool_calls", tool_calls);
|
||||
cJSON_AddItemToArray(messages, assistant);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_tool_result_message_local(cJSON* messages, const char* tool_call_id, const char* tool_result_json) {
|
||||
if (!messages || !tool_call_id) return -1;
|
||||
|
||||
cJSON* msg = cJSON_CreateObject();
|
||||
if (!msg) return -1;
|
||||
|
||||
cJSON_AddStringToObject(msg, "role", "tool");
|
||||
cJSON_AddStringToObject(msg, "tool_call_id", tool_call_id);
|
||||
cJSON_AddStringToObject(msg,
|
||||
"content",
|
||||
tool_result_json ? tool_result_json : "{\"success\":false,\"error\":\"empty tool result\"}");
|
||||
cJSON_AddItemToArray(messages, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int maybe_append_loaded_skill_instructions_local(cJSON* messages,
|
||||
const char* tool_name,
|
||||
const char* tool_result_json) {
|
||||
if (!messages || !tool_name || strcmp(tool_name, "skill_load") != 0 || !tool_result_json) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_Parse(tool_result_json);
|
||||
if (!root || !cJSON_IsObject(root)) {
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* ok = cJSON_GetObjectItemCaseSensitive(root, "success");
|
||||
cJSON* type = cJSON_GetObjectItemCaseSensitive(root, "type");
|
||||
cJSON* instructions = cJSON_GetObjectItemCaseSensitive(root, "instructions");
|
||||
|
||||
int should_apply = (ok && cJSON_IsBool(ok) && cJSON_IsTrue(ok) &&
|
||||
type && cJSON_IsString(type) && type->valuestring &&
|
||||
strcmp(type->valuestring, "skill_instructions") == 0 &&
|
||||
instructions && cJSON_IsString(instructions) && instructions->valuestring &&
|
||||
instructions->valuestring[0] != '\0');
|
||||
|
||||
int rc = 0;
|
||||
if (should_apply) {
|
||||
rc = append_simple_message_local(messages, "system", instructions->valuestring);
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int is_tool_blocked_in_skill_run_local(const char* tool_name) {
|
||||
if (!tool_name || tool_name[0] == '\0') return 1;
|
||||
|
||||
static const char* blocked_tools[] = {
|
||||
"model_get", "model_set", "model_list",
|
||||
NULL
|
||||
};
|
||||
|
||||
for (int i = 0; blocked_tools[i] != NULL; i++) {
|
||||
if (strcmp(tool_name, blocked_tools[i]) == 0) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_tool_sandbox_safe_local(const char* tool_name) {
|
||||
if (!tool_name || tool_name[0] == '\0') return 0;
|
||||
|
||||
static const char* safe_tools[] = {
|
||||
"nostr_query", "nostr_nip05_lookup", "nostr_profile_get",
|
||||
"nostr_encode", "nostr_decode", "nostr_relay_status",
|
||||
"nostr_relay_info", "nostr_post", "nostr_react",
|
||||
"nostr_list_manage", "nostr_dm_send", "nostr_dm_send_nip17",
|
||||
"skill_list", "skill_search", "skill_get", "skill_view",
|
||||
"skill_load", "skill_run",
|
||||
"memory_recall", "my_memory", "memory_short_term_read",
|
||||
"agent_version", "nostr_pubkey", "nostr_npub",
|
||||
"tool_list", "trigger_list", "message_current",
|
||||
"agent_identity", "admin_identity",
|
||||
NULL
|
||||
};
|
||||
|
||||
for (int i = 0; safe_tools[i] != NULL; i++) {
|
||||
if (strcmp(tool_name, safe_tools[i]) == 0) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char* build_sandboxed_tools_json_local(const tools_context_t* ctx) {
|
||||
char* full_tools_json = tools_build_openai_schema_json(ctx);
|
||||
if (!full_tools_json) return NULL;
|
||||
|
||||
cJSON* arr = cJSON_Parse(full_tools_json);
|
||||
free(full_tools_json);
|
||||
if (!arr || !cJSON_IsArray(arr)) {
|
||||
cJSON_Delete(arr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = cJSON_GetArraySize(arr) - 1; i >= 0; i--) {
|
||||
cJSON* item = cJSON_GetArrayItem(arr, i);
|
||||
cJSON* fn = item ? cJSON_GetObjectItemCaseSensitive(item, "function") : NULL;
|
||||
cJSON* name = fn ? cJSON_GetObjectItemCaseSensitive(fn, "name") : NULL;
|
||||
if (!name || !cJSON_IsString(name) || !name->valuestring || !is_tool_sandbox_safe_local(name->valuestring)) {
|
||||
cJSON_DeleteItemFromArray(arr, i);
|
||||
}
|
||||
}
|
||||
|
||||
char* out = cJSON_PrintUnformatted(arr);
|
||||
cJSON_Delete(arr);
|
||||
return out;
|
||||
}
|
||||
|
||||
static int fetch_skill_from_events_local(tools_context_t* ctx,
|
||||
cJSON* events,
|
||||
const char* d_tag,
|
||||
const char* pubkey_filter,
|
||||
skill_fetch_result_local_t* out,
|
||||
char** out_error) {
|
||||
if (!ctx || !ctx->cfg || !events || !cJSON_IsArray(events) || !d_tag || !out) {
|
||||
if (out_error) (void)set_error_local(out_error, "skill lookup failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* best = NULL;
|
||||
long long best_created = -1;
|
||||
|
||||
int n = cJSON_GetArraySize(events);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(events, i);
|
||||
if (!ev || !cJSON_IsObject(ev)) continue;
|
||||
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(ev, "kind");
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(ev, "tags");
|
||||
if (!kind || !cJSON_IsNumber(kind) || !pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring || !tags || !cJSON_IsArray(tags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int kind_val = (int)kind->valuedouble;
|
||||
if (kind_val != 31123 && kind_val != 31124) continue;
|
||||
if (pubkey_filter && strcmp(pubkey_filter, pubkey->valuestring) != 0) continue;
|
||||
|
||||
cJSON* d_val = find_tag_value_string_local(tags, "d");
|
||||
if (!d_val || !cJSON_IsString(d_val) || !d_val->valuestring || strcmp(d_val->valuestring, d_tag) != 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 (!best || created > best_created) {
|
||||
best = ev;
|
||||
best_created = created;
|
||||
}
|
||||
}
|
||||
|
||||
if (!best) {
|
||||
if (out_error) (void)set_error_local(out_error, "skill not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(best, "kind");
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(best, "pubkey");
|
||||
cJSON* content = cJSON_GetObjectItemCaseSensitive(best, "content");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(best, "tags");
|
||||
if (!kind || !cJSON_IsNumber(kind) || !pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring ||
|
||||
!content || !cJSON_IsString(content) || !content->valuestring || !tags || !cJSON_IsArray(tags)) {
|
||||
if (out_error) (void)set_error_local(out_error, "malformed skill event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
out->kind = (int)kind->valuedouble;
|
||||
out->d_tag = strdup(d_tag);
|
||||
out->author_pubkey = strdup(pubkey->valuestring);
|
||||
out->content = strdup(content->valuestring);
|
||||
out->tags = cJSON_Duplicate(tags, 1);
|
||||
out->is_own = (strcmp(pubkey->valuestring, ctx->cfg->keys.public_key_hex) == 0) ? 1 : 0;
|
||||
|
||||
if (!out->d_tag || !out->author_pubkey || !out->content || !out->tags) {
|
||||
free_skill_fetch_result_local(out);
|
||||
if (out_error) (void)set_error_local(out_error, "allocation failure");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fetch_skill_by_identifier_local(tools_context_t* ctx,
|
||||
const char* d_tag,
|
||||
const char* pubkey,
|
||||
skill_fetch_result_local_t* out,
|
||||
char** out_error) {
|
||||
if (out) memset(out, 0, sizeof(*out));
|
||||
if (out_error) *out_error = NULL;
|
||||
|
||||
if (!ctx || !ctx->cfg || !out || !d_tag || !validate_skill_d_tag_local(d_tag)) {
|
||||
if (out_error) (void)set_error_local(out_error, "skill identifier is invalid");
|
||||
return -1;
|
||||
}
|
||||
if (pubkey && pubkey[0] != '\0' && !is_hex_string_len_local(pubkey, 64U)) {
|
||||
if (out_error) (void)set_error_local(out_error, "pubkey must be 64-char hex when provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int own_lookup = (!pubkey || pubkey[0] == '\0' || strcmp(pubkey, ctx->cfg->keys.public_key_hex) == 0);
|
||||
char* events_json = NULL;
|
||||
|
||||
if (own_lookup) {
|
||||
events_json = nostr_handler_get_self_skill_events_json();
|
||||
if (!events_json) {
|
||||
if (out_error) (void)set_error_local(out_error, "self skill cache unavailable");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
cJSON* filter = cJSON_CreateObject();
|
||||
cJSON* kinds = cJSON_CreateArray();
|
||||
cJSON* authors = cJSON_CreateArray();
|
||||
cJSON* d_tags = cJSON_CreateArray();
|
||||
if (!filter || !kinds || !authors || !d_tags) {
|
||||
cJSON_Delete(filter);
|
||||
cJSON_Delete(kinds);
|
||||
cJSON_Delete(authors);
|
||||
cJSON_Delete(d_tags);
|
||||
if (out_error) (void)set_error_local(out_error, "failed to build external skill filter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31123));
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31124));
|
||||
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey));
|
||||
cJSON_AddItemToArray(d_tags, cJSON_CreateString(d_tag));
|
||||
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||
cJSON_AddItemToObject(filter, "authors", authors);
|
||||
cJSON_AddItemToObject(filter, "#d", d_tags);
|
||||
cJSON_AddNumberToObject(filter, "limit", 20);
|
||||
|
||||
events_json = nostr_handler_query_json(filter, 8000);
|
||||
cJSON_Delete(filter);
|
||||
if (!events_json) {
|
||||
if (out_error) (void)set_error_local(out_error, "external skill query failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* events = cJSON_Parse(events_json);
|
||||
free(events_json);
|
||||
if (!events || !cJSON_IsArray(events)) {
|
||||
cJSON_Delete(events);
|
||||
if (out_error) (void)set_error_local(out_error, "skill lookup returned invalid JSON");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rc = fetch_skill_from_events_local(ctx, events, d_tag, own_lookup ? NULL : pubkey, out, out_error);
|
||||
cJSON_Delete(events);
|
||||
return rc;
|
||||
}
|
||||
|
||||
char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
||||
|
||||
@@ -1240,9 +803,11 @@ char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
|
||||
}
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
cJSON* summary_rows = cJSON_CreateArray();
|
||||
cJSON* skills = cJSON_CreateArray();
|
||||
if (!out || !skills) {
|
||||
if (!out || !summary_rows || !skills) {
|
||||
cJSON_Delete(out);
|
||||
cJSON_Delete(summary_rows);
|
||||
cJSON_Delete(skills);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(adoption_tags);
|
||||
@@ -1250,21 +815,7 @@ char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t rendered_cap = 512U;
|
||||
size_t rendered_used = 0U;
|
||||
char* rendered_lines = (char*)malloc(rendered_cap);
|
||||
if (!rendered_lines) {
|
||||
cJSON_Delete(out);
|
||||
cJSON_Delete(skills);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(adoption_tags);
|
||||
free(adoption_content);
|
||||
return NULL;
|
||||
}
|
||||
rendered_lines[0] = '\0';
|
||||
|
||||
int adopted_count = 0;
|
||||
int skill_count = 0;
|
||||
int event_count = cJSON_GetArraySize(events);
|
||||
for (int i = 0; i < event_count; i++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(events, i);
|
||||
@@ -1296,91 +847,59 @@ char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
|
||||
if (adopted_filter >= 0 && is_adopted != adopted_filter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* owner = skill_owner_label_local(ctx->cfg, pubkey->valuestring);
|
||||
cJSON* scope_tag = find_tag_value_string_local(tags, "scope");
|
||||
cJSON* desc_tag = find_tag_value_string_local(tags, "description");
|
||||
const char* scope_out = (scope_tag && cJSON_IsString(scope_tag) && scope_tag->valuestring)
|
||||
? scope_tag->valuestring
|
||||
: ((kind_val == 31124) ? "private" : "public");
|
||||
const char* desc_out = (desc_tag && cJSON_IsString(desc_tag) && desc_tag->valuestring)
|
||||
? desc_tag->valuestring
|
||||
: "";
|
||||
|
||||
cJSON* row = cJSON_CreateObject();
|
||||
if (!row) {
|
||||
continue;
|
||||
}
|
||||
cJSON_AddStringToObject(row, "d_tag", d->valuestring);
|
||||
cJSON_AddStringToObject(row, "owner", owner);
|
||||
cJSON_AddStringToObject(row, "scope", scope_out);
|
||||
cJSON_AddNumberToObject(row, "kind", kind_val);
|
||||
cJSON_AddBoolToObject(row, "adopted", is_adopted ? 1 : 0);
|
||||
if (desc_out[0] != '\0') {
|
||||
cJSON_AddStringToObject(row, "description", desc_out);
|
||||
}
|
||||
cJSON_AddItemToArray(skills, row);
|
||||
|
||||
if (is_adopted) {
|
||||
adopted_count++;
|
||||
}
|
||||
skill_count++;
|
||||
|
||||
if (append_textf_skill_list_local(&rendered_lines,
|
||||
&rendered_cap,
|
||||
&rendered_used,
|
||||
"- %s | owner=%s | scope=%s | adopted=%s%s%s\n",
|
||||
d->valuestring,
|
||||
owner,
|
||||
scope_out,
|
||||
is_adopted ? "yes" : "no",
|
||||
desc_out[0] ? " | description=" : "",
|
||||
desc_out) != 0) {
|
||||
free(rendered_lines);
|
||||
cJSON_Delete(out);
|
||||
cJSON_Delete(skills);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(adoption_tags);
|
||||
free(adoption_content);
|
||||
return NULL;
|
||||
const char* owner = skill_owner_label_local(ctx->cfg, pubkey->valuestring);
|
||||
|
||||
cJSON* detail = extract_skill_summary_local(ev);
|
||||
if (detail) {
|
||||
cJSON_AddStringToObject(detail, "owner", owner);
|
||||
cJSON_AddBoolToObject(detail, "adopted", is_adopted ? 1 : 0);
|
||||
cJSON_AddItemToArray(skills, detail);
|
||||
}
|
||||
|
||||
cJSON* row = cJSON_CreateObject();
|
||||
if (row) {
|
||||
cJSON_AddStringToObject(row, "d_tag", d->valuestring);
|
||||
cJSON_AddStringToObject(row, "owner", owner);
|
||||
cJSON_AddBoolToObject(row, "adopted", is_adopted ? 1 : 0);
|
||||
cJSON_AddItemToArray(summary_rows, row);
|
||||
}
|
||||
}
|
||||
|
||||
if (skill_count == 0) {
|
||||
if (append_textf_skill_list_local(&rendered_lines, &rendered_cap, &rendered_used, "- (none)\n") != 0) {
|
||||
free(rendered_lines);
|
||||
cJSON_Delete(out);
|
||||
cJSON_Delete(skills);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(adoption_tags);
|
||||
free(adoption_content);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char header[128];
|
||||
snprintf(header, sizeof(header), "Skills: %d total (%d adopted)\n", skill_count, adopted_count);
|
||||
size_t header_len = strlen(header);
|
||||
char* rendered = (char*)malloc(header_len + rendered_used + 1U);
|
||||
if (!rendered) {
|
||||
free(rendered_lines);
|
||||
cJSON* skills_pretty = cJSON_CreateArray();
|
||||
if (!skills_pretty) {
|
||||
cJSON_Delete(out);
|
||||
cJSON_Delete(skills);
|
||||
cJSON_Delete(events);
|
||||
cJSON_Delete(adoption_tags);
|
||||
free(adoption_content);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(rendered, header, header_len);
|
||||
memcpy(rendered + header_len, rendered_lines, rendered_used + 1U);
|
||||
free(rendered_lines);
|
||||
|
||||
int skill_count = cJSON_GetArraySize(skills);
|
||||
for (int i = 0; i < skill_count; i++) {
|
||||
cJSON* skill_item = cJSON_GetArrayItem(skills, i);
|
||||
if (!skill_item) continue;
|
||||
char* skill_json = cJSON_PrintUnformatted(skill_item);
|
||||
if (!skill_json) continue;
|
||||
|
||||
cJSON* block = cJSON_CreateString(skill_json);
|
||||
free(skill_json);
|
||||
if (block) {
|
||||
cJSON_AddItemToArray(skills_pretty, block);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(out, "success", 1);
|
||||
cJSON_AddNumberToObject(out, "count", skill_count);
|
||||
cJSON_AddNumberToObject(out, "adopted_count", adopted_count);
|
||||
cJSON_AddBoolToObject(out, "fallback_used", 0);
|
||||
cJSON_AddStringToObject(out, "skills_separator", "\n\n\n");
|
||||
cJSON_AddItemToObject(out, "summary", summary_rows);
|
||||
cJSON_AddItemToObject(out, "skills", skills);
|
||||
cJSON_AddStringToObject(out, "rendered", rendered);
|
||||
free(rendered);
|
||||
cJSON_AddItemToObject(out, "skills_pretty", skills_pretty);
|
||||
cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(skills));
|
||||
cJSON_AddNumberToObject(out, "adopted_count", adopted_count);
|
||||
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
@@ -2251,338 +1770,16 @@ char* execute_skill_edit(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
char* execute_skill_run(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_args_local(args_json);
|
||||
if (!args) return json_error_local("invalid arguments JSON");
|
||||
|
||||
cJSON* d_tag = cJSON_GetObjectItemCaseSensitive(args, "d_tag");
|
||||
cJSON* d = cJSON_GetObjectItemCaseSensitive(args, "d");
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
|
||||
cJSON* run_args = cJSON_GetObjectItemCaseSensitive(args, "args");
|
||||
cJSON* input = cJSON_GetObjectItemCaseSensitive(args, "input");
|
||||
cJSON* sandbox = cJSON_GetObjectItemCaseSensitive(args, "sandbox");
|
||||
|
||||
const char* d_tag_value = NULL;
|
||||
if (d_tag && cJSON_IsString(d_tag) && d_tag->valuestring) {
|
||||
d_tag_value = d_tag->valuestring;
|
||||
} else if (d && cJSON_IsString(d) && d->valuestring) {
|
||||
d_tag_value = d->valuestring;
|
||||
}
|
||||
|
||||
if (!d_tag_value || !validate_skill_d_tag_local(d_tag_value)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("skill_run requires valid d_tag");
|
||||
}
|
||||
|
||||
const char* pubkey_value = NULL;
|
||||
if (pubkey && cJSON_IsString(pubkey) && pubkey->valuestring && pubkey->valuestring[0] != '\0') {
|
||||
pubkey_value = pubkey->valuestring;
|
||||
}
|
||||
|
||||
const char* user_args = "";
|
||||
if (run_args && cJSON_IsString(run_args) && run_args->valuestring) {
|
||||
user_args = run_args->valuestring;
|
||||
} else if (input && cJSON_IsString(input) && input->valuestring) {
|
||||
user_args = input->valuestring;
|
||||
}
|
||||
|
||||
int depth_incremented = 0;
|
||||
pthread_mutex_lock(&g_skill_run_depth_mutex_local);
|
||||
if (g_skill_run_depth_local >= 3) {
|
||||
pthread_mutex_unlock(&g_skill_run_depth_mutex_local);
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("skill_run max recursion depth exceeded");
|
||||
}
|
||||
g_skill_run_depth_local++;
|
||||
depth_incremented = 1;
|
||||
pthread_mutex_unlock(&g_skill_run_depth_mutex_local);
|
||||
|
||||
skill_fetch_result_local_t skill;
|
||||
memset(&skill, 0, sizeof(skill));
|
||||
char* fetch_error = NULL;
|
||||
char* tools_json = NULL;
|
||||
char* final_result = NULL;
|
||||
cJSON* messages = NULL;
|
||||
char* response_json = NULL;
|
||||
int turns_run = 0;
|
||||
llm_config_t old_llm_cfg;
|
||||
int llm_overridden = 0;
|
||||
|
||||
if (fetch_skill_by_identifier_local(ctx, d_tag_value, pubkey_value, &skill, &fetch_error) != 0) {
|
||||
response_json = json_error_local(fetch_error ? fetch_error : "skill lookup failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int sandbox_enabled = (!skill.is_own) ? 1 : 0;
|
||||
if (sandbox && cJSON_IsBool(sandbox)) {
|
||||
sandbox_enabled = cJSON_IsTrue(sandbox) ? 1 : 0;
|
||||
}
|
||||
|
||||
cJSON* llm_tag = find_tag_value_string_local(skill.tags, "llm");
|
||||
if (llm_tag && cJSON_IsString(llm_tag) && llm_tag->valuestring && llm_tag->valuestring[0] != '\0') {
|
||||
if (llm_get_config(&old_llm_cfg) == 0) {
|
||||
llm_config_t next_llm_cfg = old_llm_cfg;
|
||||
apply_llm_spec_local_to_config(llm_tag->valuestring, &next_llm_cfg);
|
||||
if (llm_set_config(&next_llm_cfg) == 0) {
|
||||
llm_overridden = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* source = skill.is_own ? "own" : "external";
|
||||
size_t sys_cap = strlen(skill.d_tag ? skill.d_tag : d_tag_value) +
|
||||
strlen(skill.content ? skill.content : "") +
|
||||
strlen(source) + 320U;
|
||||
char* system_prompt = (char*)malloc(sys_cap);
|
||||
if (!system_prompt) {
|
||||
response_json = json_error_local("allocation failure");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
snprintf(system_prompt,
|
||||
sys_cap,
|
||||
"Skill execution context:\n"
|
||||
"- You are executing a specific skill on demand.\n"
|
||||
"- Follow the skill instructions below precisely.\n"
|
||||
"- Return a concise, actionable result.\n\n"
|
||||
"Skill d_tag: %s\n"
|
||||
"Skill source: %s\n\n"
|
||||
"Skill instructions:\n%s",
|
||||
skill.d_tag ? skill.d_tag : d_tag_value,
|
||||
source,
|
||||
skill.content ? skill.content : "");
|
||||
|
||||
tools_json = sandbox_enabled ? build_sandboxed_tools_json_local(ctx) : tools_build_openai_schema_json(ctx);
|
||||
if (!tools_json) {
|
||||
free(system_prompt);
|
||||
response_json = json_error_local("skill_run failed to build tool schema");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
messages = cJSON_CreateArray();
|
||||
if (!messages ||
|
||||
append_simple_message_local(messages, "system", system_prompt) != 0 ||
|
||||
append_simple_message_local(messages, "user", user_args) != 0) {
|
||||
free(system_prompt);
|
||||
response_json = json_error_local("skill_run failed to initialize messages");
|
||||
goto cleanup;
|
||||
}
|
||||
free(system_prompt);
|
||||
|
||||
int max_turns = ctx->cfg->tools.trigger_max_turns > 0
|
||||
? ctx->cfg->tools.trigger_max_turns
|
||||
: (ctx->cfg->tools.max_turns > 0 ? ctx->cfg->tools.max_turns : 8);
|
||||
|
||||
for (int turn = 0; turn < max_turns; turn++) {
|
||||
turns_run = turn + 1;
|
||||
|
||||
char* messages_json = cJSON_PrintUnformatted(messages);
|
||||
if (!messages_json) {
|
||||
response_json = json_error_local("skill_run failed to serialize messages");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
llm_response_t resp;
|
||||
int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp);
|
||||
free(messages_json);
|
||||
if (rc != 0) {
|
||||
response_json = json_error_local("skill_run LLM request failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (resp.tool_call_count <= 0) {
|
||||
final_result = strdup(resp.content ? resp.content : "");
|
||||
llm_response_free(&resp);
|
||||
if (!final_result) {
|
||||
response_json = json_error_local("allocation failure");
|
||||
goto cleanup;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (append_assistant_tool_calls_message_local(messages, &resp) != 0) {
|
||||
llm_response_free(&resp);
|
||||
response_json = json_error_local("skill_run failed to append assistant tool calls");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (int i = 0; i < resp.tool_call_count; i++) {
|
||||
llm_tool_call_t* tc = &resp.tool_calls[i];
|
||||
char* tool_result = NULL;
|
||||
|
||||
if (is_tool_blocked_in_skill_run_local(tc->name)) {
|
||||
tool_result = strdup("{\"success\":false,\"error\":\"tool blocked by skill_run policy\"}");
|
||||
} else if (sandbox_enabled && !is_tool_sandbox_safe_local(tc->name)) {
|
||||
tool_result = strdup("{\"success\":false,\"error\":\"tool blocked by skill_run sandbox\"}");
|
||||
} else {
|
||||
tool_result = tools_execute(ctx, tc->name, tc->arguments_json);
|
||||
}
|
||||
|
||||
if (!tool_result) {
|
||||
tool_result = strdup("{\"success\":false,\"error\":\"tool execution failed\"}");
|
||||
}
|
||||
|
||||
if (append_tool_result_message_local(messages,
|
||||
tc->id ? tc->id : "",
|
||||
tool_result ? tool_result : "{\"success\":false,\"error\":\"tool execution failed\"}") != 0) {
|
||||
free(tool_result);
|
||||
llm_response_free(&resp);
|
||||
response_json = json_error_local("skill_run failed to append tool result");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (maybe_append_loaded_skill_instructions_local(messages, tc->name, tool_result) != 0) {
|
||||
free(tool_result);
|
||||
llm_response_free(&resp);
|
||||
response_json = json_error_local("skill_run failed to apply loaded skill instructions");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
free(tool_result);
|
||||
}
|
||||
|
||||
llm_response_free(&resp);
|
||||
}
|
||||
|
||||
if (!final_result) {
|
||||
final_result = strdup("");
|
||||
if (!final_result) {
|
||||
response_json = json_error_local("allocation failure");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) {
|
||||
response_json = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(out, "success", 1);
|
||||
cJSON_AddStringToObject(out, "d_tag", skill.d_tag ? skill.d_tag : d_tag_value);
|
||||
cJSON_AddStringToObject(out, "source", source);
|
||||
cJSON_AddBoolToObject(out, "sandboxed", sandbox_enabled ? 1 : 0);
|
||||
cJSON_AddNumberToObject(out, "turns", turns_run);
|
||||
cJSON_AddStringToObject(out, "result", final_result);
|
||||
|
||||
response_json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
|
||||
cleanup:
|
||||
free(final_result);
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
free(fetch_error);
|
||||
free_skill_fetch_result_local(&skill);
|
||||
cJSON_Delete(args);
|
||||
if (llm_overridden) {
|
||||
(void)llm_set_config(&old_llm_cfg);
|
||||
}
|
||||
if (depth_incremented) {
|
||||
pthread_mutex_lock(&g_skill_run_depth_mutex_local);
|
||||
g_skill_run_depth_local--;
|
||||
if (g_skill_run_depth_local < 0) g_skill_run_depth_local = 0;
|
||||
pthread_mutex_unlock(&g_skill_run_depth_mutex_local);
|
||||
}
|
||||
|
||||
return response_json ? response_json : json_error_local("skill_run failed");
|
||||
}
|
||||
|
||||
char* execute_skill_load(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_args_local(args_json);
|
||||
if (!args) return json_error_local("invalid arguments JSON");
|
||||
|
||||
cJSON* d_tag = cJSON_GetObjectItemCaseSensitive(args, "d_tag");
|
||||
cJSON* d = cJSON_GetObjectItemCaseSensitive(args, "d");
|
||||
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
|
||||
|
||||
const char* d_tag_value = NULL;
|
||||
if (d_tag && cJSON_IsString(d_tag) && d_tag->valuestring) {
|
||||
d_tag_value = d_tag->valuestring;
|
||||
} else if (d && cJSON_IsString(d) && d->valuestring) {
|
||||
d_tag_value = d->valuestring;
|
||||
}
|
||||
|
||||
if (!d_tag_value || !validate_skill_d_tag_local(d_tag_value)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("skill_load requires valid d_tag");
|
||||
}
|
||||
|
||||
const char* pubkey_value = NULL;
|
||||
if (pubkey && cJSON_IsString(pubkey) && pubkey->valuestring && pubkey->valuestring[0] != '\0') {
|
||||
pubkey_value = pubkey->valuestring;
|
||||
}
|
||||
|
||||
skill_fetch_result_local_t skill;
|
||||
memset(&skill, 0, sizeof(skill));
|
||||
char* fetch_error = NULL;
|
||||
|
||||
if (fetch_skill_by_identifier_local(ctx, d_tag_value, pubkey_value, &skill, &fetch_error) != 0) {
|
||||
char* err = json_error_local(fetch_error ? fetch_error : "skill lookup failed");
|
||||
free(fetch_error);
|
||||
cJSON_Delete(args);
|
||||
return err;
|
||||
}
|
||||
|
||||
const char* content = skill.content ? skill.content : "";
|
||||
size_t instructions_cap = strlen(content) + 256U;
|
||||
char* instructions = (char*)malloc(instructions_cap);
|
||||
if (!instructions) {
|
||||
free_skill_fetch_result_local(&skill);
|
||||
cJSON_Delete(args);
|
||||
return json_error_local("allocation failure");
|
||||
}
|
||||
|
||||
snprintf(instructions,
|
||||
instructions_cap,
|
||||
"SKILL INSTRUCTIONS — Follow these for the remainder of this conversation:\n\n%s\n\nEND SKILL INSTRUCTIONS",
|
||||
content);
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) {
|
||||
free(instructions);
|
||||
free_skill_fetch_result_local(&skill);
|
||||
cJSON_Delete(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(out, "success", 1);
|
||||
cJSON_AddStringToObject(out, "type", "skill_instructions");
|
||||
cJSON_AddStringToObject(out, "d_tag", skill.d_tag ? skill.d_tag : d_tag_value);
|
||||
cJSON_AddStringToObject(out, "source", skill.is_own ? "own" : "external");
|
||||
cJSON_AddStringToObject(out, "instructions", instructions);
|
||||
cJSON_AddStringToObject(out,
|
||||
"note",
|
||||
"The above instructions should be followed for subsequent reasoning in this conversation.");
|
||||
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
free(instructions);
|
||||
free_skill_fetch_result_local(&skill);
|
||||
free(fetch_error);
|
||||
cJSON_Delete(args);
|
||||
return json;
|
||||
}
|
||||
|
||||
char* execute_skill_refresh(tools_context_t* ctx, const char* args_json) {
|
||||
(void)args_json;
|
||||
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
||||
|
||||
int rc = nostr_handler_refresh_self_skill_cache_from_relays(5000);
|
||||
if (rc != 0) {
|
||||
return json_error_local("skill_refresh failed to reload self-skill cache from relays");
|
||||
}
|
||||
|
||||
agent_invalidate_adopted_skills_cache();
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
cJSON_AddBoolToObject(out, "success", 1);
|
||||
cJSON_AddStringToObject(out, "message", "self-skill cache refreshed from relays; adopted skills cache invalidated; next context build will use latest relay data");
|
||||
cJSON_AddStringToObject(out, "message", "adopted skills cache invalidated; next context build will reload skills");
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
return json;
|
||||
|
||||
@@ -60,20 +60,6 @@ static int nip44_encrypt_self_local(tools_context_t* ctx, const char* plaintext,
|
||||
if (!ctx || !ctx->cfg || !out_ciphertext) return -1;
|
||||
|
||||
const char* plain = plaintext ? plaintext : "";
|
||||
|
||||
/* Phase 3 item 13: route self-encrypt through the process signer when
|
||||
* wired into the tools context. Falls back to the legacy raw-key path. */
|
||||
if (ctx->signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_encrypt(ctx->signer,
|
||||
ctx->cfg->keys.public_key_hex,
|
||||
plain,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) return -1;
|
||||
*out_ciphertext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = (strlen(plain) * 4U) + 1024U;
|
||||
char* ciphertext = (char*)malloc(out_cap);
|
||||
if (!ciphertext) return -1;
|
||||
@@ -95,19 +81,6 @@ static int nip44_encrypt_self_local(tools_context_t* ctx, const char* plaintext,
|
||||
static int nip44_decrypt_self_local(tools_context_t* ctx, const char* ciphertext, char** out_plaintext) {
|
||||
if (!ctx || !ctx->cfg || !ciphertext || !out_plaintext) return -1;
|
||||
|
||||
/* Phase 3 item 13: route self-decrypt through the process signer when
|
||||
* wired into the tools context. Falls back to the legacy raw-key path. */
|
||||
if (ctx->signer) {
|
||||
char* signer_out = NULL;
|
||||
int rc = nostr_signer_nip44_decrypt(ctx->signer,
|
||||
ctx->cfg->keys.public_key_hex,
|
||||
ciphertext,
|
||||
&signer_out);
|
||||
if (rc != NOSTR_SUCCESS || !signer_out) return -1;
|
||||
*out_plaintext = signer_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t out_cap = strlen(ciphertext) + 1024U;
|
||||
char* plaintext = (char*)malloc(out_cap);
|
||||
if (!plaintext) return -1;
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
#define DIDACTYL_TOOLS_H
|
||||
|
||||
#include "../config.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_signer.h"
|
||||
|
||||
struct trigger_manager;
|
||||
|
||||
typedef struct {
|
||||
didactyl_config_t* cfg;
|
||||
/* Optional process-lifetime signer handle (non-owning). When set,
|
||||
* NIP-04/44 encrypt/decrypt tool call sites route through the signer
|
||||
* verbs so remote signer modes keep the nsec out of the agent. Falls
|
||||
* back to the legacy raw-key path when NULL (local mode / pre-Phase-3). */
|
||||
nostr_signer_t* signer;
|
||||
struct trigger_manager* trigger_manager;
|
||||
int template_sender_tier;
|
||||
const char* template_current_user_message;
|
||||
@@ -23,10 +17,6 @@ typedef struct {
|
||||
|
||||
int tools_init(tools_context_t* ctx, didactyl_config_t* cfg);
|
||||
void tools_cleanup(tools_context_t* ctx);
|
||||
/* Wire a process-lifetime signer handle into the tools context. Non-owning:
|
||||
* caller must keep the signer alive for the context's lifetime and free it
|
||||
* after tools_cleanup. Passing NULL restores the legacy raw-key fallback. */
|
||||
void tools_set_signer(tools_context_t* ctx, nostr_signer_t* signer);
|
||||
char* tools_build_openai_schema_json(const tools_context_t* ctx);
|
||||
char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* args_json);
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ int tools_init_legacy(tools_context_t* ctx, didactyl_config_t* cfg) {
|
||||
if (!ctx || !cfg) return -1;
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->cfg = cfg;
|
||||
ctx->signer = NULL;
|
||||
ctx->trigger_manager = NULL;
|
||||
ctx->template_sender_tier = 0;
|
||||
ctx->template_current_user_message = NULL;
|
||||
@@ -31,11 +30,6 @@ int tools_init_legacy(tools_context_t* ctx, didactyl_config_t* cfg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tools_set_signer(tools_context_t* ctx, nostr_signer_t* signer) {
|
||||
if (!ctx) return;
|
||||
ctx->signer = signer;
|
||||
}
|
||||
|
||||
void tools_cleanup_legacy(tools_context_t* ctx) {
|
||||
if (!ctx) return;
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
@@ -155,12 +149,6 @@ char* tools_execute_legacy(tools_context_t* ctx, const char* tool_name, const ch
|
||||
if (strcmp(tool_name, "skill_search") == 0) {
|
||||
return execute_skill_search(args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "skill_run") == 0 || strcmp(tool_name, "run") == 0) {
|
||||
return execute_skill_run(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "skill_load") == 0) {
|
||||
return execute_skill_load(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "skill_edit") == 0) {
|
||||
return execute_skill_edit(ctx, args_json);
|
||||
}
|
||||
|
||||
@@ -65,8 +65,6 @@ char* execute_skill_remove(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_edit(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_refresh(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_search(const char* args_json);
|
||||
char* execute_skill_run(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_load(tools_context_t* ctx, const char* args_json);
|
||||
|
||||
char* execute_memory_save(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_memory_recall(tools_context_t* ctx, const char* args_json);
|
||||
|
||||
@@ -1117,89 +1117,6 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) {
|
||||
cJSON_AddItemToObject(t26, "function", t26_fn);
|
||||
cJSON_AddItemToArray(tools, t26);
|
||||
|
||||
cJSON* t26a = cJSON_CreateObject();
|
||||
cJSON* t26a_fn = cJSON_CreateObject();
|
||||
cJSON* t26a_params = cJSON_CreateObject();
|
||||
cJSON* t26a_props = cJSON_CreateObject();
|
||||
cJSON* t26a_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t26a, "type", "function");
|
||||
cJSON_AddStringToObject(t26a_fn, "name", "skill_run");
|
||||
cJSON_AddStringToObject(t26a_fn, "description", "Execute a skill one-shot in an isolated context with optional sandboxing. Use d_tag for own skills or pubkey+d_tag for external skills.");
|
||||
cJSON_AddStringToObject(t26a_params, "type", "object");
|
||||
cJSON_AddItemToObject(t26a_params, "properties", t26a_props);
|
||||
cJSON_AddItemToObject(t26a_params, "required", t26a_required);
|
||||
|
||||
cJSON* p_skill_run_d_tag = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_run_d_tag, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_run_d_tag, "description", "Skill d tag identifier");
|
||||
cJSON_AddItemToObject(t26a_props, "d_tag", p_skill_run_d_tag);
|
||||
|
||||
cJSON* p_skill_run_d = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_run_d, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_run_d, "description", "Compatibility alias for d_tag");
|
||||
cJSON_AddItemToObject(t26a_props, "d", p_skill_run_d);
|
||||
|
||||
cJSON* p_skill_run_pubkey = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_run_pubkey, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_run_pubkey, "description", "Optional author pubkey for external skills");
|
||||
cJSON_AddItemToObject(t26a_props, "pubkey", p_skill_run_pubkey);
|
||||
|
||||
cJSON* p_skill_run_args = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_run_args, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_run_args, "description", "Arguments/context to pass to the skill");
|
||||
cJSON_AddItemToObject(t26a_props, "args", p_skill_run_args);
|
||||
|
||||
cJSON* p_skill_run_input = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_run_input, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_run_input, "description", "Slash shorthand fallback for args");
|
||||
cJSON_AddItemToObject(t26a_props, "input", p_skill_run_input);
|
||||
|
||||
cJSON* p_skill_run_sandbox = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_run_sandbox, "type", "boolean");
|
||||
cJSON_AddStringToObject(p_skill_run_sandbox, "description", "Override sandbox behavior (default on for external skills)");
|
||||
cJSON_AddItemToObject(t26a_props, "sandbox", p_skill_run_sandbox);
|
||||
|
||||
cJSON_AddItemToArray(t26a_required, cJSON_CreateString("d_tag"));
|
||||
|
||||
cJSON_AddItemToObject(t26a_fn, "parameters", t26a_params);
|
||||
cJSON_AddItemToObject(t26a, "function", t26a_fn);
|
||||
cJSON_AddItemToArray(tools, t26a);
|
||||
|
||||
cJSON* t26b = cJSON_CreateObject();
|
||||
cJSON* t26b_fn = cJSON_CreateObject();
|
||||
cJSON* t26b_params = cJSON_CreateObject();
|
||||
cJSON* t26b_props = cJSON_CreateObject();
|
||||
cJSON* t26b_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t26b, "type", "function");
|
||||
cJSON_AddStringToObject(t26b_fn, "name", "skill_load");
|
||||
cJSON_AddStringToObject(t26b_fn, "description", "Load a skill's instructions into the current conversation context.");
|
||||
cJSON_AddStringToObject(t26b_params, "type", "object");
|
||||
cJSON_AddItemToObject(t26b_params, "properties", t26b_props);
|
||||
cJSON_AddItemToObject(t26b_params, "required", t26b_required);
|
||||
|
||||
cJSON* p_skill_load_d_tag = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_load_d_tag, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_load_d_tag, "description", "Skill d tag identifier");
|
||||
cJSON_AddItemToObject(t26b_props, "d_tag", p_skill_load_d_tag);
|
||||
|
||||
cJSON* p_skill_load_d = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_load_d, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_load_d, "description", "Compatibility alias for d_tag");
|
||||
cJSON_AddItemToObject(t26b_props, "d", p_skill_load_d);
|
||||
|
||||
cJSON* p_skill_load_pubkey = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_load_pubkey, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_load_pubkey, "description", "Optional author pubkey for external skills");
|
||||
cJSON_AddItemToObject(t26b_props, "pubkey", p_skill_load_pubkey);
|
||||
|
||||
cJSON_AddItemToArray(t26b_required, cJSON_CreateString("d_tag"));
|
||||
|
||||
cJSON_AddItemToObject(t26b_fn, "parameters", t26b_params);
|
||||
cJSON_AddItemToObject(t26b, "function", t26b_fn);
|
||||
cJSON_AddItemToArray(tools, t26b);
|
||||
|
||||
cJSON* t27 = cJSON_CreateObject();
|
||||
cJSON* t27_fn = cJSON_CreateObject();
|
||||
cJSON* t27_params = cJSON_CreateObject();
|
||||
|
||||
+2
-309
@@ -589,274 +589,6 @@ static void parse_trigger_runtime_tags(cJSON* tags,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Load the set of adopted skill d_tags from the kind 10123 adoption list.
|
||||
*
|
||||
* Fetches the agent's latest kind 10123 event from cache, parses the "a" tags,
|
||||
* and populates out_d_tags with the d_tag portion of each adopted skill address.
|
||||
*
|
||||
* Since both trigger load paths only deal with self-authored skills, matching on
|
||||
* d_tag alone is sufficient — the pubkey in the "a" tag will be the agent's own.
|
||||
*
|
||||
* Returns the number of adopted d_tags, or -1 on error.
|
||||
*/
|
||||
#define TRIGGER_ADOPTED_D_TAGS_MAX 64
|
||||
static int load_adopted_d_tags(trigger_manager_t* mgr,
|
||||
char out_d_tags[][65],
|
||||
int max_d_tags) {
|
||||
if (!mgr || !mgr->cfg || !out_d_tags || max_d_tags <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
char* adoption_json = nostr_handler_get_self_events_by_kind_json(10123);
|
||||
if (!adoption_json) {
|
||||
DEBUG_WARN("[didactyl] trigger adoption check: no kind 10123 event in cache");
|
||||
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);
|
||||
DEBUG_WARN("[didactyl] trigger adoption check: kind 10123 parse failed or empty");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find the latest adoption list event by created_at */
|
||||
cJSON* list_event = NULL;
|
||||
double list_event_created_at = -1.0;
|
||||
int adoption_n = cJSON_GetArraySize(adoption_events);
|
||||
for (int ai = 0; ai < adoption_n; ai++) {
|
||||
cJSON* ev = cJSON_GetArrayItem(adoption_events, ai);
|
||||
if (!ev || !cJSON_IsObject(ev)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* ev_pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
|
||||
if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
|
||||
strcmp(ev_pubkey->valuestring, mgr->cfg->keys.public_key_hex) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(ev, "created_at");
|
||||
double created = (created_at && cJSON_IsNumber(created_at)) ? created_at->valuedouble : 0.0;
|
||||
if (!list_event || created > list_event_created_at) {
|
||||
list_event = ev;
|
||||
list_event_created_at = created;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* list_tags = list_event ? cJSON_GetObjectItemCaseSensitive(list_event, "tags") : NULL;
|
||||
if (!list_tags || !cJSON_IsArray(list_tags)) {
|
||||
cJSON_Delete(adoption_events);
|
||||
DEBUG_INFO("[didactyl] trigger adoption check: no tags in adoption list event");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tn = cJSON_GetArraySize(list_tags);
|
||||
for (int i = 0; i < tn && count < max_d_tags; i++) {
|
||||
cJSON* tag = cJSON_GetArrayItem(list_tags, i);
|
||||
if (!tag || !cJSON_IsArray(tag)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/* Parse "kind:pubkey:d_tag" — we only need the d_tag (3rd field) */
|
||||
const char* p1 = strchr(val->valuestring, ':');
|
||||
if (!p1) continue;
|
||||
const char* p2 = strchr(p1 + 1, ':');
|
||||
if (!p2) continue;
|
||||
|
||||
const char* d_tag_start = p2 + 1;
|
||||
size_t d_tag_len = strlen(d_tag_start);
|
||||
if (d_tag_len == 0 || d_tag_len >= 65U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
snprintf(out_d_tags[count], 65, "%s", d_tag_start);
|
||||
count++;
|
||||
}
|
||||
|
||||
cJSON_Delete(adoption_events);
|
||||
DEBUG_INFO("[didactyl] trigger adoption check: %d adopted skill(s) found", count);
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether d_tag appears in the adopted_d_tags array.
|
||||
*/
|
||||
static int is_d_tag_adopted(const char* d_tag,
|
||||
char adopted_d_tags[][65],
|
||||
int adopted_count) {
|
||||
if (!d_tag || d_tag[0] == '\0' || !adopted_d_tags || adopted_count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < adopted_count; i++) {
|
||||
if (strcmp(adopted_d_tags[i], d_tag) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int trigger_manager_reconcile_from_relays(trigger_manager_t* mgr) {
|
||||
if (!mgr || !mgr->cfg) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* filter = cJSON_CreateObject();
|
||||
cJSON* kinds = cJSON_CreateArray();
|
||||
cJSON* authors = cJSON_CreateArray();
|
||||
if (!filter || !kinds || !authors) {
|
||||
cJSON_Delete(filter);
|
||||
cJSON_Delete(kinds);
|
||||
cJSON_Delete(authors);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31123));
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31124));
|
||||
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||
cJSON_AddItemToArray(authors, cJSON_CreateString(mgr->cfg->keys.public_key_hex));
|
||||
cJSON_AddItemToObject(filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(filter, "limit", 300);
|
||||
|
||||
char* events_json = nostr_handler_query_json(filter, 8000);
|
||||
cJSON_Delete(filter);
|
||||
if (!events_json) {
|
||||
DEBUG_WARN("[didactyl] trigger reconcile skipped: relay query returned no data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* events = cJSON_Parse(events_json);
|
||||
free(events_json);
|
||||
if (!events || !cJSON_IsArray(events)) {
|
||||
cJSON_Delete(events);
|
||||
DEBUG_WARN("[didactyl] trigger reconcile skipped: invalid JSON from relay query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int considered = 0;
|
||||
int loaded = 0;
|
||||
int skipped_not_adopted = 0;
|
||||
int skipped_disabled = 0;
|
||||
|
||||
/* Load adopted d_tags from kind 10123 adoption list */
|
||||
char adopted_d_tags[TRIGGER_ADOPTED_D_TAGS_MAX][65];
|
||||
memset(adopted_d_tags, 0, sizeof(adopted_d_tags));
|
||||
int adopted_count = load_adopted_d_tags(mgr, adopted_d_tags, TRIGGER_ADOPTED_D_TAGS_MAX);
|
||||
if (adopted_count <= 0) {
|
||||
DEBUG_WARN("[didactyl] trigger reconcile: no adopted skills found — skipping all trigger registration");
|
||||
cJSON_Delete(events);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int event_count = cJSON_GetArraySize(events);
|
||||
for (int i = 0; i < event_count; i++) {
|
||||
cJSON* skill_event = cJSON_GetArrayItem(events, i);
|
||||
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)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* d = find_tag_value_string(tags, "d");
|
||||
cJSON* trigger = find_tag_value_string(tags, "trigger");
|
||||
cJSON* filter_j = find_tag_value_string(tags, "filter");
|
||||
cJSON* action = find_tag_value_string(tags, "action");
|
||||
cJSON* enabled = find_tag_value_string(tags, "enabled");
|
||||
|
||||
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_j && cJSON_IsString(filter_j) && filter_j->valuestring) ? filter_j->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";
|
||||
|
||||
considered++;
|
||||
if (!d_tag || d_tag[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Adoption gate: only register triggers for adopted skills */
|
||||
if (!is_d_tag_adopted(d_tag, adopted_d_tags, adopted_count)) {
|
||||
skipped_not_adopted++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int trigger_supported = trigger_s &&
|
||||
(strcmp(trigger_s, "nostr-subscription") == 0 ||
|
||||
strcmp(trigger_s, "webhook") == 0 ||
|
||||
strcmp(trigger_s, "cron") == 0 ||
|
||||
strcmp(trigger_s, "chain") == 0 ||
|
||||
strcmp(trigger_s, "dm") == 0);
|
||||
if (!trigger_supported || !filter_s || filter_s[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Enabled gate: skip skills explicitly disabled */
|
||||
int is_enabled = (strcmp(enabled_s, "false") == 0 || strcmp(enabled_s, "0") == 0) ? 0 : 1;
|
||||
if (!is_enabled) {
|
||||
skipped_disabled++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(action_s, "template") == 0) {
|
||||
DEBUG_WARN("[didactyl] trigger action template is deprecated; forcing llm for d_tag=%s", d_tag);
|
||||
}
|
||||
|
||||
const char* llm_s = NULL;
|
||||
const char* tools_s = NULL;
|
||||
int has_max_tokens = 0;
|
||||
int max_tokens = 0;
|
||||
int has_temperature = 0;
|
||||
double temperature = 0.0;
|
||||
int has_seed = 0;
|
||||
int seed = 0;
|
||||
parse_trigger_runtime_tags(tags,
|
||||
&llm_s,
|
||||
&tools_s,
|
||||
&has_max_tokens,
|
||||
&max_tokens,
|
||||
&has_temperature,
|
||||
&temperature,
|
||||
&has_seed,
|
||||
&seed);
|
||||
|
||||
if (trigger_manager_add(mgr,
|
||||
d_tag,
|
||||
content->valuestring,
|
||||
filter_s,
|
||||
TRIGGER_ACTION_LLM,
|
||||
trigger_s,
|
||||
is_enabled,
|
||||
llm_s,
|
||||
tools_s,
|
||||
has_max_tokens,
|
||||
max_tokens,
|
||||
has_temperature,
|
||||
temperature,
|
||||
has_seed,
|
||||
seed) == 0) {
|
||||
loaded++;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(events);
|
||||
DEBUG_INFO("[didactyl] trigger reconcile complete: loaded=%d considered=%d skipped_not_adopted=%d skipped_disabled=%d",
|
||||
loaded, considered, skipped_not_adopted, skipped_disabled);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apply_trigger_runtime_to_llm_config(const active_trigger_t* t, llm_config_t* cfg) {
|
||||
if (!t || !cfg) return;
|
||||
|
||||
@@ -1078,7 +810,6 @@ int trigger_manager_init(trigger_manager_t* mgr, didactyl_config_t* cfg) {
|
||||
}
|
||||
|
||||
mgr->last_poll_at = 0;
|
||||
mgr->last_reconcile_at = 0;
|
||||
|
||||
DEBUG_INFO("[didactyl] trigger manager initialized (capacity=%d)", mgr->capacity);
|
||||
return 0;
|
||||
@@ -1091,19 +822,8 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
|
||||
int loaded = 0;
|
||||
int considered = 0;
|
||||
int skipped_not_adopted = 0;
|
||||
int skipped_disabled = 0;
|
||||
const int kinds[2] = {31123, 31124};
|
||||
|
||||
/* Load adopted d_tags from kind 10123 adoption list */
|
||||
char adopted_d_tags[TRIGGER_ADOPTED_D_TAGS_MAX][65];
|
||||
memset(adopted_d_tags, 0, sizeof(adopted_d_tags));
|
||||
int adopted_count = load_adopted_d_tags(mgr, adopted_d_tags, TRIGGER_ADOPTED_D_TAGS_MAX);
|
||||
if (adopted_count <= 0) {
|
||||
DEBUG_WARN("[didactyl] trigger load from skills: no adopted skills found — skipping all trigger registration");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int k = 0; k < 2; k++) {
|
||||
char* skill_json = nostr_handler_get_self_events_by_kind_json(kinds[k]);
|
||||
if (!skill_json) {
|
||||
@@ -1130,25 +850,17 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
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* 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";
|
||||
|
||||
considered++;
|
||||
if (!d_tag || d_tag[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Adoption gate: only register triggers for adopted skills */
|
||||
if (!is_d_tag_adopted(d_tag, adopted_d_tags, adopted_count)) {
|
||||
skipped_not_adopted++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int trigger_supported = trigger_s &&
|
||||
(strcmp(trigger_s, "nostr-subscription") == 0 ||
|
||||
strcmp(trigger_s, "webhook") == 0 ||
|
||||
@@ -1159,13 +871,6 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Enabled gate: skip skills explicitly disabled */
|
||||
int is_enabled = (strcmp(enabled_s, "false") == 0 || strcmp(enabled_s, "0") == 0) ? 0 : 1;
|
||||
if (!is_enabled) {
|
||||
skipped_disabled++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(action_s, "template") == 0) {
|
||||
DEBUG_WARN("[didactyl] trigger action template is deprecated; forcing llm for d_tag=%s", d_tag);
|
||||
}
|
||||
@@ -1194,7 +899,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
filter_s,
|
||||
TRIGGER_ACTION_LLM,
|
||||
trigger_s,
|
||||
is_enabled,
|
||||
1,
|
||||
llm_s,
|
||||
tools_s,
|
||||
has_max_tokens,
|
||||
@@ -1210,8 +915,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
|
||||
cJSON_Delete(skill_events);
|
||||
}
|
||||
|
||||
DEBUG_INFO("[didactyl] trigger manager loaded %d trigger(s) from self skills (considered=%d skipped_not_adopted=%d skipped_disabled=%d)",
|
||||
loaded, considered, skipped_not_adopted, skipped_disabled);
|
||||
DEBUG_INFO("[didactyl] trigger manager loaded %d trigger(s) from self skills (considered=%d)", loaded, considered);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1743,17 +1447,6 @@ int trigger_manager_poll(trigger_manager_t* mgr) {
|
||||
}
|
||||
pthread_mutex_unlock(&mgr->mutex);
|
||||
|
||||
/*
|
||||
* Temporarily disable periodic relay-backed trigger reconciliation.
|
||||
* Keep code path in place for quick re-enable after adoption/cache fix validation.
|
||||
*/
|
||||
int enable_periodic_reconcile = 0;
|
||||
if (enable_periodic_reconcile &&
|
||||
(mgr->last_reconcile_at <= 0 || (now - mgr->last_reconcile_at) >= 900)) {
|
||||
mgr->last_reconcile_at = now;
|
||||
(void)trigger_manager_reconcile_from_relays(mgr);
|
||||
}
|
||||
|
||||
return fired;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ typedef struct trigger_manager {
|
||||
didactyl_config_t* cfg;
|
||||
pthread_mutex_t mutex;
|
||||
time_t last_poll_at;
|
||||
time_t last_reconcile_at;
|
||||
} trigger_manager_t;
|
||||
|
||||
int trigger_manager_init(trigger_manager_t* mgr, didactyl_config_t* cfg);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"tasks":[{"id":9,"text":"Publish README.md as longform post","created_at":1773326251,"status":"done","updated_at":1773326287},{"id":10,"text":"Publish docs/CONTEXT.md as longform post","created_at":1773326251,"status":"done","updated_at":1773326310},{"id":11,"text":"Publish docs/TOOLS.md as longform post","created_at":1773326251,"status":"done","updated_at":1773326320},{"id":12,"text":"Publish docs/SUBSCRIPTIONS.md as longform post","created_at":1773326251,"status":"done","updated_at":1773327937},{"id":13,"text":"DM admin after each publish","created_at":1773326251,"status":"done","updated_at":1773327937},{"id":14,"text":"Return final summary","created_at":1773326251,"status":"done","updated_at":1773327937}],"next_id":15}
|
||||
@@ -1,70 +0,0 @@
|
||||
Didactyl - sovereign Nostr agent
|
||||
|
||||
Usage:
|
||||
./didactyl [options]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Show this help text and exit.
|
||||
-v, --version
|
||||
Show version and exit.
|
||||
--config <path>
|
||||
Path to config/genesis JSONC file (default: ./genesis.jsonc).
|
||||
If file is missing, startup falls back to minimal nsec-only mode.
|
||||
--nsec <nsec_or_hex>
|
||||
Private key as nsec1... or 64-char hex; overrides DIDACTYL_NSEC.
|
||||
--admin <npub_or_hex>
|
||||
Administrator pubkey override; accepts npub1... or 64-char hex.
|
||||
--api-port <port>
|
||||
Enable HTTP API and bind to this port (1-65535).
|
||||
--api-bind <addr>
|
||||
Enable HTTP API and bind address (example: 127.0.0.1).
|
||||
--debug <0-5>
|
||||
Log level (0=fatal, 1=error, 2=warn, 3=info, 4=debug, 5=trace).
|
||||
--signer <mode>
|
||||
Signer provider: local | nsigner_unix | nsigner_tcp.
|
||||
Overrides signer.mode in config and DIDACTYL_SIGNER.
|
||||
In nsigner_* modes --nsec/DIDACTYL_NSEC are not required.
|
||||
--signer-socket <name>
|
||||
Abstract socket name for nsigner_unix (without leading @).
|
||||
--signer-role <role>
|
||||
n_signer role selector (default: main).
|
||||
--signer-timeout <ms>
|
||||
Per-call signer timeout in milliseconds (default: 15000).
|
||||
--signer-tcp <host:port>
|
||||
Shorthand for --signer nsigner_tcp with host/port parsed.
|
||||
--dump-schemas
|
||||
Print tool schemas JSON and exit.
|
||||
--test-tool <name> <args_json>
|
||||
Execute one tool call locally and print JSON result, then exit.
|
||||
|
||||
Environment:
|
||||
DIDACTYL_NSEC
|
||||
Fallback private key when --nsec is not provided (local mode only).
|
||||
DIDACTYL_SIGNER
|
||||
Fallback signer mode when --signer is not provided.
|
||||
|
||||
Examples:
|
||||
1) Genesis-based startup
|
||||
./didactyl --config genesis.jsonc
|
||||
|
||||
2) nsec-only startup (no config file)
|
||||
./didactyl --nsec nsec1...
|
||||
|
||||
3) nsec via environment variable
|
||||
DIDACTYL_NSEC=nsec1... ./didactyl
|
||||
|
||||
4) Override API bind/port
|
||||
./didactyl --config genesis.jsonc --api-bind 127.0.0.1 --api-port 8484
|
||||
|
||||
5) Sign with a running n_signer (separate the nsec from the agent)
|
||||
./didactyl --signer nsigner_unix --signer-socket nsigner_hairy_dog --admin npub1...
|
||||
|
||||
6) n_signer via TCP
|
||||
./didactyl --signer-tcp 127.0.0.1:7777 --admin npub1...
|
||||
|
||||
7) Dump tool schemas
|
||||
./didactyl --dump-schemas
|
||||
|
||||
8) Test a tool invocation
|
||||
./didactyl --test-tool config_recall '{"config_name":"llm_config"}'
|
||||
@@ -7,6 +7,7 @@
|
||||
"admin": {
|
||||
"pubkey": "npub1REPLACE_WITH_TEST_ADMIN_PUBKEY"
|
||||
},
|
||||
"dm_protocol": "nip04",
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
@@ -20,13 +21,6 @@
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_TEST_ADMIN_PUBKEY\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"admin": {
|
||||
"pubkey": "npub1REPLACE_WITH_TEST_ADMIN_PUBKEY"
|
||||
},
|
||||
"dm_protocol": "nip04",
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"api_key": "sk-REPLACE_WITH_API_KEY",
|
||||
@@ -21,13 +22,6 @@
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"openai\",\"api_key\":\"sk-REPLACE_WITH_API_KEY\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.anthropic.com/v1\",\"max_tokens\":512,\"temperature\":0.3},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_TEST_ADMIN_PUBKEY\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
[2026-04-28 07:18:22] [INFO ] [main.c:1103] Didactyl v0.2.48 starting
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_TEST_ADMIN_PUBKEY\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[2026-04-28 07:18:40] [INFO ] [main.c:1103] Didactyl v0.2.48 starting
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_TEST_ADMIN_PUBKEY\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
[2026-04-28 07:27:53] [INFO ] [main.c:1103] Didactyl v0.2.48 starting
|
||||
[2026-04-28 07:27:53] [INFO ] [nostr_handler.c:2558] [didactyl] initializing relay pool with 2 relays
|
||||
[2026-04-28 07:27:53] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.damus.io
|
||||
[2026-04-28 07:27:53] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.primal.net
|
||||
[2026-04-28 07:27:53] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1777375673", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1777375673", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1777375673"]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1777375673"]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1777375673"]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1777375673"]
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:304] [didactyl] startup checklist [01] Relay connectivity: begin
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:311] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2)
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:304] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1777375674", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1777375674", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1047>
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1045>
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1777375674"]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1777375674"]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1777375674"]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1777375674"]
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:1007] [didactyl] startup phase: recovered user-settings from Nostr
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:311] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok (user-settings recalled)
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:304] [didactyl] startup checklist [03] Validate LLM config: begin
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:314] [didactyl] startup checklist [03] Validate LLM config: ok
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:304] [didactyl] startup checklist [04] Validate admin config: begin
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:314] [didactyl] startup checklist [04] Validate admin config: ok
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:304] [didactyl] startup checklist [05] Initialize LLM client: begin
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:314] [didactyl] startup checklist [05] Initialize LLM client: ok
|
||||
[2026-04-28 07:27:54] [INFO ] [main.c:304] [didactyl] startup checklist [06] Detect first run via kind 10002: begin
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1777375674", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1777375674", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1777375674"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1777375674"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1777375674"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1777375674"]
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1337] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:311] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run)
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [07] Reconcile/persist startup state: begin
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1777375675", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1777375675", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=480>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=a94194d5ba150931... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=474>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=e4d54d57a4d7180d... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=827>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=ecaba96e69d6b3ad... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=553>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b1ad6f867a2f34f8... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1657] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=482>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=a94194d5ba150931... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=476>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=e4d54d57a4d7180d... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=829>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=5b96f07025aa928e... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375675)
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=555>
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b1ad6f867a2f34f8... created_at=1777375675
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375675)
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:3676] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:937] [didactyl] startup phase: skipping user-settings publish because runtime config was recalled from Nostr
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1361] [didactyl] startup phase: persisted encrypted runtime config to Nostr
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:314] [didactyl] startup checklist [07] Reconcile/persist startup state: ok
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [08] Initialize agent: begin
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1777375675", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1777375675", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","a94194d5ba1509318a6b44eece02f47c20ada0a1bb2dd4fdfce7f23d48b9f81e",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","a94194d5ba1509318a6b44eece02f47c20ada0a1bb2dd4fdfce7f23d48b9f81e",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","e4d54d57a4d7180d8476a905e526e3b779a43f167493b67d066465b4eae1f7d9",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","e4d54d57a4d7180d8476a905e526e3b779a43f167493b67d066465b4eae1f7d9",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","5b96f07025aa928e3ca6221f74cfc5ea6292a382acbfac5cb6f4f3ec8786772e",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1777375675"]
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:314] [didactyl] startup checklist [08] Initialize agent: ok
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [09] Initialize trigger manager: begin
|
||||
[2026-04-28 07:27:55] [INFO ] [trigger_manager.c:935] [didactyl] trigger manager initialized (capacity=16)
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:314] [didactyl] startup checklist [09] Initialize trigger manager: ok
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1405] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [10] Load startup triggers: begin
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1408] [didactyl] startup phase: startup-config trigger load begin
|
||||
[2026-04-28 07:27:55] [INFO ] [trigger_manager.c:1259] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:27:55] [INFO ] [trigger_manager.c:1142] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:27:55] [INFO ] [trigger_manager.c:1150] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1)
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1412] [didactyl] startup phase: startup-config trigger load end
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:314] [didactyl] startup checklist [10] Load startup triggers: ok
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:383] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:390] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":4,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375673,"last_event_time":1777375675},{"url":"wss://relay.primal.net","status":"connected","events_received":2,"events_published":4,"events_published_ok":3,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375673,"last_event_time":1777375675}]}
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1777375675", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1777375675", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","b1ad6f867a2f34f881f5032b8e8ffe9c1472819822a4c32ba8f566cc2bcfaee6",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","b1ad6f867a2f34f881f5032b8e8ffe9c1472819822a4c32ba8f566cc2bcfaee6",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","ecaba96e69d6b3ad9bde94779fbdfdfcbbd112e6a3eb8dbf508859e9a9230b93",true,""]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1777375675"]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1777375675"]
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:410] [didactyl] kind10002 query received event_count=1
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:507] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"]
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:612] [didactyl] kind10002 wait success: relay_count=2
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:614] [didactyl] kind10002 relay[0]=wss://relay.damus.io
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:614] [didactyl] kind10002 relay[1]=wss://relay.primal.net
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:618] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375673,"last_event_time":1777375675},{"url":"wss://relay.primal.net","status":"connected","events_received":2,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375673,"last_event_time":1777375675}]}
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:311] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2)
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [12] Subscribe admin context: begin
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1463] [didactyl] startup phase: subscribe admin context begin
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1777375675", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1777375675", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1777375675", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1777375675", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:2703] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f...
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1467] [didactyl] startup phase: subscribe admin context end
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:314] [didactyl] startup checklist [12] Subscribe admin context: ok
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [13] Subscribe agent self context: begin
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1471] [didactyl] startup phase: subscribe agent self context begin
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1777375675", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1777375675", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1777375675", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1777375675", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:2791] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1475] [didactyl] startup phase: subscribe agent self context end
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:314] [didactyl] startup checklist [13] Subscribe agent self context: ok
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:304] [didactyl] startup checklist [14] Subscribe self-skill cache: begin
|
||||
[2026-04-28 07:27:55] [INFO ] [main.c:1479] [didactyl] startup phase: subscribe self skill cache begin
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:2827] [didactyl] self-skill subscription filter: {"kinds":[31123,31124,10123],"authors":["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],"limit":300}
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1777375675", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1777375675", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1777375675", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:27:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1777375675", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:27:55] [INFO ] [nostr_handler.c:2887] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...)
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=471>
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=469>
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=540>
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=b1ad6f867a2f34f881f5032b8e8ffe9c1472819822a4c32ba8f566cc2bcfaee6 created_at=1777375675 via wss://relay.primal.net
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b1ad6f867a2f34f8... created_at=1777375675
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375675)
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=816>
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=ecaba96e69d6b3ad9bde94779fbdfdfcbbd112e6a3eb8dbf508859e9a9230b93 created_at=1777375675 via wss://relay.damus.io
|
||||
[2026-04-28 07:27:56] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=ecaba96e69d6b3ad... created_at=1777375675
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375675)
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=818>
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=5b96f07025aa928e3ca6221f74cfc5ea6292a382acbfac5cb6f4f3ec8786772e created_at=1777375675 via wss://relay.primal.net
|
||||
[2026-04-28 07:27:56] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=5b96f07025aa928e... created_at=1777375675
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375675)
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=538>
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=b1ad6f867a2f34f881f5032b8e8ffe9c1472819822a4c32ba8f566cc2bcfaee6 created_at=1777375675 via wss://relay.damus.io
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b1ad6f867a2f34f8... created_at=1777375675
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375675)
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1777375675"]
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:2070] [didactyl] self-skill EOSE received: cached skill events=0
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:47] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0)
|
||||
[2026-04-28 07:27:56] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:27:56] [INFO ] [trigger_manager.c:1039] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1)
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:57] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]}
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1777375675"]
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=5b96f07025aa928e3ca6221f74cfc5ea6292a382acbfac5cb6f4f3ec8786772e created_at=1777375675
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=10123 d_tag=- id=b1ad6f867a2f34f881f5032b8e8ffe9c1472819822a4c32ba8f566cc2bcfaee6 created_at=1777375675
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1546] [didactyl] startup phase: subscribe self skill cache end
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:311] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1)
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:304] [didactyl] startup checklist [15] Subscribe DMs: begin
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1550] [didactyl] startup phase: subscribe DMs begin
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:2905] [didactyl] DM subscribe: dm_protocol=2 need_kind4=1 need_kind1059=1 g_start_time=1777375673
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1777375676", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375673,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1777375676", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375673,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1777375676", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1777375676", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:3000] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:3001] [didactyl] DEBUG DM subscription g_start_time=1777375673 now=1777375676 delta=3 relay_count=2
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1565] [didactyl] startup phase: subscribe DMs end
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:314] [didactyl] startup checklist [15] Subscribe DMs: ok
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:304] [didactyl] startup checklist [16] Subscribe wallet events: begin
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1569] [didactyl] startup phase: subscribe wallet events begin
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1777375676", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375673,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1777375676", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375673,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:3053] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1573] [didactyl] startup phase: subscribe wallet events end
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:314] [didactyl] startup checklist [16] Subscribe wallet events: ok
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:304] [didactyl] startup checklist [17] Initialize cashu wallet: begin
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_16_1777375676", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_16_1777375676", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1777375675"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_16_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_16_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_16_1777375676"]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_16_1777375676"]
|
||||
[2026-04-28 07:27:56] [WARN ] [main.c:1589] [didactyl] startup phase: cashu wallet load/create deferred to first tool call
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:311] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred)
|
||||
[2026-04-28 07:27:56] [INFO ] [http_api.c:1662] [didactyl] http api listening on http://127.0.0.1:8485
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1629] [didactyl] HTTP API listening at http://127.0.0.1:8485
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1632] [didactyl] HTTP API endpoints: http://127.0.0.1:8485/api/context/current http://127.0.0.1:8485/api/context/parts
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:1844] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-28 07:27:56 (version v0.2.48, connected relays: 2/2).
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:1836] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1777375676,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"4M2CHJPV80ujTCckvsFEbZXfDAoYlTt95OJ36vFCVNktDiLEcugD1i75WDGY+nH/kmYQY4E4GOYnBQ9cRPc9z5K4URJa4NomXGQaLpyjVWqKpVd557s6t2HA/9mT5UiohdkfLEcv1fhFWHLlQZdHV1AfEklYwP9D7ZuOdobShsU=?iv=bjxvVALQI+GlATrv3iurcw==","id":"2a1530bcf8fb2b5e63022e96cd369b88ad302187179ed892cc146e769c7ebd15","sig":"f63c084fd0bf131f952c934d0e8ced20a6bda170e4d43c5c40ee9111337ec8748caf65434fd0937bf2463d5681e2ada89514d80f97f0e688126cefae163628d6"}
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:920] [didactyl] publish DM target relays (2):
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.damus.io
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.primal.net
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=681>
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=683>
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.damus.io (async)
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.primal.net (async)
|
||||
[2026-04-28 07:27:56] [INFO ] [nostr_handler.c:3209] [didactyl] sent DM 2a1530bcf8fb2b5e... to 254d7a7f68b4443f... via 2 connected relay(s)
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:311] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop)
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1668] [didactyl] entering main poll loop
|
||||
[2026-04-28 07:27:56] [INFO ] [main.c:1669] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","2a1530bcf8fb2b5e63022e96cd369b88ad302187179ed892cc146e769c7ebd15",true,""]
|
||||
[2026-04-28 07:27:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","2a1530bcf8fb2b5e63022e96cd369b88ad302187179ed892cc146e769c7ebd15",true,""]
|
||||
[2026-04-28 07:27:56] [INFO ] [llm.c:125] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=30991 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"Save test harness probe to memory, then recall your memory"},{"role":"user","content":"Save test harness probe to memory, then recall your memory","_ts":1777375676}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}}},{"type":"function","function":{"name":"nostr_delete","description":"Request deletion of one or more previously published events (NIP-09 kind 5)","parameters":{"type":"object","properties":{"event_ids":{"type":"array","description":"Event IDs to request deletion for","items":{"type":"string"}},"kinds":{"type":"array","description":"Optional kinds corresponding to event_ids entries","items":{"type":"integer"}},"reason":{"type":"string","description":"Optional human-readable reason"}},"required":["event_ids"]}}},{"type":"function","function":{"name":"nostr_react","description":"React to a Nostr event with like/dislike/emoji (NIP-25 kind 7)","parameters":{"type":"object","properties":{"event_id":{"type":"string","description":"Target event ID to react to"},"event_pubkey":{"type":"string","description":"Author pubkey of target event"},"event_kind":{"type":"integer","description":"Optional kind of target event"},"reaction":{"type":"string","description":"Reaction content such as +, -, ❤️, or emoji"}},"required":["event_id","event_pubkey"]}}},{"type":"function","function":{"name":"nostr_profile_get","description":"Look up a Nostr profile (kind 0 metadata) by pubkey","parameters":{"type":"object","properties":{"pubkey":{"type":"string","desc...
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:920] [didactyl] publish kind event target relays (2):
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.damus.io
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.primal.net
|
||||
[2026-04-28 07:27:59] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=836>
|
||||
[2026-04-28 07:27:59] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=838>
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:3323] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish)
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:3323] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish)
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=30078 author=1eb171136dbb1ba6... id=6311c65cf9b52fe1... created_at=1777375679
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:27:59] [INFO ] [nostr_handler.c:3391] [didactyl] published kind 30078 event via 2 connected relay(s)
|
||||
[2026-04-28 07:27:59] [INFO ] [llm.c:125] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=31689 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"Save test harness probe to memory, then recall your memory"},{"role":"user","content":"Save test harness probe to memory, then recall your memory","_ts":1777375676},{"role":"assistant","content":"I'll save a test harness probe to memory and then recall it.","tool_calls":[{"id":"toolu_bdrk_01GBGt8MaQjzZj4i23ij5caL","type":"function","function":{"name":"memory_save","arguments":"{\"content\": \"Test harness probe - system verification checkpoint. Agent memory subsystem operational. Timestamp: current session. This entry confirms memory_save and memory_recall functions are working correctly.\"}"}}]},{"role":"tool","tool_call_id":"toolu_bdrk_01GBGt8MaQjzZj4i23ij5caL","content":"{\"success\":true,\"d_tag\":\"memory\",\"kind\":30078,\"entry_length\":199,\"truncated\":false,\"event_id\":\"6311c65cf9b52fe1fe9bce8052a09f594d6b3dc1f83f8030f45106b8b3a108e2\"}"}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}}},{"type":"function","function":{"name":"nostr_delete","description":"Request deletion of one or more previously published events (NIP-09 kind 5)","parameters":{"type":"object","properties":{"event_ids":{"type":"array","description":"Event IDs to request deletion for","items":{"type":"string"}},"kinds":{"type":"array","description":"Optional kinds corresponding to event_ids entries","items":{"type":"integer"}},"reason":{"type":"string","description":"Optional human-readable reason"}},"required":["event_ids"]}}},{"type":"function","function":{"name":"nostr...
|
||||
[2026-04-28 07:28:02] [INFO ] [llm.c:125] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=32229 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"Save test harness probe to memory, then recall your memory"},{"role":"user","content":"Save test harness probe to memory, then recall your memory","_ts":1777375676},{"role":"assistant","content":"I'll save a test harness probe to memory and then recall it.","tool_calls":[{"id":"toolu_bdrk_01GBGt8MaQjzZj4i23ij5caL","type":"function","function":{"name":"memory_save","arguments":"{\"content\": \"Test harness probe - system verification checkpoint. Agent memory subsystem operational. Timestamp: current session. This entry confirms memory_save and memory_recall functions are working correctly.\"}"}}]},{"role":"tool","tool_call_id":"toolu_bdrk_01GBGt8MaQjzZj4i23ij5caL","content":"{\"success\":true,\"d_tag\":\"memory\",\"kind\":30078,\"entry_length\":199,\"truncated\":false,\"event_id\":\"6311c65cf9b52fe1fe9bce8052a09f594d6b3dc1f83f8030f45106b8b3a108e2\"}"},{"role":"assistant","content":"Great! Now let me recall the memory:","tool_calls":[{"id":"toolu_bdrk_01QgBARiRY5FTx8TJH3GhzU6","type":"function","function":{"name":"memory_recall","arguments":"{}"}}]},{"role":"tool","tool_call_id":"toolu_bdrk_01QgBARiRY5FTx8TJH3GhzU6","content":"{\"success\":true,\"content\":\"Test harness probe - system verification checkpoint. Agent memory subsystem operational. Timestamp: current session. This entry confirms memory_save and memory_recall functions are working correctly.\",\"content_length\":199}"}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}}},{"type":"function",...
|
||||
[2026-04-28 07:28:19] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","6311c65cf9b52fe1fe9bce8052a09f594d6b3dc1f83f8030f45106b8b3a108e2",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:28:19] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","6311c65cf9b52fe1fe9bce8052a09f594d6b3dc1f83f8030f45106b8b3a108e2",true,""]
|
||||
[2026-04-28 07:28:25] [INFO ] [main.c:1691] [didactyl] shutting down
|
||||
[2026-04-28 07:28:25] [INFO ] [trigger_manager.c:1734] [didactyl] trigger manager cleaned up
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1777375675"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1777375676"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1777375676"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1777375676"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1777375676"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1777375676"]
|
||||
[2026-04-28 07:28:25] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1777375676"]
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
[2026-04-28 07:28:25] [INFO ] [main.c:1103] Didactyl v0.2.48 starting
|
||||
[2026-04-28 07:28:25] [INFO ] [nostr_handler.c:2558] [didactyl] initializing relay pool with 2 relays
|
||||
[2026-04-28 07:28:25] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.damus.io
|
||||
[2026-04-28 07:28:25] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.primal.net
|
||||
[2026-04-28 07:28:26] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1777375705", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:28:26] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1777375705"]
|
||||
[2026-04-28 07:28:26] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1777375705"]
|
||||
[2026-04-28 07:28:26] [INFO ] [main.c:304] [didactyl] startup checklist [01] Relay connectivity: begin
|
||||
[2026-04-28 07:28:26] [INFO ] [main.c:311] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 1/2)
|
||||
[2026-04-28 07:28:26] [INFO ] [main.c:304] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1777375706", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1047>
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1777375706"]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1777375706"]
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:1007] [didactyl] startup phase: recovered user-settings from Nostr
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:311] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok (user-settings recalled)
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:304] [didactyl] startup checklist [03] Validate LLM config: begin
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:314] [didactyl] startup checklist [03] Validate LLM config: ok
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:304] [didactyl] startup checklist [04] Validate admin config: begin
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:314] [didactyl] startup checklist [04] Validate admin config: ok
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:304] [didactyl] startup checklist [05] Initialize LLM client: begin
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:314] [didactyl] startup checklist [05] Initialize LLM client: ok
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:304] [didactyl] startup checklist [06] Detect first run via kind 10002: begin
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1777375707", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1777375707", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1777375707"]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1777375707"]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1777375707"]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1777375707"]
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:1337] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:311] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run)
|
||||
[2026-04-28 07:28:27] [INFO ] [main.c:304] [didactyl] startup checklist [07] Reconcile/persist startup state: begin
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1777375707", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1777375707", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1777375707"]
|
||||
[2026-04-28 07:28:27] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1777375707"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1777375707"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1777375707"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=480>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=d19395f4a4826b36... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=474>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=1bdbec7b70533806... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=827>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=7ddb16d25587f2d8... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=553>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=3940625df170eddc... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1657] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=482>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=d19395f4a4826b36... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=476>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=1bdbec7b70533806... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=829>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=1ab607d36b07b8a7... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375708)
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=555>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=3940625df170eddc... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375708)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3676] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:937] [didactyl] startup phase: skipping user-settings publish because runtime config was recalled from Nostr
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1361] [didactyl] startup phase: persisted encrypted runtime config to Nostr
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [07] Reconcile/persist startup state: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [08] Initialize agent: begin
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1777375708", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1777375708", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","1bdbec7b70533806ccad4e1224cd17aca8dedbc200499dc70687716f08eb6009",true,""]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","1bdbec7b70533806ccad4e1224cd17aca8dedbc200499dc70687716f08eb6009",true,""]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","d19395f4a4826b36b58d3d2c71b993233615df0d613eea7cc6e6b958d8ea6c12",true,""]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","d19395f4a4826b36b58d3d2c71b993233615df0d613eea7cc6e6b958d8ea6c12",true,""]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=824>
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1777375708"]
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [08] Initialize agent: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [09] Initialize trigger manager: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:935] [didactyl] trigger manager initialized (capacity=16)
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [09] Initialize trigger manager: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1405] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [10] Load startup triggers: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1408] [didactyl] startup phase: startup-config trigger load begin
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:1259] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:1142] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:1150] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1)
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1412] [didactyl] startup phase: startup-config trigger load end
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [10] Load startup triggers: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:383] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:390] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":4,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375705,"last_event_time":1777375708,"last_connection_error":"Failed to create WebSocket client for relay wss://relay.damus.io"},{"url":"wss://relay.primal.net","status":"connected","events_received":3,"events_published":4,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375705,"last_event_time":1777375708}]}
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1777375708", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1777375708", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","7ddb16d25587f2d80a59fd576141a8baec5a30b245969305fbe1628c42e73b2b",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","1ab607d36b07b8a7f7afc54319e3426754f22f8676829b3c8b7a5e50b21d7222",true,""]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","3940625df170eddc0770d0b334a1759e945d3261af94fa15c21f48fe0e6d5c90",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","3940625df170eddc0770d0b334a1759e945d3261af94fa15c21f48fe0e6d5c90",true,""]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1777375708"]
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:410] [didactyl] kind10002 query received event_count=1
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:507] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"]
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:612] [didactyl] kind10002 wait success: relay_count=2
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:614] [didactyl] kind10002 relay[0]=wss://relay.damus.io
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:614] [didactyl] kind10002 relay[1]=wss://relay.primal.net
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:618] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":4,"events_published_ok":2,"events_published_failed":2,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375705,"last_event_time":1777375708,"last_publish_error":"rate-limited: you are noting too much","last_connection_error":"Failed to create WebSocket client for relay wss://relay.damus.io"},{"url":"wss://relay.primal.net","status":"connected","events_received":4,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375705,"last_event_time":1777375708}]}
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:311] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2)
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [12] Subscribe admin context: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1463] [didactyl] startup phase: subscribe admin context begin
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1777375708", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1777375708", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1777375708", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1777375708", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2703] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f...
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1467] [didactyl] startup phase: subscribe admin context end
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [12] Subscribe admin context: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [13] Subscribe agent self context: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1471] [didactyl] startup phase: subscribe agent self context begin
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1777375708", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1777375708", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1777375708", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1777375708", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2791] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1475] [didactyl] startup phase: subscribe agent self context end
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [13] Subscribe agent self context: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [14] Subscribe self-skill cache: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1479] [didactyl] startup phase: subscribe self skill cache begin
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2827] [didactyl] self-skill subscription filter: {"kinds":[31123,31124,10123],"authors":["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],"limit":300}
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1777375708", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1777375708", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1777375708", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1777375708", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2887] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...)
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=469>
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=471>
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=816>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=ecaba96e69d6b3ad9bde94779fbdfdfcbbd112e6a3eb8dbf508859e9a9230b93 created_at=1777375675 via wss://relay.damus.io
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=ecaba96e69d6b3ad... created_at=1777375675
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1609] [didactyl] self-skill upsert rejected: stale (existing created_at=1777375708 >= new created_at=1777375675)
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=540>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=3940625df170eddc0770d0b334a1759e945d3261af94fa15c21f48fe0e6d5c90 created_at=1777375708 via wss://relay.primal.net
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=3940625df170eddc... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375708)
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=538>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=b1ad6f867a2f34f881f5032b8e8ffe9c1472819822a4c32ba8f566cc2bcfaee6 created_at=1777375675 via wss://relay.damus.io
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b1ad6f867a2f34f8... created_at=1777375675
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1609] [didactyl] self-skill upsert rejected: stale (existing created_at=1777375708 >= new created_at=1777375675)
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=818>
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=1ab607d36b07b8a7f7afc54319e3426754f22f8676829b3c8b7a5e50b21d7222 created_at=1777375708 via wss://relay.primal.net
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=1ab607d36b07b8a7... created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375708)
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1777375708"]
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2070] [didactyl] self-skill EOSE received: cached skill events=0
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:47] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0)
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:28:28] [INFO ] [trigger_manager.c:1039] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1)
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:57] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]}
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=1ab607d36b07b8a7f7afc54319e3426754f22f8676829b3c8b7a5e50b21d7222 created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=10123 d_tag=- id=3940625df170eddc0770d0b334a1759e945d3261af94fa15c21f48fe0e6d5c90 created_at=1777375708
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1546] [didactyl] startup phase: subscribe self skill cache end
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:311] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1)
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [15] Subscribe DMs: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1550] [didactyl] startup phase: subscribe DMs begin
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:2905] [didactyl] DM subscribe: dm_protocol=2 need_kind4=1 need_kind1059=1 g_start_time=1777375705
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1777375708", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375705,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1777375708", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375705,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1777375708", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1777375708", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3000] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:3001] [didactyl] DEBUG DM subscription g_start_time=1777375705 now=1777375708 delta=3 relay_count=2
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1565] [didactyl] startup phase: subscribe DMs end
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [15] Subscribe DMs: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [16] Subscribe wallet events: begin
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1569] [didactyl] startup phase: subscribe wallet events begin
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1777375708", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375705,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1777375708", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375705,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:28:28] [INFO ] [nostr_handler.c:3053] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:1573] [didactyl] startup phase: subscribe wallet events end
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:314] [didactyl] startup checklist [16] Subscribe wallet events: ok
|
||||
[2026-04-28 07:28:28] [INFO ] [main.c:304] [didactyl] startup checklist [17] Initialize cashu wallet: begin
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_16_1777375708", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_16_1777375708", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_16_1777375708"]
|
||||
[2026-04-28 07:28:28] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_16_1777375708"]
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_16_1777375708"]
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_16_1777375708"]
|
||||
[2026-04-28 07:28:29] [WARN ] [main.c:1589] [didactyl] startup phase: cashu wallet load/create deferred to first tool call
|
||||
[2026-04-28 07:28:29] [INFO ] [main.c:311] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred)
|
||||
[2026-04-28 07:28:29] [INFO ] [http_api.c:1662] [didactyl] http api listening on http://127.0.0.1:8485
|
||||
[2026-04-28 07:28:29] [INFO ] [main.c:1629] [didactyl] HTTP API listening at http://127.0.0.1:8485
|
||||
[2026-04-28 07:28:29] [INFO ] [main.c:1632] [didactyl] HTTP API endpoints: http://127.0.0.1:8485/api/context/current http://127.0.0.1:8485/api/context/parts
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:1844] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-28 07:28:29 (version v0.2.48, connected relays: 2/2).
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:1836] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1777375709,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"HQoPu71Z+V2fjBIgRpwDhDnJjzRc840nyhTNrgx3SpIqnEAueVKmRDeHzZMmaFSBbi9/Q774ZyLE80+XWqLI0OKjIReusdnzOQyZNi7939Fx+jZeTCuJHPKdLHw2gaghM23iPD9/5KdCwTzeXTfwX4jm8qpNGgkieacGigkdzjs=?iv=s3i0OGmiQdnmil6/6vtT4A==","id":"a0da6a4e741285abaec758df98b67b24cf87b4395ce4b13429276711809db95c","sig":"d8721548e9353ad3ca007becc37a8f84bf7426e8f6bfbcf9ba1d18598390af723c447f45f90e63cb6c8d1fac897c051a128293e589e60aad3a47035fa1e417b4"}
|
||||
[2026-04-28 07:28:29] [INFO ] [nostr_handler.c:920] [didactyl] publish DM target relays (2):
|
||||
[2026-04-28 07:28:29] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.damus.io
|
||||
[2026-04-28 07:28:29] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.primal.net
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=681>
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=683>
|
||||
[2026-04-28 07:28:29] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.damus.io (async)
|
||||
[2026-04-28 07:28:29] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.primal.net (async)
|
||||
[2026-04-28 07:28:29] [INFO ] [nostr_handler.c:3209] [didactyl] sent DM a0da6a4e741285ab... to 254d7a7f68b4443f... via 2 connected relay(s)
|
||||
[2026-04-28 07:28:29] [INFO ] [main.c:311] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop)
|
||||
[2026-04-28 07:28:29] [INFO ] [main.c:1668] [didactyl] entering main poll loop
|
||||
[2026-04-28 07:28:29] [INFO ] [main.c:1669] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","a0da6a4e741285abaec758df98b67b24cf87b4395ce4b13429276711809db95c",true,""]
|
||||
[2026-04-28 07:28:29] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","a0da6a4e741285abaec758df98b67b24cf87b4395ce4b13429276711809db95c",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:28:30] [INFO ] [main.c:1691] [didactyl] shutting down
|
||||
[2026-04-28 07:28:30] [INFO ] [trigger_manager.c:1734] [didactyl] trigger manager cleaned up
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1777375708"]
|
||||
[2026-04-28 07:28:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1777375708"]
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"run_meta": {
|
||||
"run_timestamp": "2026-04-28T11:28:30.755354+00:00",
|
||||
"base_url": "http://127.0.0.1:8485",
|
||||
"config": "tests/results/20260428T112825Z/runtime_test_genesis.jsonc",
|
||||
"binary": "didactyl",
|
||||
"test_count": 4
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "status_returns_200",
|
||||
"status": "pass",
|
||||
"message": "status success",
|
||||
"duration_seconds": 0.329037634015549,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"keys": [
|
||||
"success",
|
||||
"name",
|
||||
"version",
|
||||
"pubkey",
|
||||
"relay_count",
|
||||
"connected_relays",
|
||||
"active_triggers"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "status_has_fields",
|
||||
"status": "pass",
|
||||
"message": "required fields present",
|
||||
"duration_seconds": 0.3310440799978096,
|
||||
"agent_errors": [],
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "context_current_returns_messages",
|
||||
"status": "pass",
|
||||
"message": "context current ok",
|
||||
"duration_seconds": 0.3324761890107766,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"message_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "context_parts_has_system_prompt",
|
||||
"status": "pass",
|
||||
"message": "context parts ok",
|
||||
"duration_seconds": 0.3313834259752184,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"parts": [
|
||||
"system_prompt",
|
||||
"dm_history"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"pass": 4
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
== Didactyl Test Results ==
|
||||
Run: 2026-04-28T11:28:30.755354+00:00
|
||||
Agent base URL: http://127.0.0.1:8485
|
||||
|
||||
Pass: 4
|
||||
Fail: 0
|
||||
Error: 0
|
||||
Timeout: 0
|
||||
Skip: 0
|
||||
Total: 4
|
||||
|
||||
[PASS] test_health::status_returns_200 (0.33s)
|
||||
status success
|
||||
|
||||
[PASS] test_health::status_has_fields (0.33s)
|
||||
required fields present
|
||||
|
||||
[PASS] test_health::context_current_returns_messages (0.33s)
|
||||
context current ok
|
||||
|
||||
[PASS] test_health::context_parts_has_system_prompt (0.33s)
|
||||
context parts ok
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,421 +0,0 @@
|
||||
[2026-04-28 07:29:45] [INFO ] [main.c:1103] Didactyl v0.2.48 starting
|
||||
[2026-04-28 07:29:45] [INFO ] [nostr_handler.c:2558] [didactyl] initializing relay pool with 2 relays
|
||||
[2026-04-28 07:29:45] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.damus.io
|
||||
[2026-04-28 07:29:45] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.primal.net
|
||||
[2026-04-28 07:29:46] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1777375785", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:46] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1777375785", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:46] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1777375785"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1777375785"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1777375785"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1777375785"]
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [01] Relay connectivity: begin
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:311] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2)
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1777375787", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1777375787", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1047>
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1045>
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1777375787"]
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:1007] [didactyl] startup phase: recovered user-settings from Nostr
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:311] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok (user-settings recalled)
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [03] Validate LLM config: begin
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:314] [didactyl] startup checklist [03] Validate LLM config: ok
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [04] Validate admin config: begin
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:314] [didactyl] startup checklist [04] Validate admin config: ok
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [05] Initialize LLM client: begin
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:314] [didactyl] startup checklist [05] Initialize LLM client: ok
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [06] Detect first run via kind 10002: begin
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1777375787", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1777375787", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1777375787"]
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:1337] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:311] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run)
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [07] Reconcile/persist startup state: begin
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1777375787", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1777375787", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1777375787"]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=480>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=addbd253f977c8de... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=474>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=ff65d5864097fe6c... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=827>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=78b0aaddaf99b7dc... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=553>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=dc138a7c573943b8... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1657] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=482>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=addbd253f977c8de... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=476>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=ff65d5864097fe6c... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=829>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=63ffd51f9c14691d... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375787)
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=555>
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=dc138a7c573943b8... created_at=1777375787
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375787)
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:29:47] [INFO ] [nostr_handler.c:3676] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:937] [didactyl] startup phase: skipping user-settings publish because runtime config was recalled from Nostr
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:1361] [didactyl] startup phase: persisted encrypted runtime config to Nostr
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:314] [didactyl] startup checklist [07] Reconcile/persist startup state: ok
|
||||
[2026-04-28 07:29:47] [INFO ] [main.c:304] [didactyl] startup checklist [08] Initialize agent: begin
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1777375787", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:47] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1777375787", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=824>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","addbd253f977c8de882830e3029a78eea0ffa736bbfbeaecae7c5ac65fb9a2ec",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1777375787"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","ff65d5864097fe6cbffd211784f62ce93e67bad0ff5a6032a1fc8488b227009a",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","63ffd51f9c14691d8496ed68da75fdad71a6bd6f6a5669d023fd8dc0ad6771af",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1777375787"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","addbd253f977c8de882830e3029a78eea0ffa736bbfbeaecae7c5ac65fb9a2ec",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1777375787"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1777375787"]
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:314] [didactyl] startup checklist [08] Initialize agent: ok
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [09] Initialize trigger manager: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:935] [didactyl] trigger manager initialized (capacity=16)
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:314] [didactyl] startup checklist [09] Initialize trigger manager: ok
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1405] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [10] Load startup triggers: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1408] [didactyl] startup phase: startup-config trigger load begin
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:1259] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:1142] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:1150] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1)
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1412] [didactyl] startup phase: startup-config trigger load end
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:314] [didactyl] startup checklist [10] Load startup triggers: ok
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:383] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:390] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":4,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375785,"last_event_time":1777375788},{"url":"wss://relay.primal.net","status":"connected","events_received":3,"events_published":4,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375785,"last_event_time":1777375788}]}
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1777375788", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1777375788", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","78b0aaddaf99b7dc66a8094c036551a416542ef87e7701ef53a4efbfe5f2d85d",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","dc138a7c573943b81100e3d41471840a3bfa6bc662809fc30b47af87276aa54b",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","dc138a7c573943b81100e3d41471840a3bfa6bc662809fc30b47af87276aa54b",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","ff65d5864097fe6cbffd211784f62ce93e67bad0ff5a6032a1fc8488b227009a",true,""]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1777375788"]
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:410] [didactyl] kind10002 query received event_count=1
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:507] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"]
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:612] [didactyl] kind10002 wait success: relay_count=2
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:614] [didactyl] kind10002 relay[0]=wss://relay.damus.io
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:614] [didactyl] kind10002 relay[1]=wss://relay.primal.net
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:618] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375785,"last_event_time":1777375788},{"url":"wss://relay.primal.net","status":"connected","events_received":4,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375785,"last_event_time":1777375788}]}
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:311] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2)
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [12] Subscribe admin context: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1463] [didactyl] startup phase: subscribe admin context begin
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1777375788", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1777375788", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1777375788", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1777375788", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2703] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f...
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1467] [didactyl] startup phase: subscribe admin context end
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:314] [didactyl] startup checklist [12] Subscribe admin context: ok
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [13] Subscribe agent self context: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1471] [didactyl] startup phase: subscribe agent self context begin
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1777375788", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1777375788", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1777375788", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1777375788", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2791] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1475] [didactyl] startup phase: subscribe agent self context end
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:314] [didactyl] startup checklist [13] Subscribe agent self context: ok
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [14] Subscribe self-skill cache: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1479] [didactyl] startup phase: subscribe self skill cache begin
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2827] [didactyl] self-skill subscription filter: {"kinds":[31123,31124,10123],"authors":["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],"limit":300}
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1777375788", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1777375788", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1777375788", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1777375788", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2887] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...)
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=469>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=471>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=538>
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=dc138a7c573943b81100e3d41471840a3bfa6bc662809fc30b47af87276aa54b created_at=1777375787 via wss://relay.damus.io
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=dc138a7c573943b8... created_at=1777375787
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375787)
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=540>
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=dc138a7c573943b81100e3d41471840a3bfa6bc662809fc30b47af87276aa54b created_at=1777375787 via wss://relay.primal.net
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=dc138a7c573943b8... created_at=1777375787
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375787)
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=816>
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=78b0aaddaf99b7dc66a8094c036551a416542ef87e7701ef53a4efbfe5f2d85d created_at=1777375787 via wss://relay.damus.io
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=78b0aaddaf99b7dc... created_at=1777375787
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375787)
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=818>
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=63ffd51f9c14691d8496ed68da75fdad71a6bd6f6a5669d023fd8dc0ad6771af created_at=1777375787 via wss://relay.primal.net
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=63ffd51f9c14691d... created_at=1777375787
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375787)
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1777375788"]
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2070] [didactyl] self-skill EOSE received: cached skill events=0
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:47] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0)
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:29:48] [INFO ] [trigger_manager.c:1039] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1)
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:57] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]}
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=63ffd51f9c14691d8496ed68da75fdad71a6bd6f6a5669d023fd8dc0ad6771af created_at=1777375787
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=10123 d_tag=- id=dc138a7c573943b81100e3d41471840a3bfa6bc662809fc30b47af87276aa54b created_at=1777375787
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1546] [didactyl] startup phase: subscribe self skill cache end
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:311] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1)
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [15] Subscribe DMs: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1550] [didactyl] startup phase: subscribe DMs begin
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:2905] [didactyl] DM subscribe: dm_protocol=2 need_kind4=1 need_kind1059=1 g_start_time=1777375785
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1777375788", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375785,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1777375788", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375785,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1777375788", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1777375788", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:3000] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:3001] [didactyl] DEBUG DM subscription g_start_time=1777375785 now=1777375788 delta=3 relay_count=2
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1565] [didactyl] startup phase: subscribe DMs end
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:314] [didactyl] startup checklist [15] Subscribe DMs: ok
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [16] Subscribe wallet events: begin
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1569] [didactyl] startup phase: subscribe wallet events begin
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1777375788", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375785,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1777375788", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375785,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:3053] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1573] [didactyl] startup phase: subscribe wallet events end
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:314] [didactyl] startup checklist [16] Subscribe wallet events: ok
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:304] [didactyl] startup checklist [17] Initialize cashu wallet: begin
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_16_1777375788", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_16_1777375788", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_16_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_16_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_16_1777375788"]
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_16_1777375788"]
|
||||
[2026-04-28 07:29:48] [WARN ] [main.c:1589] [didactyl] startup phase: cashu wallet load/create deferred to first tool call
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:311] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred)
|
||||
[2026-04-28 07:29:48] [INFO ] [http_api.c:1662] [didactyl] http api listening on http://127.0.0.1:8491
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1629] [didactyl] HTTP API listening at http://127.0.0.1:8491
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1632] [didactyl] HTTP API endpoints: http://127.0.0.1:8491/api/context/current http://127.0.0.1:8491/api/context/parts
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:1844] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-28 07:29:48 (version v0.2.48, connected relays: 2/2).
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:1836] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1777375788,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"LFGqdsk1HPY08BtLcfL6tfxlVqeG7laUblZvMP2ailVXIf8bOJPtjI+tZQnQTQp1uRhsxDDFfKZCaPaXB8KHwfjLMWiDdqU1TAFginaVtzmaR17KVPY3p3+id+gwjbNhdPVIHx+jBqAf54ER/H3wB8gyg35o1D4o3UH6fhPIvPM=?iv=69XXqTTdJgGC48Ivw3NUgw==","id":"4d4b2b6f443460a14310cf37d239f9c6ffb49499029fe5c32508278e5ce9367c","sig":"373445bfd668a547d25f59929f9dd1a624847ecdce579b430f4814926afb0d48ce42397a227cece59386bf6e5011132c2130c3aa397ce2cdb14b9614e6514c50"}
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:920] [didactyl] publish DM target relays (2):
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.damus.io
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.primal.net
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=681>
|
||||
[2026-04-28 07:29:48] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=683>
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.damus.io (async)
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.primal.net (async)
|
||||
[2026-04-28 07:29:48] [INFO ] [nostr_handler.c:3209] [didactyl] sent DM 4d4b2b6f443460a1... to 254d7a7f68b4443f... via 2 connected relay(s)
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:311] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop)
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1668] [didactyl] entering main poll loop
|
||||
[2026-04-28 07:29:48] [INFO ] [main.c:1669] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc
|
||||
[2026-04-28 07:29:49] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","4d4b2b6f443460a14310cf37d239f9c6ffb49499029fe5c32508278e5ce9367c",true,""]
|
||||
[2026-04-28 07:29:49] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","4d4b2b6f443460a14310cf37d239f9c6ffb49499029fe5c32508278e5ce9367c",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:29:49] [INFO ] [llm.c:125] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=50875 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
|
||||
[2026-04-28 07:29:54] [INFO ] [main.c:1691] [didactyl] shutting down
|
||||
[2026-04-28 07:29:54] [INFO ] [trigger_manager.c:1734] [didactyl] trigger manager cleaned up
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1777375788"]
|
||||
[2026-04-28 07:29:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1777375788"]
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"run_meta": {
|
||||
"run_timestamp": "2026-04-28T11:29:54.142514+00:00",
|
||||
"base_url": "http://127.0.0.1:8491",
|
||||
"config": "tests/results/20260428T112851Z/runtime_test_genesis.jsonc",
|
||||
"binary": "didactyl",
|
||||
"test_count": 4
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "simple_greeting",
|
||||
"status": "pass",
|
||||
"message": "greeting response ok",
|
||||
"duration_seconds": 4.47363213999779,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"response": "Hello! I'm a Didactyl Agent - a test agent designed to help you with various tasks. I can assist with things like:\n\n- **Nostr protocol operations** (posting, querying events, managing profiles, etc.)\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "agent_responds_about_itself",
|
||||
"status": "pass",
|
||||
"message": "self description ok",
|
||||
"duration_seconds": 5.864955286990153,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"response": "i'm **didactyl**, a nostr-native ai agent. here's what i do:\n\n**core identity:**\n- i'm an autonomous agent running on the nostr protocol, designed to interact with decentralized networks and handle bo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "empty_message_handling",
|
||||
"status": "timeout",
|
||||
"message": "timed out",
|
||||
"duration_seconds": 43.47824207498343,
|
||||
"agent_errors": [],
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "very_long_message",
|
||||
"status": "pass",
|
||||
"message": "long message handled",
|
||||
"duration_seconds": 4.959190360998036,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"pass": 3,
|
||||
"timeout": 1
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
== Didactyl Test Results ==
|
||||
Run: 2026-04-28T11:29:54.142514+00:00
|
||||
Agent base URL: http://127.0.0.1:8491
|
||||
|
||||
Pass: 3
|
||||
Fail: 0
|
||||
Error: 0
|
||||
Timeout: 1
|
||||
Skip: 0
|
||||
Total: 4
|
||||
|
||||
[PASS] test_conversation::simple_greeting (4.47s)
|
||||
greeting response ok
|
||||
|
||||
[PASS] test_conversation::agent_responds_about_itself (5.86s)
|
||||
self description ok
|
||||
|
||||
[TIMEOUT] test_conversation::empty_message_handling (43.48s)
|
||||
timed out
|
||||
|
||||
[PASS] test_conversation::very_long_message (4.96s)
|
||||
long message handled
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
[2026-04-28 07:31:29] [INFO ] [main.c:1103] Didactyl v0.2.48 starting
|
||||
[2026-04-28 07:31:29] [INFO ] [nostr_handler.c:2558] [didactyl] initializing relay pool with 2 relays
|
||||
[2026-04-28 07:31:29] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.damus.io
|
||||
[2026-04-28 07:31:29] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.primal.net
|
||||
[2026-04-28 07:31:30] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1777375889", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1777375889", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1777375889"]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1777375889"]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1777375889"]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1777375889"]
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:304] [didactyl] startup checklist [01] Relay connectivity: begin
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:311] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2)
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:304] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1777375891", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1777375891", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1047>
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1045>
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1777375891"]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1777375891"]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1777375891"]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1777375891"]
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:1007] [didactyl] startup phase: recovered user-settings from Nostr
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:311] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok (user-settings recalled)
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:304] [didactyl] startup checklist [03] Validate LLM config: begin
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:314] [didactyl] startup checklist [03] Validate LLM config: ok
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:304] [didactyl] startup checklist [04] Validate admin config: begin
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:314] [didactyl] startup checklist [04] Validate admin config: ok
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:304] [didactyl] startup checklist [05] Initialize LLM client: begin
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:314] [didactyl] startup checklist [05] Initialize LLM client: ok
|
||||
[2026-04-28 07:31:31] [INFO ] [main.c:304] [didactyl] startup checklist [06] Detect first run via kind 10002: begin
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1777375891", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:31] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1777375891", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1777375891"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1777375891"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1777375891"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1777375891"]
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1337] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:311] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run)
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [07] Reconcile/persist startup state: begin
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1777375892", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1777375892", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=480>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=3fe6071b53da897c... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=474>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=b479ae241442d8a9... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=827>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=8837248dc5163739... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=553>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=be5d4be2386c8715... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1657] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=482>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=3fe6071b53da897c... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=476>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=b479ae241442d8a9... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=829>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=0ad4dcde6efcb420... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375892)
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=555>
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=be5d4be2386c8715... created_at=1777375892
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375892)
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:3676] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:937] [didactyl] startup phase: skipping user-settings publish because runtime config was recalled from Nostr
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1361] [didactyl] startup phase: persisted encrypted runtime config to Nostr
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:314] [didactyl] startup checklist [07] Reconcile/persist startup state: ok
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [08] Initialize agent: begin
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1777375892", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1777375892", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","3fe6071b53da897c7a173d4ee4d9ed03b969fb476a8f7f9a7a50f131f475d1ee",true,""]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","b479ae241442d8a9457fd83cef39b269e7415c22963dbde30d2ebb79c36ef4c0",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","b479ae241442d8a9457fd83cef39b269e7415c22963dbde30d2ebb79c36ef4c0",true,""]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","3fe6071b53da897c7a173d4ee4d9ed03b969fb476a8f7f9a7a50f131f475d1ee",true,""]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=824>
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","8837248dc5163739b290229b0bfbc1bd6e171863d2d9afa300e97ce327fbd578",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","be5d4be2386c8715d0a548b725f2df3e075775913c991f84f3ca513d7360ac48",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","0ad4dcde6efcb420185e65ef1758654e79381c5c32982027b590ac5c76655dbb",true,""]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","be5d4be2386c8715d0a548b725f2df3e075775913c991f84f3ca513d7360ac48",true,""]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1777375892"]
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:314] [didactyl] startup checklist [08] Initialize agent: ok
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [09] Initialize trigger manager: begin
|
||||
[2026-04-28 07:31:32] [INFO ] [trigger_manager.c:935] [didactyl] trigger manager initialized (capacity=16)
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:314] [didactyl] startup checklist [09] Initialize trigger manager: ok
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1405] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [10] Load startup triggers: begin
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1408] [didactyl] startup phase: startup-config trigger load begin
|
||||
[2026-04-28 07:31:32] [INFO ] [trigger_manager.c:1259] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:31:32] [INFO ] [trigger_manager.c:1142] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:31:32] [INFO ] [trigger_manager.c:1150] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1)
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1412] [didactyl] startup phase: startup-config trigger load end
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:314] [didactyl] startup checklist [10] Load startup triggers: ok
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:383] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:390] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":4,"events_published_ok":1,"events_published_failed":3,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375889,"last_event_time":1777375892,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":3,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375889,"last_event_time":1777375892}]}
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1777375892", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1777375892", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1777375892"]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1777375892"]
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:410] [didactyl] kind10002 query received event_count=2
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:507] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"]
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:612] [didactyl] kind10002 wait success: relay_count=2
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:614] [didactyl] kind10002 relay[0]=wss://relay.damus.io
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:614] [didactyl] kind10002 relay[1]=wss://relay.primal.net
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:618] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":2,"events_published":4,"events_published_ok":1,"events_published_failed":3,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375889,"last_event_time":1777375892,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":4,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777375889,"last_event_time":1777375892}]}
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:311] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2)
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [12] Subscribe admin context: begin
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1463] [didactyl] startup phase: subscribe admin context begin
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1777375892", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1777375892", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1777375892", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1777375892", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:2703] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f...
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1467] [didactyl] startup phase: subscribe admin context end
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:314] [didactyl] startup checklist [12] Subscribe admin context: ok
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [13] Subscribe agent self context: begin
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1471] [didactyl] startup phase: subscribe agent self context begin
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1777375892", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1777375892", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1777375892", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1777375892", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:2791] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1475] [didactyl] startup phase: subscribe agent self context end
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:314] [didactyl] startup checklist [13] Subscribe agent self context: ok
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:304] [didactyl] startup checklist [14] Subscribe self-skill cache: begin
|
||||
[2026-04-28 07:31:32] [INFO ] [main.c:1479] [didactyl] startup phase: subscribe self skill cache begin
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:2827] [didactyl] self-skill subscription filter: {"kinds":[31123,31124,10123],"authors":["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],"limit":300}
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1777375892", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1777375892", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1777375892", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:31:32] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1777375892", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:31:32] [INFO ] [nostr_handler.c:2887] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...)
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=469>
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=471>
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=538>
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=dc138a7c573943b81100e3d41471840a3bfa6bc662809fc30b47af87276aa54b created_at=1777375787 via wss://relay.damus.io
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=dc138a7c573943b8... created_at=1777375787
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1609] [didactyl] self-skill upsert rejected: stale (existing created_at=1777375892 >= new created_at=1777375787)
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=816>
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=78b0aaddaf99b7dc66a8094c036551a416542ef87e7701ef53a4efbfe5f2d85d created_at=1777375787 via wss://relay.damus.io
|
||||
[2026-04-28 07:31:33] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=78b0aaddaf99b7dc... created_at=1777375787
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1609] [didactyl] self-skill upsert rejected: stale (existing created_at=1777375892 >= new created_at=1777375787)
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=540>
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=be5d4be2386c8715d0a548b725f2df3e075775913c991f84f3ca513d7360ac48 created_at=1777375892 via wss://relay.primal.net
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=be5d4be2386c8715... created_at=1777375892
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375892)
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=818>
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=0ad4dcde6efcb420185e65ef1758654e79381c5c32982027b590ac5c76655dbb created_at=1777375892 via wss://relay.primal.net
|
||||
[2026-04-28 07:31:33] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=0ad4dcde6efcb420... created_at=1777375892
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777375892)
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1777375892"]
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:2070] [didactyl] self-skill EOSE received: cached skill events=0
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:47] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0)
|
||||
[2026-04-28 07:31:33] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:31:33] [INFO ] [trigger_manager.c:1039] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1)
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:57] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]}
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=0ad4dcde6efcb420185e65ef1758654e79381c5c32982027b590ac5c76655dbb created_at=1777375892
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=10123 d_tag=- id=be5d4be2386c8715d0a548b725f2df3e075775913c991f84f3ca513d7360ac48 created_at=1777375892
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1546] [didactyl] startup phase: subscribe self skill cache end
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:311] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1)
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:304] [didactyl] startup checklist [15] Subscribe DMs: begin
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1550] [didactyl] startup phase: subscribe DMs begin
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:2905] [didactyl] DM subscribe: dm_protocol=2 need_kind4=1 need_kind1059=1 g_start_time=1777375889
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1777375893", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375889,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1777375893", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375889,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1777375893", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1777375893", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:3000] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:3001] [didactyl] DEBUG DM subscription g_start_time=1777375889 now=1777375893 delta=4 relay_count=2
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1565] [didactyl] startup phase: subscribe DMs end
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:314] [didactyl] startup checklist [15] Subscribe DMs: ok
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:304] [didactyl] startup checklist [16] Subscribe wallet events: begin
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1569] [didactyl] startup phase: subscribe wallet events begin
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1777375893", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375889,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1777375893", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777375889,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:3053] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1573] [didactyl] startup phase: subscribe wallet events end
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:314] [didactyl] startup checklist [16] Subscribe wallet events: ok
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:304] [didactyl] startup checklist [17] Initialize cashu wallet: begin
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_16_1777375893", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_16_1777375893", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1777375892"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_16_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_16_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_16_1777375893"]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_16_1777375893"]
|
||||
[2026-04-28 07:31:33] [WARN ] [main.c:1589] [didactyl] startup phase: cashu wallet load/create deferred to first tool call
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:311] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred)
|
||||
[2026-04-28 07:31:33] [INFO ] [http_api.c:1662] [didactyl] http api listening on http://127.0.0.1:8492
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1629] [didactyl] HTTP API listening at http://127.0.0.1:8492
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1632] [didactyl] HTTP API endpoints: http://127.0.0.1:8492/api/context/current http://127.0.0.1:8492/api/context/parts
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:1844] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-28 07:31:33 (version v0.2.48, connected relays: 2/2).
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:1836] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1777375893,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"Okz4VaTNjFWFv/ege6fWWTOq6Vn+A4xlLURHP+qvNO6kRfdZrIEToVeBqJnMWtx9IzuFeB3fBK4qzxs74ANTn0aFNwD4mDetdRD0/TcDU8zSgwOV5D6FgIWk64aR0xvQ3beE9Fbq0PXezu8DwvfjvtKBqsasqhQ3PJWhPUi4kLY=?iv=UIBiCRfNQRnh4TN4zU02/w==","id":"2bd1a4c12247eee64f0a8e0f6ae2f663c78ba357c42768a8311e0e066c549502","sig":"eae6b86703c549c6bb1f567add1b411c8db5b84c18a3659ff929c2fc6cb2d4aa13be36d8e48106fd72675d67adc591e0872ab11a1f78b604e7fd5e571d109c15"}
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:920] [didactyl] publish DM target relays (2):
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.damus.io
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.primal.net
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=681>
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=683>
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.damus.io (async)
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.primal.net (async)
|
||||
[2026-04-28 07:31:33] [INFO ] [nostr_handler.c:3209] [didactyl] sent DM 2bd1a4c12247eee6... to 254d7a7f68b4443f... via 2 connected relay(s)
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:311] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop)
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1668] [didactyl] entering main poll loop
|
||||
[2026-04-28 07:31:33] [INFO ] [main.c:1669] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","2bd1a4c12247eee64f0a8e0f6ae2f663c78ba357c42768a8311e0e066c549502",true,""]
|
||||
[2026-04-28 07:31:33] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","2bd1a4c12247eee64f0a8e0f6ae2f663c78ba357c42768a8311e0e066c549502",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:31:33] [INFO ] [llm.c:125] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=50875 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
|
||||
[2026-04-28 07:31:37] [INFO ] [main.c:1691] [didactyl] shutting down
|
||||
[2026-04-28 07:31:37] [INFO ] [trigger_manager.c:1734] [didactyl] trigger manager cleaned up
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1777375892"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1777375893"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1777375893"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1777375893"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1777375893"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1777375893"]
|
||||
[2026-04-28 07:31:37] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1777375893"]
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"run_meta": {
|
||||
"run_timestamp": "2026-04-28T11:31:37.668616+00:00",
|
||||
"base_url": "http://127.0.0.1:8492",
|
||||
"config": "tests/results/20260428T113005Z/runtime_test_genesis.jsonc",
|
||||
"binary": "didactyl",
|
||||
"test_count": 4
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "simple_greeting",
|
||||
"status": "pass",
|
||||
"message": "greeting response ok",
|
||||
"duration_seconds": 3.2006136770069133,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"response": "Hello! I'm a test agent powered by Didactyl. I don't have a specific personal name, but you can think of me as your Didactyl assistant. I'm here to help you with various tasks using the tools and capa"
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "agent_responds_about_itself",
|
||||
"status": "pass",
|
||||
"message": "self description ok",
|
||||
"duration_seconds": 6.3043079580238555,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"response": "i'm **didactyl**, a nostr-based ai agent. here's a brief description of what i am:\n\n## core identity\n- a decentralized autonomous agent that operates on the nostr protocol\n- i can publish, query, and "
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "empty_message_handling",
|
||||
"status": "timeout",
|
||||
"message": "timed out",
|
||||
"duration_seconds": 74.4663479780138,
|
||||
"agent_errors": [],
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"suite": "test_conversation",
|
||||
"name": "very_long_message",
|
||||
"status": "pass",
|
||||
"message": "long message handled",
|
||||
"duration_seconds": 4.021215749002295,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"pass": 3,
|
||||
"timeout": 1
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
== Didactyl Test Results ==
|
||||
Run: 2026-04-28T11:31:37.668616+00:00
|
||||
Agent base URL: http://127.0.0.1:8492
|
||||
|
||||
Pass: 3
|
||||
Fail: 0
|
||||
Error: 0
|
||||
Timeout: 1
|
||||
Skip: 0
|
||||
Total: 4
|
||||
|
||||
[PASS] test_conversation::simple_greeting (3.20s)
|
||||
greeting response ok
|
||||
|
||||
[PASS] test_conversation::agent_responds_about_itself (6.30s)
|
||||
self description ok
|
||||
|
||||
[TIMEOUT] test_conversation::empty_message_handling (74.47s)
|
||||
timed out
|
||||
|
||||
[PASS] test_conversation::very_long_message (4.02s)
|
||||
long message handled
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
[2026-04-28 07:33:54] [INFO ] [main.c:1103] Didactyl v0.2.48 starting
|
||||
[2026-04-28 07:33:54] [INFO ] [nostr_handler.c:2558] [didactyl] initializing relay pool with 2 relays
|
||||
[2026-04-28 07:33:54] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.damus.io
|
||||
[2026-04-28 07:33:54] [INFO ] [nostr_handler.c:2582] [didactyl] added relay: wss://relay.primal.net
|
||||
[2026-04-28 07:33:54] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1777376034", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1777376034", {
|
||||
"kinds": [10000],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1777376034"]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1777376034"]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1777376034"]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1777376034"]
|
||||
[2026-04-28 07:33:55] [INFO ] [main.c:304] [didactyl] startup checklist [01] Relay connectivity: begin
|
||||
[2026-04-28 07:33:55] [INFO ] [main.c:311] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2)
|
||||
[2026-04-28 07:33:55] [INFO ] [main.c:304] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1777376035", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1777376035", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["user-settings"],
|
||||
"limit": 20
|
||||
}]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1047>
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1777376035"]
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=1045>
|
||||
[2026-04-28 07:33:55] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1777376035"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1777376035"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1777376035"]
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:1007] [didactyl] startup phase: recovered user-settings from Nostr
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:311] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok (user-settings recalled)
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [03] Validate LLM config: begin
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:314] [didactyl] startup checklist [03] Validate LLM config: ok
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [04] Validate admin config: begin
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:314] [didactyl] startup checklist [04] Validate admin config: ok
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [05] Initialize LLM client: begin
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:314] [didactyl] startup checklist [05] Initialize LLM client: ok
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [06] Detect first run via kind 10002: begin
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1777376036", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1777376036", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1777376036"]
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:1337] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:311] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run)
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [07] Reconcile/persist startup state: begin
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1777376036", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1777376036", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=480>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=5b81d0953899e3a9... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=474>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=fba0d939f4f65546... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=827>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=7e0b33e002ab60a7... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=553>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b92a83c390fd9778... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1645] [didactyl] self-skill upsert accepted: new entry added
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1657] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=482>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=0 author=1eb171136dbb1ba6... id=5b81d0953899e3a9... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=476>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10002 author=1eb171136dbb1ba6... id=fba0d939f4f65546... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1540] [didactyl] self-skill upsert rejected: kind not in {31123,31124,10123}
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=829>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=af34bcfae6b03bbd... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777376036)
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=555>
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3323] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b92a83c390fd9778... created_at=1777376036
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777376036)
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3664] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net
|
||||
[2026-04-28 07:33:56] [INFO ] [nostr_handler.c:3676] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:937] [didactyl] startup phase: skipping user-settings publish because runtime config was recalled from Nostr
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:1361] [didactyl] startup phase: persisted encrypted runtime config to Nostr
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:314] [didactyl] startup checklist [07] Reconcile/persist startup state: ok
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [08] Initialize agent: begin
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1777376036", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1777376036", {
|
||||
"kinds": [30078],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"#d": ["memory"],
|
||||
"limit": 1
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","5b81d0953899e3a93924791661281c26127575ad0469a818c0efdc4d1d7aa824",true,""]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","fba0d939f4f655465a2b4aed4ec8ed0b6f3780137874881f0715d10647c90c7a",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","fba0d939f4f655465a2b4aed4ec8ed0b6f3780137874881f0715d10647c90c7a",true,""]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","5b81d0953899e3a93924791661281c26127575ad0469a818c0efdc4d1d7aa824",true,""]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=824>
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1777376036"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1777376036"]
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:314] [didactyl] startup checklist [08] Initialize agent: ok
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [09] Initialize trigger manager: begin
|
||||
[2026-04-28 07:33:56] [INFO ] [trigger_manager.c:935] [didactyl] trigger manager initialized (capacity=16)
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:314] [didactyl] startup checklist [09] Initialize trigger manager: ok
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:1405] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [10] Load startup triggers: begin
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:1408] [didactyl] startup phase: startup-config trigger load begin
|
||||
[2026-04-28 07:33:56] [INFO ] [trigger_manager.c:1259] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:33:56] [INFO ] [trigger_manager.c:1142] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:33:56] [INFO ] [trigger_manager.c:1150] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1)
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:1412] [didactyl] startup phase: startup-config trigger load end
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:314] [didactyl] startup checklist [10] Load startup triggers: ok
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:304] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:383] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2
|
||||
[2026-04-28 07:33:56] [INFO ] [main.c:390] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":4,"events_published_ok":1,"events_published_failed":1,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777376034,"last_event_time":1777376036,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":3,"events_published":4,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777376034,"last_event_time":1777376036}]}
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1777376036", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1777376036", {
|
||||
"kinds": [10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 16
|
||||
}]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","7e0b33e002ab60a76e630503ab86ce56a6e27c75f07c17d3f359c24a7233a5d2",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","af34bcfae6b03bbdabc704228be682239cf327cc2ffc2dbf4f95d58e227310ae",true,""]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","b92a83c390fd97786654c10249b1a4f48b6a53cab4ad448833341ce3433b57ce",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:33:56] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","b92a83c390fd97786654c10249b1a4f48b6a53cab4ad448833341ce3433b57ce",true,""]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1777376036"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1777376036"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1777376036"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1777376036"]
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:410] [didactyl] kind10002 query received event_count=2
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:507] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"]
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:612] [didactyl] kind10002 wait success: relay_count=2
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:614] [didactyl] kind10002 relay[0]=wss://relay.damus.io
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:614] [didactyl] kind10002 relay[1]=wss://relay.primal.net
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:618] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":4,"events_published_ok":1,"events_published_failed":3,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777376034,"last_event_time":1777376036,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":4,"events_published":4,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1777376034,"last_event_time":1777376036}]}
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:311] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2)
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:304] [didactyl] startup checklist [12] Subscribe admin context: begin
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1463] [didactyl] startup phase: subscribe admin context begin
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1777376037", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1777376037", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 32
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1777376037", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1777376037", {
|
||||
"kinds": [1],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2703] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f...
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1467] [didactyl] startup phase: subscribe admin context end
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:314] [didactyl] startup checklist [12] Subscribe admin context: ok
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:304] [didactyl] startup checklist [13] Subscribe agent self context: begin
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1471] [didactyl] startup phase: subscribe agent self context begin
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1777376037", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1777376037", {
|
||||
"kinds": [0, 3, 10002],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 64
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1777376037", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1777376037", {
|
||||
"kinds": [1],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 10
|
||||
}]
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2791] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1475] [didactyl] startup phase: subscribe agent self context end
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:314] [didactyl] startup checklist [13] Subscribe agent self context: ok
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:304] [didactyl] startup checklist [14] Subscribe self-skill cache: begin
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1479] [didactyl] startup phase: subscribe self skill cache begin
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2827] [didactyl] self-skill subscription filter: {"kinds":[31123,31124,10123],"authors":["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],"limit":300}
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1777376037", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1777376037", {
|
||||
"kinds": [31123, 31124, 10123],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 300
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1777376037", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1777376037", {
|
||||
"kinds": [31123],
|
||||
"authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"],
|
||||
"limit": 150
|
||||
}]
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2887] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...)
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=469>
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=460>
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=462>
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=471>
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=538>
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=4949970fdf8382fa8a44ec78d9d37a77713df7e20121f7f1d49520829aef8165 created_at=1777376025 via wss://relay.damus.io
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=4949970fdf8382fa... created_at=1777376025
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1609] [didactyl] self-skill upsert rejected: stale (existing created_at=1777376036 >= new created_at=1777376025)
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=816>
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=91d20999ae0370494c7a7d789aef531cc80b85758175f5e16ac703d0c66ba56b created_at=1777376025 via wss://relay.damus.io
|
||||
[2026-04-28 07:33:57] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=91d20999ae037049... created_at=1777376025
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1609] [didactyl] self-skill upsert rejected: stale (existing created_at=1777376036 >= new created_at=1777376025)
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=540>
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=10123 d_tag=- id=b92a83c390fd97786654c10249b1a4f48b6a53cab4ad448833341ce3433b57ce created_at=1777376036 via wss://relay.primal.net
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=10123 author=1eb171136dbb1ba6... id=b92a83c390fd9778... created_at=1777376036
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777376036)
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1634] [didactyl] kind 10123 accepted with 1 'a' tags
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] RECV <large EVENT frame suppressed, bytes=818>
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2500] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=af34bcfae6b03bbdabc704228be682239cf327cc2ffc2dbf4f95d58e227310ae created_at=1777376036 via wss://relay.primal.net
|
||||
[2026-04-28 07:33:57] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1502] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1526] [didactyl] self-skill upsert candidate: kind=31124 author=1eb171136dbb1ba6... id=af34bcfae6b03bbd... created_at=1777376036
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:1622] [didactyl] self-skill upsert accepted: replaced existing (old created_at=1777376036)
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1777376037"]
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2070] [didactyl] self-skill EOSE received: cached skill events=0
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:47] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0)
|
||||
[2026-04-28 07:33:57] [INFO ] [trigger_manager.c:1485] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1
|
||||
[2026-04-28 07:33:57] [INFO ] [trigger_manager.c:1039] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1)
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:57] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]}
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=af34bcfae6b03bbdabc704228be682239cf327cc2ffc2dbf4f95d58e227310ae created_at=1777376036
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:3114] [didactyl] self-skill cache event: kind=10123 d_tag=- id=b92a83c390fd97786654c10249b1a4f48b6a53cab4ad448833341ce3433b57ce created_at=1777376036
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1546] [didactyl] startup phase: subscribe self skill cache end
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:311] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1)
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:304] [didactyl] startup checklist [15] Subscribe DMs: begin
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1550] [didactyl] startup phase: subscribe DMs begin
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:2905] [didactyl] DM subscribe: dm_protocol=2 need_kind4=1 need_kind1059=1 g_start_time=1777376034
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1777376037", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777376034,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1777376037", {
|
||||
"kinds": [4],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777376034,
|
||||
"limit": 100
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1777376037", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1777376037", {
|
||||
"kinds": [1059],
|
||||
"#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"limit": 400
|
||||
}]
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:3000] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:3001] [didactyl] DEBUG DM subscription g_start_time=1777376034 now=1777376037 delta=3 relay_count=2
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1565] [didactyl] startup phase: subscribe DMs end
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:314] [didactyl] startup checklist [15] Subscribe DMs: ok
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:304] [didactyl] startup checklist [16] Subscribe wallet events: begin
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1569] [didactyl] startup phase: subscribe wallet events begin
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1777376037", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777376034,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1777376037", {
|
||||
"kinds": [17375, 7375, 5],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"],
|
||||
"since": 1777376034,
|
||||
"limit": 500
|
||||
}]
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:3053] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6...
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1573] [didactyl] startup phase: subscribe wallet events end
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:314] [didactyl] startup checklist [16] Subscribe wallet events: ok
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:304] [didactyl] startup checklist [17] Initialize cashu wallet: begin
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_16_1777376037", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_16_1777376037", {
|
||||
"kinds": [17375, 7375],
|
||||
"authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]
|
||||
}]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:2063] [didactyl] DEBUG on_eose called: event_count=0
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_16_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_16_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_16_1777376037"]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_16_1777376037"]
|
||||
[2026-04-28 07:33:57] [WARN ] [main.c:1589] [didactyl] startup phase: cashu wallet load/create deferred to first tool call
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:311] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred)
|
||||
[2026-04-28 07:33:57] [INFO ] [http_api.c:1662] [didactyl] http api listening on http://127.0.0.1:8495
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1629] [didactyl] HTTP API listening at http://127.0.0.1:8495
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1632] [didactyl] HTTP API endpoints: http://127.0.0.1:8495/api/context/current http://127.0.0.1:8495/api/context/parts
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:1844] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-28 07:33:57 (version v0.2.48, connected relays: 2/2).
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:1836] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1777376037,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"4p/sJF1duSvs+UYfkSCgqI1Kh5THpuvq9LdjqG3QIqEM2815CGdjDJNU4vC008/hOGPQ0DOgQCJzwWpVUOdk1o4jYPMTfy4Dhx/h6G6rIVyPeUXZM0ChwueW3pijjBaZZFncCbFkP5TUV/bIfmBFNSe2L8RPyPMTkJvdl3lid80=?iv=XxaWd5flFRnNVMHxLKvoMw==","id":"1cc56bcf91cb665579cc89b38229097826ea5bc548a7823d123968cb2458cebe","sig":"3c87772d5063f753f6c6dc03f080a9540e749a1a6e3d601e6d209fad757528449932cd36572907ed7d1159596d4f481f5f46432521861c5524f94c3af08d5ec2"}
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:920] [didactyl] publish DM target relays (2):
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.damus.io
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:922] [didactyl] -> wss://relay.primal.net
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=681>
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:176] [didactyl] [nostr:websocket] SEND <large EVENT frame suppressed, bytes=683>
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.damus.io (async)
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:3200] [didactyl] kind 4 event published to wss://relay.primal.net (async)
|
||||
[2026-04-28 07:33:57] [INFO ] [nostr_handler.c:3209] [didactyl] sent DM 1cc56bcf91cb6655... to 254d7a7f68b4443f... via 2 connected relay(s)
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:311] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop)
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1668] [didactyl] entering main poll loop
|
||||
[2026-04-28 07:33:57] [INFO ] [main.c:1669] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","1cc56bcf91cb665579cc89b38229097826ea5bc548a7823d123968cb2458cebe",true,""]
|
||||
[2026-04-28 07:33:57] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","1cc56bcf91cb665579cc89b38229097826ea5bc548a7823d123968cb2458cebe",false,"rate-limited: you are noting too much"]
|
||||
[2026-04-28 07:33:59] [INFO ] [llm.c:125] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=30927 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"Say hello in one sentence."},{"role":"user","content":"Say hello in one sentence.","_ts":1777376039}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}}},{"type":"function","function":{"name":"nostr_delete","description":"Request deletion of one or more previously published events (NIP-09 kind 5)","parameters":{"type":"object","properties":{"event_ids":{"type":"array","description":"Event IDs to request deletion for","items":{"type":"string"}},"kinds":{"type":"array","description":"Optional kinds corresponding to event_ids entries","items":{"type":"integer"}},"reason":{"type":"string","description":"Optional human-readable reason"}},"required":["event_ids"]}}},{"type":"function","function":{"name":"nostr_react","description":"React to a Nostr event with like/dislike/emoji (NIP-25 kind 7)","parameters":{"type":"object","properties":{"event_id":{"type":"string","description":"Target event ID to react to"},"event_pubkey":{"type":"string","description":"Author pubkey of target event"},"event_kind":{"type":"integer","description":"Optional kind of target event"},"reaction":{"type":"string","description":"Reaction content such as +, -, ❤️, or emoji"}},"required":["event_id","event_pubkey"]}}},{"type":"function","function":{"name":"nostr_profile_get","description":"Look up a Nostr profile (kind 0 metadata) by pubkey","parameters":{"type":"object","properties":{"pubkey":{"type":"string","description":"Hex pubkey of the profile to fetch"}},"required":["pub...
|
||||
[2026-04-28 07:34:02] [INFO ] [main.c:1691] [didactyl] shutting down
|
||||
[2026-04-28 07:34:02] [INFO ] [trigger_manager.c:1734] [didactyl] trigger manager cleaned up
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1777376037"]
|
||||
[2026-04-28 07:34:02] [TRACE] [nostr_handler.c:183] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1777376037"]
|
||||
@@ -1,100 +0,0 @@
|
||||
{
|
||||
"run_meta": {
|
||||
"run_timestamp": "2026-04-28T11:34:02.786131+00:00",
|
||||
"base_url": "http://127.0.0.1:8495",
|
||||
"config": "tests/results/20260428T113354Z/runtime_test_genesis.jsonc",
|
||||
"binary": "didactyl",
|
||||
"test_count": 7
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "status_returns_200",
|
||||
"status": "pass",
|
||||
"message": "status success",
|
||||
"duration_seconds": 0.32880259698140435,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"keys": [
|
||||
"success",
|
||||
"name",
|
||||
"version",
|
||||
"pubkey",
|
||||
"relay_count",
|
||||
"connected_relays",
|
||||
"active_triggers"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "status_has_fields",
|
||||
"status": "pass",
|
||||
"message": "required fields present",
|
||||
"duration_seconds": 0.33118895199731924,
|
||||
"agent_errors": [],
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "context_current_returns_messages",
|
||||
"status": "pass",
|
||||
"message": "context current ok",
|
||||
"duration_seconds": 0.3325634880166035,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"message_count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_health",
|
||||
"name": "context_parts_has_system_prompt",
|
||||
"status": "pass",
|
||||
"message": "context parts ok",
|
||||
"duration_seconds": 0.3308573659742251,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"parts": [
|
||||
"system_prompt",
|
||||
"dm_history"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_timeouts",
|
||||
"name": "response_within_timeout",
|
||||
"status": "pass",
|
||||
"message": "response within timeout",
|
||||
"duration_seconds": 2.608836165018147,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"elapsed": 2.6088266609876882
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_timeouts",
|
||||
"name": "status_responds_fast",
|
||||
"status": "pass",
|
||||
"message": "status fast",
|
||||
"duration_seconds": 0.3307172780041583,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"elapsed": 0.3307107430009637
|
||||
}
|
||||
},
|
||||
{
|
||||
"suite": "test_timeouts",
|
||||
"name": "context_responds_fast",
|
||||
"status": "pass",
|
||||
"message": "context fast",
|
||||
"duration_seconds": 0.33165748399915174,
|
||||
"agent_errors": [],
|
||||
"details": {
|
||||
"elapsed": 0.33165288498275913
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"pass": 7
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
== Didactyl Test Results ==
|
||||
Run: 2026-04-28T11:34:02.786131+00:00
|
||||
Agent base URL: http://127.0.0.1:8495
|
||||
|
||||
Pass: 7
|
||||
Fail: 0
|
||||
Error: 0
|
||||
Timeout: 0
|
||||
Skip: 0
|
||||
Total: 7
|
||||
|
||||
[PASS] test_health::status_returns_200 (0.33s)
|
||||
status success
|
||||
|
||||
[PASS] test_health::status_has_fields (0.33s)
|
||||
required fields present
|
||||
|
||||
[PASS] test_health::context_current_returns_messages (0.33s)
|
||||
context current ok
|
||||
|
||||
[PASS] test_health::context_parts_has_system_prompt (0.33s)
|
||||
context parts ok
|
||||
|
||||
[PASS] test_timeouts::response_within_timeout (2.61s)
|
||||
response within timeout
|
||||
|
||||
[PASS] test_timeouts::status_responds_fast (0.33s)
|
||||
status fast
|
||||
|
||||
[PASS] test_timeouts::context_responds_fast (0.33s)
|
||||
context fast
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[2026-07-08 10:31:28] [INFO ] [main.c:1476] Didactyl v0.2.50 starting
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_TEST_ADMIN_PUBKEY\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[2026-07-08 10:31:28] [INFO ] [main.c:1476] Didactyl v0.2.50 starting
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"npub1REPLACE_WITH_TEST_ADMIN_PUBKEY\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
// TEST CONFIGURATION
|
||||
// Use disposable keys/accounts only.
|
||||
"key": {
|
||||
"nsec": "nsec1REPLACE_WITH_DISPOSABLE_TEST_NSEC"
|
||||
},
|
||||
"admin": {
|
||||
"pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"
|
||||
},
|
||||
"llm": {
|
||||
"provider": "https://api.ppq.ai",
|
||||
"api_key": "sk-SBI2Pga0J6n8jpPorHbRHC",
|
||||
"model": "claude-haiku-4.5",
|
||||
"base_url": "https://api.ppq.ai",
|
||||
"max_tokens": 10000,
|
||||
"temperature": 0.7
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"port": 8485,
|
||||
"bind_address": "127.0.0.1"
|
||||
},
|
||||
"encrypted_events": [
|
||||
{
|
||||
"kind": 30078,
|
||||
"d_tag": "user-settings",
|
||||
"content": "{\"v\":2,\"updatedAt\":0,\"global_llm\":{\"provider\":\"https://api.ppq.ai\",\"api_key\":\"sk-SBI2Pga0J6n8jpPorHbRHC\",\"model\":\"claude-haiku-4.5\",\"base_url\":\"https://api.ppq.ai\",\"max_tokens\":10000,\"temperature\":0.7},\"didactyl\":{\"admin_pubkey\":\"254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac\",\"dm_protocol\":\"both\",\"max_turns\":40}}"
|
||||
}
|
||||
],
|
||||
"startup_events": [
|
||||
{
|
||||
"kind": 0,
|
||||
"content_fields": {
|
||||
"name": "Didactyl Test Agent",
|
||||
"about": "Automated test instance"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"kind": 10002,
|
||||
"content": "",
|
||||
"tags": [
|
||||
["r", "wss://relay.damus.io"],
|
||||
["r", "wss://relay.primal.net"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": 31124,
|
||||
"content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.",
|
||||
"tags": [
|
||||
["d", "identity_and_rules"],
|
||||
["app", "didactyl"],
|
||||
["scope", "private"],
|
||||
["trigger", "dm"],
|
||||
["filter", "{\"from\":\"admin\"}"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user