plugin: add harness/call-tool so plugins can invoke dirge's tools - #1
Open
wayniacal wants to merge 1 commit into
Open
plugin: add harness/call-tool so plugins can invoke dirge's tools#1wayniacal wants to merge 1 commit into
wayniacal wants to merge 1 commit into
Conversation
Plugins could register tools and intercept them, but not call one. A plugin
that wanted a tool's output had to reimplement it — and therefore reimplement
its permission checks — and could never reach MCP or semantic tools at all.
Adds three Janet functions, modelled on the LSP bridge:
(harness/tools?) bridge live?
(harness/list-tools) JSON of {name, description, parameters}
(harness/call-tool name args) -> @{:ok bool :output string}
src/plugin/tool_bridge.rs holds the policy and the tokio responder; the FFI
sits in worker.rs with the other C functions. The registry is republished on
every agent build rather than captured once, since the agent is rebuilt at run
boundaries and MCP tools attach late.
Permission checks are unaffected: check_perm* runs inside each tool, so
dispatching straight to LoopTool::execute keeps the gate. Verified — under
--restrictive a plugin's bash and write return "Permission denied by user" and
nothing runs.
Two calls are refused rather than attempted, both of which would hang:
plugin-registered tools (their handlers need the Janet worker, which is
blocked awaiting the reply) and `task` (subagents run isolated from plugin
hooks). Hooks do not fire for bridged calls, for the same re-entrancy reason
maki's Emit::Silent exists.
Two deadlocks found by running it, not by reading it:
- The bridge must not touch the PluginManager lock. The hook dispatcher holds
it across the Janet call and it is not reentrant, so asking for it from a
harness C function — or from the responder the worker is blocked on — hangs
the agent. Plugin tool names are now cached from the build path instead.
- Headless --print dispatched on-prompt inline on the runtime thread, and the
runtime is flavor = "current_thread". Any harness bridge that waits on a
reply from the runtime could never be answered. Now dispatched via
spawn_blocking, matching the tool hooks and the TUI path. This also fixes
harness/lsp and harness/confirm from on-prompt under -p.
Adds docs/plugins.md coverage, plugins/call_tool_example.janet, refusal and
flatten unit tests, and Janet-level tests that the symbols exist and degrade
to nil when the bridge is unwired.
wayniacal
force-pushed
the
feat/harness-call-tool
branch
from
August 24, 2026 09:40
64ea00b to
c2ad211
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review copy inside the fork — not a PR against
dirge-code/dirge.Lets a Janet plugin call dirge's own tools instead of reimplementing them:
Permission checks are unaffected —
check_perm*runs inside each tool, sodispatching to
LoopTool::executekeeps the gate. Verified: under--restrictivea plugin'sbashreturns "Permission denied by user" andnothing runs.
Two calls are refused rather than attempted because both would hang:
plugin-registered tools (their handlers need the Janet worker, which is
blocked awaiting the reply) and
task.Two behaviour changes beyond the headline feature, for the reviewer:
--printdispatchedon-promptinline on aflavor = "current_thread"runtime, so anything waiting on that runtimecould never be answered. Now
spawn_blocking, matching the tool hooks andthe TUI path. This also fixes
harness/lspandharness/confirmfromon-promptunder-p.the
PluginManagerlock — the hook dispatcher holds it across the Janetcall and it is not reentrant.
Rebased onto
33c95c5dand builds clean with--features pluginand--no-default-features --features no-plugin.cargo testhas never been run(the dev box OOMs linking the test binary);
cargo check --profile testpasses.