v0.0.50 - Rename tools to category-consistent names, add local_* prefixes, merge context tool naming, and keep message_current template-only
This commit is contained in:
@@ -53,11 +53,11 @@ Skills support context modes (`inject`, `full`, `override`) and per-skill LLM fa
|
||||
|
||||
Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers.
|
||||
|
||||
## Current Status — v0.0.49
|
||||
## Current Status — v0.0.50
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.0.49 — Fix DM history context assembly by filtering kind-4 query with #p participants and increased timeout
|
||||
> Last release update: v0.0.50 — Rename tools to category-consistent names, add local_* prefixes, merge context tool naming, and keep message_current template-only
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
@@ -314,7 +314,7 @@ Didactyl builds tier-aware context:
|
||||
|
||||
- **ADMIN** request context — assembled from the soul's `---template---` section (if present), otherwise hardcoded order:
|
||||
1. Soul personality (everything above `---template---` in kind `31120`)
|
||||
2. Named template sections in order using `tool:` directives (for example `context_admin_profile`, `context_admin_notes`, `context_tasks`, `context_user_message`, `dm_history`)
|
||||
2. Named template sections in order using `tool:` directives (for example `nostr_admin_profile`, `nostr_admin_notes`, `task_list`, `message_current`, `dm_history`)
|
||||
3. Each section executes its configured context tool, optionally extracting `result_field` (default: `content`)
|
||||
4. Provider-specific content overrides per section remain supported for literal `content:` sections
|
||||
5. Section names are used in `context.log` headers and `/api/context/parts` response
|
||||
@@ -356,12 +356,12 @@ Current tool schema exposed to the LLM in [`tools_build_openai_schema_json()`](s
|
||||
- `skill_remove`
|
||||
- `skill_search`
|
||||
- Local/host tools:
|
||||
- `shell_exec`
|
||||
- `file_read`
|
||||
- `file_write`
|
||||
- `http_fetch`
|
||||
- `local_shell_exec`
|
||||
- `local_file_read`
|
||||
- `local_file_write`
|
||||
- `local_http_fetch`
|
||||
- Agent metadata:
|
||||
- `my_version`
|
||||
- `agent_version`
|
||||
- Model management:
|
||||
- `model_get`
|
||||
- `model_set`
|
||||
@@ -435,7 +435,7 @@ All dependencies are statically linked into the binary at build time. No system
|
||||
- [x] Relay pool with auto-reconnect and status logging
|
||||
- [x] Per-relay startup publish on relay-connected transitions
|
||||
- [x] Runtime diagnostics — relay health, message flow, event kind publish logs
|
||||
- [x] Tool-calling loop (nostr_post, nostr_query, shell_exec, file_read, file_write)
|
||||
- [x] Tool-calling loop (nostr_post, nostr_query, local_shell_exec, local_file_read, local_file_write)
|
||||
- [x] Context assembly with startup events + recent DM history
|
||||
- [x] Context payload logging to [`context.log`](context.log)
|
||||
- [x] Skill kind definitions (`31120` Soul, `31123` Public Skill, `31124` Private Skill)
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
// structured context sections that are injected at runtime.
|
||||
{
|
||||
"kind": 31120,
|
||||
"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 `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 content: |\n ## Administrator Identity (source: config.admin.pubkey)\n\n This is your administrator! Admin pubkey (hex): {{admin_pubkey}}\n\n- section: admin_profile\n role: system\n content: |\n ## Administrator Kind 0 Profile (source: nostr kind 0)\n\n Administrator kind 0 profile content (JSON): {{admin_kind0_json}}\n provider:\n anthropic: |\n <admin_kind0_profile source=\"nostr_kind_0\">\n {{admin_kind0_json}}\n </admin_kind0_profile>\n\n- section: admin_contacts\n role: system\n content: |\n ## Administrator Contact List (source: nostr kind 3)\n\n Administrator kind 3 contact list pubkeys (JSON array): {{admin_kind3_json}}\n provider:\n anthropic: |\n <admin_contacts source=\"nostr_kind_3\">\n {{admin_kind3_json}}\n </admin_contacts>\n\n- section: admin_relays\n role: system\n content: |\n ## Administrator Relay List (source: nostr kind 10002)\n\n Administrator kind 10002 relay list (JSON): {{admin_kind10002_json}}\n provider:\n anthropic: |\n <admin_relays source=\"nostr_kind_10002\">\n {{admin_kind10002_json}}\n </admin_relays>\n\n- section: admin_notes\n role: system\n content: |\n ## Administrator Recent Notes (source: nostr kind 1)\n\n Administrator recent kind 1 notes (JSON array): {{admin_kind1_json}}\n provider:\n anthropic: |\n <admin_notes source=\"nostr_kind_1\">\n {{admin_kind1_json}}\n </admin_notes>\n\n- section: skills\n role: system\n content: |\n ## Adopted Skills\n\n {{skills_json}}\n\n- section: tasks\n role: system\n content: |\n ## Current Task List\n\n {{tasks_json}}\n\n- section: conversation\n role: conversation\n content: |\n {{conversation}}",
|
||||
"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 content: |\n ## Administrator Identity (source: config.admin.pubkey)\n\n This is your administrator! Admin pubkey (hex): {{admin_pubkey}}\n\n- section: admin_profile\n role: system\n content: |\n ## Administrator Kind 0 Profile (source: nostr kind 0)\n\n Administrator kind 0 profile content (JSON): {{admin_kind0_json}}\n provider:\n anthropic: |\n <admin_kind0_profile source=\"nostr_kind_0\">\n {{admin_kind0_json}}\n </admin_kind0_profile>\n\n- section: admin_contacts\n role: system\n content: |\n ## Administrator Contact List (source: nostr kind 3)\n\n Administrator kind 3 contact list pubkeys (JSON array): {{admin_kind3_json}}\n provider:\n anthropic: |\n <admin_contacts source=\"nostr_kind_3\">\n {{admin_kind3_json}}\n </admin_contacts>\n\n- section: admin_relays\n role: system\n content: |\n ## Administrator Relay List (source: nostr kind 10002)\n\n Administrator kind 10002 relay list (JSON): {{admin_kind10002_json}}\n provider:\n anthropic: |\n <admin_relays source=\"nostr_kind_10002\">\n {{admin_kind10002_json}}\n </admin_relays>\n\n- section: admin_notes\n role: system\n content: |\n ## Administrator Recent Notes (source: nostr kind 1)\n\n Administrator recent kind 1 notes (JSON array): {{admin_kind1_json}}\n provider:\n anthropic: |\n <admin_notes source=\"nostr_kind_1\">\n {{admin_kind1_json}}\n </admin_notes>\n\n- section: skills\n role: system\n content: |\n ## Adopted Skills\n\n {{skills_json}}\n\n- section: tasks\n role: system\n content: |\n ## Current Task List\n\n {{tasks_json}}\n\n- section: conversation\n role: conversation\n content: |\n {{conversation}}",
|
||||
"tags": [
|
||||
["d", "soul"],
|
||||
["app", "didactyl"],
|
||||
@@ -191,7 +191,7 @@
|
||||
"uses_skill": "long_form_note",
|
||||
"event_kind": 30023,
|
||||
"procedure": [
|
||||
"Read README.md using file_read with path README.md.",
|
||||
"Read README.md using local_file_read with path README.md.",
|
||||
"Set d tag exactly to readme.md.",
|
||||
"Set title from the first markdown H1 heading in README.md.",
|
||||
"Set summary from the opening paragraph of README.md.",
|
||||
|
||||
+7
-7
@@ -3,37 +3,37 @@
|
||||
```yaml
|
||||
- section: admin_identity
|
||||
role: system
|
||||
tool: context_admin_identity
|
||||
tool: admin_identity
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_profile
|
||||
role: system
|
||||
tool: context_admin_profile
|
||||
tool: nostr_admin_profile
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_contacts
|
||||
role: system
|
||||
tool: context_admin_contacts
|
||||
tool: nostr_admin_contacts
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_relays
|
||||
role: system
|
||||
tool: context_admin_relays
|
||||
tool: nostr_admin_relays
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_notes
|
||||
role: system
|
||||
tool: context_admin_notes
|
||||
tool: nostr_admin_notes
|
||||
skip_if_empty: true
|
||||
|
||||
- section: tasks
|
||||
role: system
|
||||
tool: context_tasks
|
||||
tool: task_list
|
||||
skip_if_empty: true
|
||||
|
||||
- section: conversation
|
||||
role: user
|
||||
tool: context_user_message
|
||||
tool: message_current
|
||||
skip_if_empty: true
|
||||
|
||||
- section: dm_history
|
||||
|
||||
+5
-5
@@ -98,11 +98,11 @@ These tools manage skill and trigger lifecycle; skill semantics and trigger exec
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `my_version` | Return current Didactyl version and metadata from build macros |
|
||||
| `http_fetch` | Fetch HTTP(S) resources with optional method, headers, timeout, and body |
|
||||
| `shell_exec` | Execute a shell command and return stdout/stderr |
|
||||
| `file_read` | Read a local file as text from the configured working directory |
|
||||
| `file_write` | Write text content to a local file in the configured working directory |
|
||||
| `agent_version` | Return current Didactyl version and metadata from build macros |
|
||||
| `local_http_fetch` | Fetch HTTP(S) resources with optional method, headers, timeout, and body |
|
||||
| `local_shell_exec` | Execute a shell command and return stdout/stderr |
|
||||
| `local_file_read` | Read a local file as text from the configured working directory |
|
||||
| `local_file_write` | Write text content to a local file in the configured working directory |
|
||||
| `tool_list` | List available tools with name, description, and JSON parameter schema |
|
||||
|
||||
### Content Publishing Conveniences
|
||||
|
||||
@@ -57,7 +57,7 @@ flowchart TD
|
||||
LOOP --> LLM
|
||||
LLM -->|tool_call| TOOLS
|
||||
TOOLS -->|nostr tools| NOSTR
|
||||
TOOLS -->|shell_exec| SHELL
|
||||
TOOLS -->|local_shell_exec| SHELL
|
||||
TOOLS -->|result| LOOP
|
||||
LLM -->|final answer| DMS
|
||||
```
|
||||
@@ -212,7 +212,7 @@ sequenceDiagram
|
||||
|
||||
| Tool | Description | Notes |
|
||||
|---|---|---|
|
||||
| `shell_exec` | Run a shell command, capture stdout/stderr | Sandboxed with timeouts |
|
||||
| `local_shell_exec` | Run a shell command, capture stdout/stderr | Sandboxed with timeouts |
|
||||
|
||||
---
|
||||
|
||||
@@ -377,7 +377,7 @@ void agent_on_message(const char* sender, const char* message) {
|
||||
3. **Agent loop** — rewrite `agent.c` with the tool-call loop
|
||||
4. **`nostr_post` tool** — publish any kind event
|
||||
5. **`nostr_query` tool** — query relays with filters
|
||||
6. **`shell_exec` tool** — sandboxed shell command execution
|
||||
6. **`local_shell_exec` tool** — sandboxed shell command execution
|
||||
7. **`nostr_dm` tool** — NIP-17 private DMs
|
||||
8. **Config extension** — parse tools config section
|
||||
9. **Security hardening** — timeouts, output limits, allowlists
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
| 5 | `nostr_react` | 7 / NIP-25 | ✅ Shipped |
|
||||
| 6 | `nostr_profile_get` | 0 query | ✅ Shipped |
|
||||
| 7 | `nostr_relay_status` | Pool stats | ✅ Shipped |
|
||||
| 8 | `shell_exec` | OS | ✅ Shipped |
|
||||
| 9 | `file_read` | OS | ✅ Shipped |
|
||||
| 10 | `file_write` | OS | ✅ Shipped |
|
||||
| 8 | `local_shell_exec` | OS | ✅ Shipped |
|
||||
| 9 | `local_file_read` | OS | ✅ Shipped |
|
||||
| 10 | `local_file_write` | OS | ✅ Shipped |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ Simple YAML-like format parsed in C. Each section has:
|
||||
|---|---|---|
|
||||
| `section` | yes | Section name for logging and API identification |
|
||||
| `role` | yes | Chat message role: `system`, `user`, `assistant`, or `expand` |
|
||||
| `tool` | recommended | Tool name executed by template builder (for example `context_admin_profile`) |
|
||||
| `tool` | recommended | Tool name executed by template builder (for example `nostr_admin_profile`) |
|
||||
| `args` | no | JSON args string passed to the tool (default `{}`) |
|
||||
| `result_field` | no | Field extracted from tool JSON result (default `content`) |
|
||||
| `content` | optional fallback | Literal content when no `tool` is specified |
|
||||
|
||||
@@ -194,9 +194,9 @@ New sections in `config.json`:
|
||||
|
||||
### Problem
|
||||
|
||||
Even with Phase 1 protections, the private key (nsec) still lives in Didactyl process memory and in `config.json` on disk. The LLM has `shell_exec` and `file_read` tools — a sufficiently clever prompt injection could theoretically:
|
||||
- Read `config.json` via `file_read` (contains nsec)
|
||||
- Execute `cat /proc/self/maps` or similar via `shell_exec`
|
||||
Even with Phase 1 protections, the private key (nsec) still lives in Didactyl process memory and in `config.json` on disk. The LLM has `local_shell_exec` and `local_file_read` tools — a sufficiently clever prompt injection could theoretically:
|
||||
- Read `config.json` via `local_file_read` (contains nsec)
|
||||
- Execute `cat /proc/self/maps` or similar via `local_shell_exec`
|
||||
- Exfiltrate the key via `nostr_post`
|
||||
|
||||
### Solution: NIP-46 Remote Signer
|
||||
@@ -280,7 +280,7 @@ flowchart LR
|
||||
|
||||
- Private key never in Didactyl process memory
|
||||
- Private key never on disk in config.json
|
||||
- LLM tools (shell_exec, file_read) cannot access the key
|
||||
- LLM tools (local_shell_exec, local_file_read) cannot access the key
|
||||
- Even full process compromise of Didactyl does not leak the signing key
|
||||
- Remote signer can be on a separate hardened machine
|
||||
- Remote signer can implement rate limiting, approval flows, etc.
|
||||
|
||||
@@ -26,27 +26,27 @@ Replace `content: |` with `tool:` and optional `args:` directives:
|
||||
|
||||
- section: admin_identity
|
||||
role: system
|
||||
tool: context_admin_identity
|
||||
tool: admin_identity
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_profile
|
||||
role: system
|
||||
tool: context_admin_profile
|
||||
tool: nostr_admin_profile
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_contacts
|
||||
role: system
|
||||
tool: context_admin_contacts
|
||||
tool: nostr_admin_contacts
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_relays
|
||||
role: system
|
||||
tool: context_admin_relays
|
||||
tool: nostr_admin_relays
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_notes
|
||||
role: system
|
||||
tool: context_admin_notes
|
||||
tool: nostr_admin_notes
|
||||
skip_if_empty: true
|
||||
|
||||
- section: tools
|
||||
@@ -56,7 +56,7 @@ Replace `content: |` with `tool:` and optional `args:` directives:
|
||||
|
||||
- section: tasks
|
||||
role: system
|
||||
tool: context_tasks
|
||||
tool: task_list
|
||||
skip_if_empty: true
|
||||
|
||||
- section: dm_history
|
||||
@@ -65,7 +65,7 @@ Replace `content: |` with `tool:` and optional `args:` directives:
|
||||
|
||||
- section: conversation
|
||||
role: user
|
||||
tool: context_user_message
|
||||
tool: message_current
|
||||
skip_if_empty: true
|
||||
```
|
||||
|
||||
@@ -94,19 +94,19 @@ Add lightweight "context tools" that read from cached in-memory data. These are
|
||||
|
||||
| Tool | Returns | Source |
|
||||
|---|---|---|
|
||||
| `context_admin_identity` | Admin pubkey + verification text | config + sender tier |
|
||||
| `context_admin_profile` | Admin kind 0 profile JSON | cached `g_admin_kind0_json` |
|
||||
| `context_admin_contacts` | Admin kind 3 contacts JSON array | cached `g_admin_wot_contacts` |
|
||||
| `context_admin_relays` | Admin kind 10002 relay list JSON | cached `g_admin_kind10002_json` |
|
||||
| `context_admin_notes` | Admin recent kind 1 notes | cached `g_admin_kind1_notes` |
|
||||
| `context_tasks` | Current task list from tasks.json | file read (already exists as `task_manage` with `action: list`) |
|
||||
| `context_user_message` | Current user message text | passed via tool context |
|
||||
| `context_agent_identity` | Agent pubkey, npub | config |
|
||||
| `admin_identity` | Admin pubkey + verification text | config + sender tier |
|
||||
| `nostr_admin_profile` | Admin kind 0 profile JSON | cached `g_admin_kind0_json` |
|
||||
| `nostr_admin_contacts` | Admin kind 3 contacts JSON array | cached `g_admin_wot_contacts` |
|
||||
| `nostr_admin_relays` | Admin kind 10002 relay list JSON | cached `g_admin_kind10002_json` |
|
||||
| `nostr_admin_notes` | Admin recent kind 1 notes | cached `g_admin_kind1_notes` |
|
||||
| `task_list` | Current task list from tasks.json | file read (already exists as `task_manage` with `action: list`) |
|
||||
| `message_current` | Current user message text | passed via tool context |
|
||||
| `agent_identity` | Agent pubkey, npub | config |
|
||||
|
||||
Existing tools that already work for context:
|
||||
- `tool_list` — returns tool schemas (already exists)
|
||||
- `task_manage` with `{"action":"list"}` — returns tasks (already exists)
|
||||
- `file_read` — can read any file (already exists)
|
||||
- `local_file_read` — can read any file (already exists)
|
||||
|
||||
### Tool Result → Message Content
|
||||
|
||||
@@ -175,27 +175,27 @@ You are Didactyl, a sovereign AI agent living on Nostr.
|
||||
|
||||
- section: admin_identity
|
||||
role: system
|
||||
tool: context_admin_identity
|
||||
tool: admin_identity
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_profile
|
||||
role: system
|
||||
tool: context_admin_profile
|
||||
tool: nostr_admin_profile
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_contacts
|
||||
role: system
|
||||
tool: context_admin_contacts
|
||||
tool: nostr_admin_contacts
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_relays
|
||||
role: system
|
||||
tool: context_admin_relays
|
||||
tool: nostr_admin_relays
|
||||
skip_if_empty: true
|
||||
|
||||
- section: admin_notes
|
||||
role: system
|
||||
tool: context_admin_notes
|
||||
tool: nostr_admin_notes
|
||||
skip_if_empty: true
|
||||
|
||||
- section: tools
|
||||
@@ -217,7 +217,7 @@ You are Didactyl, a sovereign AI agent living on Nostr.
|
||||
|
||||
- section: conversation
|
||||
role: user
|
||||
tool: context_user_message
|
||||
tool: message_current
|
||||
skip_if_empty: true
|
||||
```
|
||||
|
||||
@@ -226,10 +226,10 @@ You are Didactyl, a sovereign AI agent living on Nostr.
|
||||
- `agent_template_resolve_var()` and all its `if (strcmp(...))` branches
|
||||
- `build_tool_schemas_json_string()`
|
||||
- `build_tasks_content_string()` (replaced by `task_manage` tool)
|
||||
- `build_admin_recent_posts_text()` (replaced by `context_admin_notes` tool)
|
||||
- `build_admin_profile_plain_text()` (replaced by `context_admin_profile` tool)
|
||||
- `build_admin_relay_list_plain_text()` (replaced by `context_admin_relays` tool)
|
||||
- `build_sender_verification_text()` (folded into `context_admin_identity` tool)
|
||||
- `build_admin_recent_posts_text()` (replaced by `nostr_admin_notes` tool)
|
||||
- `build_admin_profile_plain_text()` (replaced by `nostr_admin_profile` tool)
|
||||
- `build_admin_relay_list_plain_text()` (replaced by `nostr_admin_relays` tool)
|
||||
- `build_sender_verification_text()` (folded into `admin_identity` tool)
|
||||
- `build_startup_events_json_string()` (replaced by tool if needed)
|
||||
- `build_adopted_skills_payload_string()` (replaced by tool if needed)
|
||||
- `nostr_handler_get_admin_kind3_context()` (just added, will be replaced by tool)
|
||||
@@ -240,7 +240,7 @@ You are Didactyl, a sovereign AI agent living on Nostr.
|
||||
1. **Single data-fetching mechanism** — tools are the only way to get data
|
||||
2. **No more variable name mismatches** — tool names are validated at registration
|
||||
3. **User-configurable context** — admin can add any tool output to context by editing the soul template
|
||||
4. **Testable** — `--test-tool context_admin_profile` shows exactly what goes into context
|
||||
4. **Testable** — `--test-tool nostr_admin_profile` shows exactly what goes into context
|
||||
5. **Self-documenting** — `tool_list` shows all available context tools with descriptions
|
||||
6. **Extensible** — new tools automatically become available as context sources
|
||||
7. **Provider overrides still work** — `provider:` directive can still override formatting per-provider
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# Didactyl Tool Naming — Final List
|
||||
|
||||
## Decisions
|
||||
|
||||
- `context_*` tools merged into existing categories with proper names
|
||||
- Local tools get `local_` prefix
|
||||
- `context_user_message` removed from LLM schema (template-only)
|
||||
- `nostr_post_readme` kept — a post is different from a longform note
|
||||
- `nostr_file_md_to_longform_post` kept as-is
|
||||
- `task_manage` kept as-is
|
||||
|
||||
---
|
||||
|
||||
## Complete Tool List (sorted by category prefix)
|
||||
|
||||
### `admin_` — Administrator Metadata
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 1 | `admin_identity` | `context_admin_identity` | Admin pubkey, sender tier, verification metadata |
|
||||
|
||||
### `agent_` — Agent Self-Metadata
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 2 | `agent_identity` | `context_agent_identity` | Agent pubkey hex + npub |
|
||||
| 3 | `agent_version` | `my_version` | Didactyl version and build metadata |
|
||||
|
||||
### `local_` — Local / Host Tools
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 4 | `local_shell_exec` | `shell_exec` | Execute a shell command |
|
||||
| 5 | `local_file_read` | `file_read` | Read a local file as text |
|
||||
| 6 | `local_file_write` | `file_write` | Write text to a local file |
|
||||
| 7 | `local_http_fetch` | `http_fetch` | Fetch HTTP/S resources |
|
||||
|
||||
### `model_` — LLM / Model Management
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 8 | `model_get` | — | Get current active LLM config |
|
||||
| 9 | `model_set` | — | Update active LLM config and persist |
|
||||
| 10 | `model_list` | — | List available model IDs from provider |
|
||||
|
||||
### `nostr_` — Nostr Protocol Tools
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 11 | `nostr_post` | — | Publish a Nostr event to connected relays |
|
||||
| 12 | `nostr_post_readme` | — | Publish README.md as kind 30023 |
|
||||
| 13 | `nostr_file_md_to_longform_post` | — | Read markdown file and publish as kind 30023 longform |
|
||||
| 14 | `nostr_delete` | — | Request deletion of events — NIP-09 kind 5 |
|
||||
| 15 | `nostr_react` | — | React to event — NIP-25 kind 7 |
|
||||
| 16 | `nostr_query` | — | Query events from relays using a filter |
|
||||
| 17 | `nostr_profile_get` | — | Look up kind 0 profile by pubkey |
|
||||
| 18 | `nostr_nip05_lookup` | — | Look up/verify NIP-05 identifier |
|
||||
| 19 | `nostr_admin_profile` | `context_admin_profile` | Get admin kind 0 profile from cache |
|
||||
| 20 | `nostr_admin_contacts` | `context_admin_contacts` | Get admin kind 3 contacts from cache |
|
||||
| 21 | `nostr_admin_relays` | `context_admin_relays` | Get admin kind 10002 relay list from cache |
|
||||
| 22 | `nostr_admin_notes` | `context_admin_notes` | Get admin kind 1 recent notes from cache |
|
||||
| 23 | `nostr_dm_send` | — | Send NIP-04 encrypted DM |
|
||||
| 24 | `nostr_dm_send_nip17` | — | Send NIP-17 gift-wrap DM |
|
||||
| 25 | `nostr_encrypt` | — | Encrypt plaintext — NIP-44 |
|
||||
| 26 | `nostr_decrypt` | — | Decrypt ciphertext — NIP-44 |
|
||||
| 27 | `nostr_encode` | — | Encode entity into nostr: URI |
|
||||
| 28 | `nostr_decode` | — | Decode bech32/nostr: URI |
|
||||
| 29 | `nostr_pubkey` | — | Return agent pubkey hex |
|
||||
| 30 | `nostr_npub` | — | Return agent pubkey as npub |
|
||||
| 31 | `nostr_relay_status` | — | Get connection status for all relays |
|
||||
| 32 | `nostr_relay_info` | — | Fetch NIP-11 relay info document |
|
||||
| 33 | `nostr_list_manage` | — | Add/remove tags in replaceable list events — NIP-51 |
|
||||
|
||||
### `skill_` — Skill Management
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 34 | `skill_create` | — | Create/update skill definition |
|
||||
| 35 | `skill_list` | — | List agent published skills |
|
||||
| 36 | `skill_adopt` | — | Adopt a skill — add to kind 10123 |
|
||||
| 37 | `skill_remove` | — | Remove skill from adoption list |
|
||||
| 38 | `skill_search` | — | Search public skills |
|
||||
|
||||
### `task_` — Task Management
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 39 | `task_manage` | — | CRUD for agent task memory |
|
||||
| 40 | `task_list` | `context_tasks` | Get current task list — read-only |
|
||||
|
||||
### Template-Only (not in LLM schema)
|
||||
|
||||
| # | Name | Old Name | Description |
|
||||
|---|---|---|---|
|
||||
| 41 | `message_current` | `context_user_message` | Current user message text — template assembly only |
|
||||
|
||||
---
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| Old Name | New Name | Change Type |
|
||||
|---|---|---|
|
||||
| `context_admin_identity` | `admin_identity` | Rename |
|
||||
| `context_agent_identity` | `agent_identity` | Rename |
|
||||
| `context_admin_profile` | `nostr_admin_profile` | Rename + recategorize |
|
||||
| `context_admin_contacts` | `nostr_admin_contacts` | Rename + recategorize |
|
||||
| `context_admin_relays` | `nostr_admin_relays` | Rename + recategorize |
|
||||
| `context_admin_notes` | `nostr_admin_notes` | Rename + recategorize |
|
||||
| `context_tasks` | `task_list` | Rename + recategorize |
|
||||
| `context_user_message` | `message_current` | Rename + remove from LLM schema |
|
||||
| `my_version` | `agent_version` | Rename |
|
||||
| `shell_exec` | `local_shell_exec` | Add prefix |
|
||||
| `file_read` | `local_file_read` | Add prefix |
|
||||
| `file_write` | `local_file_write` | Add prefix |
|
||||
| `http_fetch` | `local_http_fetch` | Add prefix |
|
||||
|
||||
**Total: 13 renames, 0 removals, 0 additions**
|
||||
+10
-10
@@ -14,7 +14,7 @@ This plan covers four related features:
|
||||
When a message starts with `/`, bypass the LLM entirely and execute the tool directly.
|
||||
|
||||
```
|
||||
/shell_exec {"command": "ls -la"}
|
||||
/local_shell_exec {"command": "ls -la"}
|
||||
/nostr_query {"filter": {"kinds": [1], "limit": 5}}
|
||||
/nostr_nip05_lookup {"identifier": "jack@cash.app"}
|
||||
```
|
||||
@@ -160,8 +160,8 @@ When executing a skill from another author (not own pubkey), a **tool sandbox**
|
||||
- `skill_list`, `skill_search` — read skill metadata
|
||||
|
||||
**Blocked tools (destructive/dangerous):**
|
||||
- `shell_exec` — arbitrary command execution
|
||||
- `file_read`, `file_write` — filesystem access
|
||||
- `local_shell_exec` — arbitrary command execution
|
||||
- `local_file_read`, `local_file_write` — filesystem access
|
||||
- Any future tools marked as `destructive: true`
|
||||
|
||||
**Override:** The admin can explicitly opt in to full tool access:
|
||||
@@ -213,10 +213,10 @@ tool_schema:
|
||||
enum: [staging, production]
|
||||
default: staging
|
||||
steps:
|
||||
- tool: shell_exec
|
||||
- tool: local_shell_exec
|
||||
args:
|
||||
command: "make build TARGET={{target}}"
|
||||
- tool: shell_exec
|
||||
- tool: local_shell_exec
|
||||
args:
|
||||
command: "rsync -av dist/ server:/var/www/{{target}}/"
|
||||
- return: "Deployed to {{target}}"
|
||||
@@ -246,7 +246,7 @@ A simple sequential executor for hardened skills.
|
||||
```yaml
|
||||
steps:
|
||||
# Execute a tool
|
||||
- tool: shell_exec
|
||||
- tool: local_shell_exec
|
||||
args: {command: "ls -la"}
|
||||
save_as: listing # optional: save result to variable
|
||||
|
||||
@@ -260,7 +260,7 @@ steps:
|
||||
# Conditional - simple
|
||||
- if: "{{listing.success}}"
|
||||
then:
|
||||
- tool: shell_exec
|
||||
- tool: local_shell_exec
|
||||
args: {command: "echo done"}
|
||||
else:
|
||||
- return: "Failed: {{listing.error}}"
|
||||
@@ -325,9 +325,9 @@ typedef enum {
|
||||
| `skill_list` | safe | Read-only |
|
||||
| `skill_search` | safe | Read-only |
|
||||
| `skill_run` | safe | Recursive execution uses its own sandbox |
|
||||
| `shell_exec` | destructive | Arbitrary command execution |
|
||||
| `file_read` | destructive | Filesystem access |
|
||||
| `file_write` | destructive | Filesystem mutation |
|
||||
| `local_shell_exec` | destructive | Arbitrary command execution |
|
||||
| `local_file_read` | destructive | Filesystem access |
|
||||
| `local_file_write` | destructive | Filesystem mutation |
|
||||
|
||||
### Sandbox Rules
|
||||
|
||||
|
||||
+3
-13
@@ -1396,27 +1396,17 @@ static int append_recent_admin_dm_history(cJSON* messages, const char* current_m
|
||||
|
||||
cJSON* filter = cJSON_CreateObject();
|
||||
cJSON* kinds = cJSON_CreateArray();
|
||||
cJSON* authors = cJSON_CreateArray();
|
||||
cJSON* p_values = cJSON_CreateArray();
|
||||
if (!filter || !kinds || !authors || !p_values) {
|
||||
if (!filter || !kinds) {
|
||||
cJSON_Delete(filter);
|
||||
cJSON_Delete(kinds);
|
||||
cJSON_Delete(authors);
|
||||
cJSON_Delete(p_values);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(4));
|
||||
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||
cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->admin.pubkey));
|
||||
cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
|
||||
cJSON_AddItemToObject(filter, "authors", authors);
|
||||
cJSON_AddItemToArray(p_values, cJSON_CreateString(g_cfg->admin.pubkey));
|
||||
cJSON_AddItemToArray(p_values, cJSON_CreateString(g_cfg->keys.public_key_hex));
|
||||
cJSON_AddItemToObject(filter, "#p", p_values);
|
||||
cJSON_AddNumberToObject(filter, "limit", AGENT_HISTORY_QUERY_LIMIT);
|
||||
cJSON_AddNumberToObject(filter, "limit", AGENT_HISTORY_QUERY_LIMIT * 4);
|
||||
|
||||
char* events_json = nostr_handler_query_json(filter, 3000);
|
||||
char* events_json = nostr_handler_query_json(filter, 5000);
|
||||
cJSON_Delete(filter);
|
||||
if (!events_json) {
|
||||
return 0;
|
||||
|
||||
+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 0
|
||||
#define DIDACTYL_VERSION_PATCH 49
|
||||
#define DIDACTYL_VERSION "v0.0.49"
|
||||
#define DIDACTYL_VERSION_PATCH 50
|
||||
#define DIDACTYL_VERSION "v0.0.50"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
+72
-86
@@ -719,10 +719,10 @@ typedef struct {
|
||||
size_t cap;
|
||||
size_t max_bytes;
|
||||
int truncated;
|
||||
} http_fetch_buffer_t;
|
||||
} local_http_fetch_buffer_t;
|
||||
|
||||
static size_t http_fetch_write_cb(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
http_fetch_buffer_t* rb = (http_fetch_buffer_t*)userp;
|
||||
static size_t local_http_fetch_write_cb(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
local_http_fetch_buffer_t* rb = (local_http_fetch_buffer_t*)userp;
|
||||
size_t total = size * nmemb;
|
||||
if (!rb || total == 0) return total;
|
||||
|
||||
@@ -1090,7 +1090,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t3_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t3, "type", "function");
|
||||
cJSON_AddStringToObject(t3_fn, "name", "shell_exec");
|
||||
cJSON_AddStringToObject(t3_fn, "name", "local_shell_exec");
|
||||
cJSON_AddStringToObject(t3_fn, "description", "Execute a shell command and return stdout/stderr");
|
||||
cJSON_AddStringToObject(t3_params, "type", "object");
|
||||
cJSON_AddItemToObject(t3_params, "properties", t3_props);
|
||||
@@ -1112,7 +1112,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t4_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t4, "type", "function");
|
||||
cJSON_AddStringToObject(t4_fn, "name", "file_read");
|
||||
cJSON_AddStringToObject(t4_fn, "name", "local_file_read");
|
||||
cJSON_AddStringToObject(t4_fn, "description", "Read a local file as text from the configured working directory");
|
||||
cJSON_AddStringToObject(t4_params, "type", "object");
|
||||
cJSON_AddItemToObject(t4_params, "properties", t4_props);
|
||||
@@ -1137,7 +1137,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t5_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t5, "type", "function");
|
||||
cJSON_AddStringToObject(t5_fn, "name", "file_write");
|
||||
cJSON_AddStringToObject(t5_fn, "name", "local_file_write");
|
||||
cJSON_AddStringToObject(t5_fn, "description", "Write text content to a local file in the configured working directory");
|
||||
cJSON_AddStringToObject(t5_params, "type", "object");
|
||||
cJSON_AddItemToObject(t5_params, "properties", t5_props);
|
||||
@@ -1549,7 +1549,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t20_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t20, "type", "function");
|
||||
cJSON_AddStringToObject(t20_fn, "name", "my_version");
|
||||
cJSON_AddStringToObject(t20_fn, "name", "agent_version");
|
||||
cJSON_AddStringToObject(t20_fn, "description", "Return current Didactyl version and metadata from build macros");
|
||||
cJSON_AddStringToObject(t20_params, "type", "object");
|
||||
cJSON_AddItemToObject(t20_params, "properties", t20_props);
|
||||
@@ -1565,7 +1565,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t21_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t21, "type", "function");
|
||||
cJSON_AddStringToObject(t21_fn, "name", "http_fetch");
|
||||
cJSON_AddStringToObject(t21_fn, "name", "local_http_fetch");
|
||||
cJSON_AddStringToObject(t21_fn, "description", "Fetch HTTP(S) resources with optional method, headers, timeout, and body");
|
||||
cJSON_AddStringToObject(t21_params, "type", "object");
|
||||
cJSON_AddItemToObject(t21_params, "properties", t21_props);
|
||||
@@ -1947,7 +1947,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t37_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t37, "type", "function");
|
||||
cJSON_AddStringToObject(t37_fn, "name", "context_admin_identity");
|
||||
cJSON_AddStringToObject(t37_fn, "name", "admin_identity");
|
||||
cJSON_AddStringToObject(t37_fn, "description", "Build admin identity context block from cached runtime metadata");
|
||||
cJSON_AddStringToObject(t37_params, "type", "object");
|
||||
cJSON_AddItemToObject(t37_params, "properties", t37_props);
|
||||
@@ -1961,7 +1961,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t38_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t38, "type", "function");
|
||||
cJSON_AddStringToObject(t38_fn, "name", "context_admin_profile");
|
||||
cJSON_AddStringToObject(t38_fn, "name", "nostr_admin_profile");
|
||||
cJSON_AddStringToObject(t38_fn, "description", "Build admin profile context block from cached kind 0 metadata");
|
||||
cJSON_AddStringToObject(t38_params, "type", "object");
|
||||
cJSON_AddItemToObject(t38_params, "properties", t38_props);
|
||||
@@ -1975,7 +1975,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t39_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t39, "type", "function");
|
||||
cJSON_AddStringToObject(t39_fn, "name", "context_admin_contacts");
|
||||
cJSON_AddStringToObject(t39_fn, "name", "nostr_admin_contacts");
|
||||
cJSON_AddStringToObject(t39_fn, "description", "Build admin contacts context block from cached kind 3 contact list");
|
||||
cJSON_AddStringToObject(t39_params, "type", "object");
|
||||
cJSON_AddItemToObject(t39_params, "properties", t39_props);
|
||||
@@ -1989,7 +1989,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t40_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t40, "type", "function");
|
||||
cJSON_AddStringToObject(t40_fn, "name", "context_admin_relays");
|
||||
cJSON_AddStringToObject(t40_fn, "name", "nostr_admin_relays");
|
||||
cJSON_AddStringToObject(t40_fn, "description", "Build admin relay context block from cached kind 10002 data");
|
||||
cJSON_AddStringToObject(t40_params, "type", "object");
|
||||
cJSON_AddItemToObject(t40_params, "properties", t40_props);
|
||||
@@ -2003,7 +2003,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t41_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t41, "type", "function");
|
||||
cJSON_AddStringToObject(t41_fn, "name", "context_admin_notes");
|
||||
cJSON_AddStringToObject(t41_fn, "name", "nostr_admin_notes");
|
||||
cJSON_AddStringToObject(t41_fn, "description", "Build admin notes context block from cached kind 1 notes");
|
||||
cJSON_AddStringToObject(t41_params, "type", "object");
|
||||
cJSON_AddItemToObject(t41_params, "properties", t41_props);
|
||||
@@ -2017,7 +2017,7 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON* t42_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t42, "type", "function");
|
||||
cJSON_AddStringToObject(t42_fn, "name", "context_tasks");
|
||||
cJSON_AddStringToObject(t42_fn, "name", "task_list");
|
||||
cJSON_AddStringToObject(t42_fn, "description", "Build current task list context block from tasks.json");
|
||||
cJSON_AddStringToObject(t42_params, "type", "object");
|
||||
cJSON_AddItemToObject(t42_params, "properties", t42_props);
|
||||
@@ -2025,27 +2025,13 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON_AddItemToObject(t42, "function", t42_fn);
|
||||
cJSON_AddItemToArray(tools, t42);
|
||||
|
||||
cJSON* t43 = cJSON_CreateObject();
|
||||
cJSON* t43_fn = cJSON_CreateObject();
|
||||
cJSON* t43_params = cJSON_CreateObject();
|
||||
cJSON* t43_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t43, "type", "function");
|
||||
cJSON_AddStringToObject(t43_fn, "name", "context_user_message");
|
||||
cJSON_AddStringToObject(t43_fn, "description", "Return current user message text for context template conversation section");
|
||||
cJSON_AddStringToObject(t43_params, "type", "object");
|
||||
cJSON_AddItemToObject(t43_params, "properties", t43_props);
|
||||
cJSON_AddItemToObject(t43_fn, "parameters", t43_params);
|
||||
cJSON_AddItemToObject(t43, "function", t43_fn);
|
||||
cJSON_AddItemToArray(tools, t43);
|
||||
|
||||
cJSON* t44 = cJSON_CreateObject();
|
||||
cJSON* t44_fn = cJSON_CreateObject();
|
||||
cJSON* t44_params = cJSON_CreateObject();
|
||||
cJSON* t44_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t44, "type", "function");
|
||||
cJSON_AddStringToObject(t44_fn, "name", "context_agent_identity");
|
||||
cJSON_AddStringToObject(t44_fn, "name", "agent_identity");
|
||||
cJSON_AddStringToObject(t44_fn, "description", "Build agent identity context block with pubkey and npub");
|
||||
cJSON_AddStringToObject(t44_params, "type", "object");
|
||||
cJSON_AddItemToObject(t44_params, "properties", t44_props);
|
||||
@@ -4216,7 +4202,7 @@ static char* execute_nostr_npub(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_context_admin_identity(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_admin_identity(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4252,7 +4238,7 @@ static char* execute_context_admin_identity(tools_context_t* ctx, const char* ar
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_context_admin_profile(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_nostr_admin_profile(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4291,7 +4277,7 @@ static char* execute_context_admin_profile(tools_context_t* ctx, const char* arg
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_context_admin_contacts(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_nostr_admin_contacts(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4330,7 +4316,7 @@ static char* execute_context_admin_contacts(tools_context_t* ctx, const char* ar
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_context_admin_relays(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_nostr_admin_relays(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4369,7 +4355,7 @@ static char* execute_context_admin_relays(tools_context_t* ctx, const char* args
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_context_admin_notes(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_nostr_admin_notes(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4410,17 +4396,17 @@ static char* execute_context_admin_notes(tools_context_t* ctx, const char* args_
|
||||
|
||||
static char* execute_task_manage(tools_context_t* ctx, const char* args_json);
|
||||
|
||||
static char* execute_context_tasks(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_task_list(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
char* listed = execute_task_manage(ctx, "{\"action\":\"list\"}");
|
||||
if (!listed) return json_error("context_tasks failed to list tasks");
|
||||
if (!listed) return json_error("task_list failed to list tasks");
|
||||
|
||||
cJSON* listed_root = cJSON_Parse(listed);
|
||||
if (!listed_root || !cJSON_IsObject(listed_root)) {
|
||||
cJSON_Delete(listed_root);
|
||||
free(listed);
|
||||
return json_error("context_tasks received invalid task_manage response");
|
||||
return json_error("task_list received invalid task_manage response");
|
||||
}
|
||||
|
||||
cJSON* tasks = cJSON_GetObjectItemCaseSensitive(listed_root, "tasks");
|
||||
@@ -4533,7 +4519,7 @@ static char* execute_context_tasks(tools_context_t* ctx, const char* args_json)
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_context_user_message(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_message_current(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4549,7 +4535,7 @@ static char* execute_context_user_message(tools_context_t* ctx, const char* args
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_context_agent_identity(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_agent_identity(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4580,7 +4566,7 @@ static char* execute_context_agent_identity(tools_context_t* ctx, const char* ar
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_my_version(const char* args_json) {
|
||||
static char* execute_agent_version(const char* args_json) {
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
cJSON_Delete(args);
|
||||
@@ -4602,7 +4588,7 @@ static char* execute_my_version(const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_http_fetch(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_local_http_fetch(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = parse_tool_args_json(args_json);
|
||||
@@ -4617,15 +4603,15 @@ static char* execute_http_fetch(tools_context_t* ctx, const char* args_json) {
|
||||
|
||||
if (!url || !cJSON_IsString(url) || !url->valuestring || url->valuestring[0] == '\0') {
|
||||
cJSON_Delete(args);
|
||||
return json_error("http_fetch requires string url");
|
||||
return json_error("local_http_fetch requires string url");
|
||||
}
|
||||
if (headers && !cJSON_IsArray(headers)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("http_fetch headers must be an array when provided");
|
||||
return json_error("local_http_fetch headers must be an array when provided");
|
||||
}
|
||||
if (body && !cJSON_IsString(body)) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("http_fetch body must be a string when provided");
|
||||
return json_error("local_http_fetch body must be a string when provided");
|
||||
}
|
||||
|
||||
const char* method_str = (method && cJSON_IsString(method) && method->valuestring && method->valuestring[0] != '\0')
|
||||
@@ -4634,7 +4620,7 @@ static char* execute_http_fetch(tools_context_t* ctx, const char* args_json) {
|
||||
|
||||
if (body && cJSON_IsString(body) && body->valuestring && strcasecmp(method_str, "GET") == 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("http_fetch GET requests cannot include body");
|
||||
return json_error("local_http_fetch GET requests cannot include body");
|
||||
}
|
||||
|
||||
int timeout_seconds = (timeout && cJSON_IsNumber(timeout)) ? (int)timeout->valuedouble : 20;
|
||||
@@ -4648,10 +4634,10 @@ static char* execute_http_fetch(tools_context_t* ctx, const char* args_json) {
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("http_fetch failed to initialize curl");
|
||||
return json_error("local_http_fetch failed to initialize curl");
|
||||
}
|
||||
|
||||
http_fetch_buffer_t rb;
|
||||
local_http_fetch_buffer_t rb;
|
||||
memset(&rb, 0, sizeof(rb));
|
||||
rb.max_bytes = (size_t)max_bytes;
|
||||
|
||||
@@ -4671,10 +4657,10 @@ static char* execute_http_fetch(tools_context_t* ctx, const char* args_json) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url->valuestring);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout_seconds);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_fetch_write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, local_http_fetch_write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rb);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_headers);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "didactyl/http_fetch");
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "didactyl/local_http_fetch");
|
||||
|
||||
const char* ca_bundle = detect_ca_bundle_path_for_tools();
|
||||
if (ca_bundle) {
|
||||
@@ -4746,7 +4732,7 @@ static char* execute_http_fetch(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_shell_exec(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_local_shell_exec(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
if (!ctx->cfg->tools.shell.enabled) return json_error("shell tool disabled");
|
||||
|
||||
@@ -4756,7 +4742,7 @@ static char* execute_shell_exec(tools_context_t* ctx, const char* args_json) {
|
||||
cJSON* command = cJSON_GetObjectItemCaseSensitive(args, "command");
|
||||
if (!command || !cJSON_IsString(command) || !command->valuestring || command->valuestring[0] == '\0') {
|
||||
cJSON_Delete(args);
|
||||
return json_error("shell_exec requires string command");
|
||||
return json_error("local_shell_exec requires string command");
|
||||
}
|
||||
|
||||
const char* cwd = ctx->cfg->tools.shell.working_directory[0] != '\0'
|
||||
@@ -4847,7 +4833,7 @@ static char* execute_shell_exec(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_file_read(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_local_file_read(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
|
||||
@@ -4857,7 +4843,7 @@ static char* execute_file_read(tools_context_t* ctx, const char* args_json) {
|
||||
cJSON* maxb = cJSON_GetObjectItemCaseSensitive(args, "max_bytes");
|
||||
if (!path || !cJSON_IsString(path) || !path->valuestring) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_read requires string path");
|
||||
return json_error("local_file_read requires string path");
|
||||
}
|
||||
|
||||
int hard_max = ctx->cfg->tools.shell.max_output_bytes > 0 ? ctx->cfg->tools.shell.max_output_bytes : 65536;
|
||||
@@ -4867,12 +4853,12 @@ static char* execute_file_read(tools_context_t* ctx, const char* args_json) {
|
||||
char file_path[PATH_MAX];
|
||||
if (build_tool_path(ctx, path->valuestring, file_path, sizeof(file_path)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_read path is not allowed");
|
||||
return json_error("local_file_read path is not allowed");
|
||||
}
|
||||
|
||||
FILE* fp = fopen(file_path, "rb");
|
||||
cJSON_Delete(args);
|
||||
if (!fp) return json_error("file_read failed to open file");
|
||||
if (!fp) return json_error("local_file_read failed to open file");
|
||||
|
||||
char* buf = (char*)calloc((size_t)max_bytes + 1U, 1U);
|
||||
if (!buf) {
|
||||
@@ -4903,7 +4889,7 @@ static char* execute_file_read(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_file_write(tools_context_t* ctx, const char* args_json) {
|
||||
static char* execute_local_file_write(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
|
||||
@@ -4915,13 +4901,13 @@ static char* execute_file_write(tools_context_t* ctx, const char* args_json) {
|
||||
if (!path || !cJSON_IsString(path) || !path->valuestring ||
|
||||
!content || !cJSON_IsString(content) || !content->valuestring) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_write requires string path and content");
|
||||
return json_error("local_file_write requires string path and content");
|
||||
}
|
||||
|
||||
char file_path[PATH_MAX];
|
||||
if (build_tool_path(ctx, path->valuestring, file_path, sizeof(file_path)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_write path is not allowed");
|
||||
return json_error("local_file_write path is not allowed");
|
||||
}
|
||||
|
||||
const char* content_str = content->valuestring;
|
||||
@@ -4930,11 +4916,11 @@ static char* execute_file_write(tools_context_t* ctx, const char* args_json) {
|
||||
|
||||
FILE* fp = fopen(file_path, do_append ? "ab" : "wb");
|
||||
cJSON_Delete(args);
|
||||
if (!fp) return json_error("file_write failed to open file");
|
||||
if (!fp) return json_error("local_file_write failed to open file");
|
||||
|
||||
size_t n = fwrite(content_str, 1, len, fp);
|
||||
fclose(fp);
|
||||
if (n != len) return json_error("file_write failed to write all bytes");
|
||||
if (n != len) return json_error("local_file_write failed to write all bytes");
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
@@ -5605,8 +5591,8 @@ char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* arg
|
||||
if (strcmp(tool_name, "nostr_query") == 0) {
|
||||
return execute_nostr_query(args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "my_version") == 0) {
|
||||
return execute_my_version(args_json);
|
||||
if (strcmp(tool_name, "agent_version") == 0) {
|
||||
return execute_agent_version(args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "nostr_pubkey") == 0) {
|
||||
return execute_nostr_pubkey(ctx, args_json);
|
||||
@@ -5620,17 +5606,17 @@ char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* arg
|
||||
if (strcmp(tool_name, "my_npub") == 0) {
|
||||
return execute_nostr_npub(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "http_fetch") == 0) {
|
||||
return execute_http_fetch(ctx, args_json);
|
||||
if (strcmp(tool_name, "local_http_fetch") == 0) {
|
||||
return execute_local_http_fetch(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "shell_exec") == 0) {
|
||||
return execute_shell_exec(ctx, args_json);
|
||||
if (strcmp(tool_name, "local_shell_exec") == 0) {
|
||||
return execute_local_shell_exec(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "file_read") == 0) {
|
||||
return execute_file_read(ctx, args_json);
|
||||
if (strcmp(tool_name, "local_file_read") == 0) {
|
||||
return execute_local_file_read(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "file_write") == 0) {
|
||||
return execute_file_write(ctx, args_json);
|
||||
if (strcmp(tool_name, "local_file_write") == 0) {
|
||||
return execute_local_file_write(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "skill_create") == 0) {
|
||||
return execute_skill_create(ctx, args_json);
|
||||
@@ -5672,29 +5658,29 @@ char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* arg
|
||||
if (strcmp(tool_name, "task_manage") == 0) {
|
||||
return execute_task_manage(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_admin_identity") == 0) {
|
||||
return execute_context_admin_identity(ctx, args_json);
|
||||
if (strcmp(tool_name, "admin_identity") == 0) {
|
||||
return execute_admin_identity(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_admin_profile") == 0) {
|
||||
return execute_context_admin_profile(ctx, args_json);
|
||||
if (strcmp(tool_name, "nostr_admin_profile") == 0) {
|
||||
return execute_nostr_admin_profile(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_admin_contacts") == 0) {
|
||||
return execute_context_admin_contacts(ctx, args_json);
|
||||
if (strcmp(tool_name, "nostr_admin_contacts") == 0) {
|
||||
return execute_nostr_admin_contacts(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_admin_relays") == 0) {
|
||||
return execute_context_admin_relays(ctx, args_json);
|
||||
if (strcmp(tool_name, "nostr_admin_relays") == 0) {
|
||||
return execute_nostr_admin_relays(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_admin_notes") == 0) {
|
||||
return execute_context_admin_notes(ctx, args_json);
|
||||
if (strcmp(tool_name, "nostr_admin_notes") == 0) {
|
||||
return execute_nostr_admin_notes(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_tasks") == 0) {
|
||||
return execute_context_tasks(ctx, args_json);
|
||||
if (strcmp(tool_name, "task_list") == 0) {
|
||||
return execute_task_list(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_user_message") == 0) {
|
||||
return execute_context_user_message(ctx, args_json);
|
||||
if (strcmp(tool_name, "message_current") == 0) {
|
||||
return execute_message_current(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "context_agent_identity") == 0) {
|
||||
return execute_context_agent_identity(ctx, args_json);
|
||||
if (strcmp(tool_name, "agent_identity") == 0) {
|
||||
return execute_agent_identity(ctx, args_json);
|
||||
}
|
||||
|
||||
return json_error("unknown tool");
|
||||
|
||||
Reference in New Issue
Block a user