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:
Your Name
2026-03-08 18:54:59 -03:00
parent bbfdf70a77
commit c45c6dbfe5
14 changed files with 265 additions and 172 deletions
+9 -9
View File
@@ -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)
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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 |
---
+1 -1
View File
@@ -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 |
+4 -4
View File
@@ -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.
+27 -27
View File
@@ -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
+117
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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");