feat: in-process Lua core handler for opencode (#47, phase 3)#65
Merged
Conversation
Flips OpenCode's hook entry off the bash core handler and onto the in-process Lua orchestrator landed in #63. The TS plugin becomes a thin transport — collects {tool, args, directory} from each hook firing, pipes JSON into a per-agent shell shim under backends/opencode/, which performs socket discovery and RPCs pre_tool/post_tool.handle. Tool-name and camelCase→snake_case mapping moves out of TS and into pre_tool.normalisers.opencode. Decisions worth recording (see grill notes / ADR-0006): - Bash shim stays. TS-native socket discovery was rejected to avoid two divergent implementations that #46 would have to reconcile. OpenCode's OS-independence is deliberately deferred to #46. - hookQueue stays, with its rationale rewritten: in-process Lua serialises within nvim's main thread, but TS→nvim send-order can still reorder concurrent before(A)/after(B) firings during socket discovery. - Both hooks await the RPC; 15s execSync timeout bounds stuck-nvim. - TS-side allowlist short-circuits read/glob/grep before forking bash (OpenCode reads prolifically). - bin-path.txt now resolves to plugin root; legacy bin/ values get a transitional fallback in TS. Also fixes a latent crash in post_tool.patch_paths: gsub returns (string, count), so table.insert(paths, custom:gsub(...)) was hitting the (t, pos, value) 3-arg form. The unified-diff branch sidestepped it via assignment; the *** Update File: branch (GPT-class ApplyPatch) hit it head-on. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two refinements from review: 1. opencode normaliser now runs vim.fs.normalize() on the resolved filePath so ".." / "." segments collapse — matches the old TS plugin's path.resolve semantics. Without it, opencode active_diffs keys could be raw "/proj/../escape.txt" strings that don't compare equal to claudecode-shaped keys for the same logical file. No bug in practice today (single-backend round-trips stay consistent), but the divergence was real. 2. The *** Update File: regression test now asserts which paths get passed to diff.close_for_file (one per patched file, cwd-resolved absolute), not just that the call doesn't raise. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
Phase 3 of #47 for OpenCode — flips the OpenCode hook entry off
bin/core-pre-tool.sh/bin/core-post-tool.shand onto the in-process Lua orchestrator that landed for Claude Code in #63.The TS plugin becomes a thin transport: it collects
{tool, args, directory}from each hook firing, pipes JSON into a per-agent shell shim underbackends/opencode/, which performs socket discovery and RPCspre_tool.handle/post_tool.handle. All tool-name and camelCase→snake_case mapping moves out of TS intopre_tool.normalisers.opencode.After this PR, only
codexandcopilotremain on the bash core handler.Architectural decisions
Captured in
docs/adr/0006-opencode-defers-os-independence-to-46.md. Highlights:hookQueuekept, rationale rewritten. In-process Lua serialises within nvim's main thread, but TS→nvim send-order can still reorder concurrentbefore(A)/after(B)firings during socket discovery. The queue's load-bearing job changed; its existence didn't.awaitthe RPC. 15sexecSynctimeout bounds stuck-nvim. Timeouts log at debug before being swallowed; shim-resolution failures do the same.read/glob/grepbefore the bash fork (OpenCode reads prolifically).bin-path.txtnow resolves to plugin root, not thebin/directory. Legacy values get a transitional fallback in TS (backends/opencode/index.ts:28— comment marks it for removal in the next major).Bug fix bundled in
post_tool.patch_pathshad a latent crash:gsubreturns(string, count), sotable.insert(paths, custom:gsub(...))was triggering the(t, pos, value)3-arg form oftable.insert. The unified-diff branch sidestepped it via assignment; the*** Update File:branch (GPT-class ApplyPatch shape) hit it head-on. Hit during opencode multi-file ApplyPatch testing. Fix: force single-value return with parens. Regression test added.Test plan
tests/run.sh— all 68 tests pass (plugin Lua + claudecode + opencode + codex + copilot backends)gsubbug, now fixedfilePathresolves against cwdPre-merge note
backends/opencode/index.ts:28has av<X.Y>/v<X+1>.0placeholder for the transitionalbin-path.txtfallback comment. Fill in with the release version this lands as before tagging.🤖 Generated with Claude Code