Skip to content

plugin: add harness/call-tool so plugins can invoke dirge's tools - #1

Open
wayniacal wants to merge 1 commit into
mainfrom
feat/harness-call-tool
Open

plugin: add harness/call-tool so plugins can invoke dirge's tools#1
wayniacal wants to merge 1 commit into
mainfrom
feat/harness-call-tool

Conversation

@wayniacal

Copy link
Copy Markdown
Owner

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:

(harness/tools?)                 bridge live?
(harness/list-tools)             JSON of {name, description, parameters}
(harness/call-tool name args)    -> @{:ok bool :output string}

Permission checks are unaffected — check_perm* runs inside each tool, so
dispatching to LoopTool::execute keeps the gate. Verified: under
--restrictive a plugin's bash returns "Permission denied by user" and
nothing 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:

  1. Headless --print dispatched on-prompt inline on a
    flavor = "current_thread" runtime, so anything waiting on that runtime
    could never be answered. Now spawn_blocking, matching the tool hooks and
    the TUI path. This also fixes harness/lsp and harness/confirm from
    on-prompt under -p.
  2. Plugin tool names are cached at agent-build time so the bridge never takes
    the PluginManager lock — the hook dispatcher holds it across the Janet
    call and it is not reentrant.

Rebased onto 33c95c5d and builds clean with --features plugin and
--no-default-features --features no-plugin. cargo test has never been run
(the dev box OOMs linking the test binary); cargo check --profile test
passes.

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
wayniacal force-pushed the feat/harness-call-tool branch from 64ea00b to c2ad211 Compare August 24, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant