Skip to content

feat: in-process Lua core handler for opencode (#47, phase 3)#65

Merged
Cannon07 merged 3 commits into
mainfrom
opencode-phase-3
May 20, 2026
Merged

feat: in-process Lua core handler for opencode (#47, phase 3)#65
Cannon07 merged 3 commits into
mainfrom
opencode-phase-3

Conversation

@Cannon07
Copy link
Copy Markdown
Owner

Summary

Phase 3 of #47 for OpenCode — flips the OpenCode hook entry off bin/core-pre-tool.sh / bin/core-post-tool.sh and 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 under backends/opencode/, which performs socket discovery and RPCs pre_tool.handle / post_tool.handle. All tool-name and camelCase→snake_case mapping moves out of TS into pre_tool.normalisers.opencode.

After this PR, only codex and copilot remain on the bash core handler.

Architectural decisions

Captured in docs/adr/0006-opencode-defers-os-independence-to-46.md. Highlights:

  • Bash shim stays. TS-native socket discovery was on the table — OpenCode's TypeScript plugin is natively cross-platform and could have been the first integration to escape bash. We rejected it: two divergent discovery implementations would force Request Windows 11 support #46 (centralised RPC/discovery rewrite) to reconcile them. OpenCode's OS-independence is deliberately deferred to Request Windows 11 support #46.
  • hookQueue kept, 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. The queue's load-bearing job changed; its existence didn't.
  • Both hooks await the RPC. 15s execSync timeout bounds stuck-nvim. Timeouts log at debug before being swallowed; shim-resolution failures do the same.
  • TS-side allowlist short-circuits read / glob / grep before the bash fork (OpenCode reads prolifically).
  • bin-path.txt now resolves to plugin root, not the bin/ 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_paths had a latent crash: gsub returns (string, count), so table.insert(paths, custom:gsub(...)) was triggering the (t, pos, value) 3-arg form of table.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)
  • Manual: edit existing file
  • Manual: create new file
  • Manual: delete and edit simultaneously (Bash + Edit)
  • Manual: ApplyPatch on multiple files (GPT-5.4 via OpenCode) — exposed the gsub bug, now fixed
  • Manual: rapid-fire multi-file proposals (hookQueue ordering)
  • Manual: rejection path closes previews and clears indicators
  • Manual: visible-only mode suppresses previews but keeps neo-tree indicators
  • Manual: nvim-unreachable → opencode falls back to native flow without hanging
  • Manual: relative filePath resolves against cwd

Pre-merge note

backends/opencode/index.ts:28 has a v<X.Y> / v<X+1>.0 placeholder for the transitional bin-path.txt fallback comment. Fill in with the release version this lands as before tagging.

🤖 Generated with Claude Code

Cannon07 and others added 3 commits May 20, 2026 17:31
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>
@Cannon07 Cannon07 merged commit 55c8fea into main May 20, 2026
2 checks passed
@Cannon07 Cannon07 deleted the opencode-phase-3 branch May 20, 2026 14:11
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