Repository navigation
feat: report from opencode and Crush sessions - #10
Merged
Merged
Conversation
Two more harnesses, and neither fits the script model the other two share — so each is met where it is rather than bent into the shape of the others. **opencode** runs no hook commands: a plugin there is a JavaScript module its server imports. Its client is one file, opencode/lich.js, posting the same four reports to the same endpoints off the event bus — the session id from `session.created`, `busy`/`done` from `session.status`, `waiting` from `permission.asked`, the tool line from `tool.execute.before`, the label from `session.updated`, and the git-status refresh from `file.edited`. Installing it is dropping the file in ~/.config/opencode/plugin/; there is no marketplace. Three things the module has to get right, all of them measured rather than assumed: - Sub-sessions are dropped by their `parentID`. The `task` tool runs a sub-agent as its own session, and its `idle` would otherwise end the turn on the card while the real one is still running. - The title a session is created with is a placeholder built from its timestamp, so the module holds that first value and reports only a title that differs from it — no matching on its wording. - opencode awaits its plugin hooks, so no report is ever awaited and nothing throws. That is this client's version of "always exit 0", and a test holds it: a listener that takes 3s must not hold the turn. **Crush** runs the existing scripts unchanged — its hooks are Claude Code-compatible down to the stdin payload — but `PreToolUse` is the only event it has. Only the two reports that event can honour are registered: the session id, and the git-status refresh on write tools. A `busy` with no `Stop` behind it would pin a spinner to a card, so state and title stay unregistered until Crush ships an end-of-turn event, at which point report-state.sh and report-tool.sh are already here. Crush has no plugin system either, so hooks/crush-hooks.json is a block to merge into the user's own crush.json, with <lich-plugin> where the clone's path goes. The suite splits so both clients answer to the same lines: tests/contract.mjs holds lich's fixtures, the assertions and the stub; tests/opencode.test.mjs drives the module through the payloads a real opencode run emits, including the ones it must drop.
3 tasks done
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.
Two more harnesses report to lich. Neither fits the script model the other two share, so each is met where it is rather than bent into the shape of the others.
Implements the contracts extended in omartelo/lich#217 — that PR merges first: the
fixtures-driftjob here diffstests/fixtures/against lich'smain, so it is red until then. The vendoredsession-start.jsonlin this branch is already the post-merge copy.opencode
opencode runs no hook commands: a plugin there is a JavaScript module its server imports. So its client is one file,
opencode/lich.js, posting the same four reports to the same endpoints off the event bus — the session id fromsession.created,busy/donefromsession.status,waitingfrompermission.asked, the tool line fromtool.execute.before, the label fromsession.updated, and the git-status refresh fromfile.edited. Installing it is dropping the file in~/.config/opencode/plugin/; there is no marketplace to go through.Three things the module has to get right, each measured rather than assumed:
parentID. Thetasktool runs a sub-agent as its own session, and itsidlewould otherwise end the turn on the card while the real one is still running.Crush
Crush runs the existing scripts unchanged — its hooks are Claude Code-compatible down to the stdin payload and the exit codes — but
PreToolUseis the only event it has. Only the two reports that event can honour are registered: the session id, and the git-status refresh on write tools.busyis deliberately left out. With noStopbehind it, a spinner would stay on the card until the next turn — wrong for longer than it is right. The day Crush ships an end-of-turn event,report-state.shandreport-tool.share already here and the registration grows two lines.Crush has no plugin system either, so
hooks/crush-hooks.jsonis a block to merge into the user's owncrush.json, with<lich-plugin>where the clone's path goes —commandis resolved against the working directory, not the config file, so a global install needs the absolute path.Tests
The suite splits so both clients answer to the same lines:
tests/contract.mjsholds lich's fixtures, the assertions and the stub;tests/hooks.test.mjskeeps spawning scripts (now including Crush's registration);tests/opencode.test.mjsimports the module and drives it through the payloads a real opencode run emits — including the sub-session ones it must drop, a listener answering 500, a refused connection, and unknown or malformed events.opencode/package.jsonexists only so Node readslich.jsas a module; opencode imports it through Bun, which needs no help.Test plan
node --test tests/*.test.mjs— 113 passcrush runin an isolatedXDG_CONFIG_HOMEagainst a stub listener, all three reports arriving on the one eventopencode servewith this module installed, driven through its HTTP API against a stub provider —/session-start,busy,busy+write/oc-probe.txt,/session-touched,waitinganddoneall arrived, and a rename produced/session-titleparentIDfilter fails 4 tests, reporting the placeholder title fails 1, mapping opencode'sidleto lich'sidlefails 1