refactor: make OpenCode adapter a thin wrapper routing through peon.sh#430
refactor: make OpenCode adapter a thin wrapper routing through peon.sh#430garysheng merged 2 commits intoPeonPing:mainfrom
Conversation
The OpenCode TypeScript plugin was a 950-line partial re-implementation of peon.sh: own sound playback, notifications, config, state management, pack loading, spam detection, debouncing, etc. This meant any feature added to peon.sh (trainer, TTS, new categories) had to be duplicated in TypeScript. Replace it with a 160-line thin adapter that: - Catches OpenCode events - Maps them to peon.sh hook_event_name (SessionStart, Stop, UserPromptSubmit, etc.) - Spawns peon.sh with the JSON payload on stdin This gives OpenCode users access to ALL peon-ping features automatically: trainer reminders, TTS, spam detection, SSH/devcontainer relay, pack rotation, desktop notifications, and any future features added to peon.sh. The installer now checks for peon.sh first and refuses to install without it, since the adapter is an add-on to peon-ping, not a standalone installation. Closes PeonPing#427 Closes PeonPing#428
|
@ImBIOS is attempting to deploy a commit to the Gary Sheng's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
This refactor is exactly right — the OpenCode adapter was way too much of a standalone re-implementation. The 950→160 line reduction speaks for itself, and routing through Tests are failing because The test suite needs to be updated to:
The core logic change is correct — just needs the test file to catch up with the new contract. |
enolive
left a comment
There was a problem hiding this comment.
added remarks for differences from my PR result.
other than that, at least two things are missing here:
- change the kilo adapter installer as well
- change the PS1 variants of the installers
- the vitest tests are not really covering anything inside this plugin. I fail to see why they couldn't tbh 😉
| "WezTerm", | ||
| "ghostty", | ||
| "Hyper", | ||
| const PEON_SH_PATHS = [ |
There was a problem hiding this comment.
my version of resolving the path done by Caaaarl was actually a little bit more sophisticated:
function resolvePeonSh(): string | null {
const candidates = [
process.env.PEON_DIR,
process.env.CLAUDE_PEON_DIR,
process.env.CLAUDE_CONFIG_DIR
? path.join(process.env.CLAUDE_CONFIG_DIR, "hooks", "peon-ping")
: null,
path.join(os.homedir(), ".claude", "hooks", "peon-ping"),
]
for (const dir of candidates) {
if (!dir) continue
const sh = path.join(dir, "peon.sh")
if (fs.existsSync(sh)) return sh
}
return null
}| // Tab Title | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| function setTabTitle(title: string): void { |
There was a problem hiding this comment.
peon.sh is also doing a thing with changing the tab title. In my PR attempt no tab title changes were done by the opencode adapter and it changed netherless. So is this really important?
| notifyTitle: `${projectName} \u2014 Error occurred`, | ||
| }) | ||
| setTabTitle(`\u25cf ${projectName}: error`) | ||
| firePeon("PostToolUseFailure") |
There was a problem hiding this comment.
most adapters (like for instance codex.sh) are also setting the tool_name and error properties in the payload if this kind of thing happens.
I fail to see it here
| notifyTitle: `${projectName} \u2014 Task complete`, | ||
| }) | ||
| setTabTitle(`\u25cf ${projectName}: done`) | ||
| firePeon("Stop") |
There was a problem hiding this comment.
I see that the call to start peon.sh is ignoring the session id from the opencoe event using instead its own. is this the correct behavior?
|
Pushed a fix for the failing bats tests: updated Changes:
All 9 opencode.bats tests pass locally. CI should be green now. |
Summary
Rewrite the OpenCode TypeScript plugin from a 950-line partial re-implementation of
peon.shinto a 160-line thin adapter that routes all events throughpeon.sh— the same pattern used by every other IDE adapter (Cursor, Codex, Windsurf, Kiro, etc.).Problem
The OpenCode plugin (
adapters/opencode/peon-ping.ts) duplicated massive amounts ofpeon.shlogic in TypeScript:Any feature added to
peon.shhad to be manually ported to TypeScript. This caused:peon.shwas never ported~/.config/opencode/peon-ping/config.jsonvs~/.claude/hooks/peon-ping/config.jsonpeon.shdidn't automatically reach OpenCode usersSolution
The new plugin is a thin adapter that:
peon.shhook event names (SessionStart, Stop, PostToolUseFailure, PermissionRequest, UserPromptSubmit)hook_event_name,cwd,session_id,notification_type,permission_mode,source)bash peon.shwith the JSON on stdinThis matches exactly how Cursor, Codex, Windsurf, and Kiro adapters work — they all translate IDE-specific events to the
peon.shJSON contract.Event Mapping
hook_event_namesession.created(no parent)SessionStartsession.status(busy/running)UserPromptSubmitsession.idleStopsession.errorPostToolUseFailurepermission.askedPermissionRequestOpenCode-specific features preserved
process.stdout.writeOSC escape sequences)parentIDare skipped)Changes
adapters/opencode/peon-ping.ts(950 → 160 lines)firePeon()— builds JSON payload and spawnspeon.shadapters/opencode.sh(190 → 80 lines)peon.shat~/.claude/hooks/peon-ping/peon.shor~/.openclaw/hooks/peon-ping/peon.shWhat OpenCode users gain automatically
peonCLITesting
bun build)bash -n)Closes
Closes #427
Closes #428