feat(agent-opencode): hooks-based activity detection#2030
feat(agent-opencode): hooks-based activity detection#2030harshitsinghbhandari wants to merge 3 commits into
Conversation
…platform-event hooks OpenCode exposes a plugin/event system under .opencode/plugins/ that streams lifecycle events. AO previously inferred activity by regex-matching terminal output, and waiting_input had no authoritative source. - setupWorkspaceHooks installs an auto-loaded activity plugin into the workspace's .opencode/plugins/ and excludes it from git (worktree-aware info/exclude) so it stays out of the agent's PRs. - Plugin maps permission.asked -> waiting_input, session.error -> blocked, session.idle -> ready, tool/file/message events -> active, writing to .ao/activity.jsonl with source "hook". No-ops without AO_SESSION_ID; honors AO_OPENCODE_HOOK_ACTIVITY=0 opt-out. - getActivityState prefers fresh hook entries over the polled session-list API for all states; API remains the fallback. - recordActivity removed so terminal writes can't shadow hook events (mirrors Claude Code AgentWrapper#1941); detectActivity stays as the lifecycle's last resort. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the OpenCode activity plugin source + install/git-exclude helpers out of index.ts into activity-plugin.ts, mirroring agent-claude-code's activity-detection.ts split. Keeps index.ts focused on the Agent interface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the OpenCode agent plugin to derive activity state from authoritative OpenCode lifecycle events (via an auto-loaded workspace plugin) instead of fragile terminal-regex inference, aligning OpenCode with the hook-driven model used for other agents.
Changes:
- Install a generated
.opencode/plugins/ao-activity.jsplugin viasetupWorkspaceHooks, and best-effort exclude it from git viainfo/exclude. - Prefer
source: "hook"JSONL entries ingetActivityState(including for active/ready/idle via age-decay fallback), while removingrecordActivityto avoid terminal-driven shadow writes. - Add unit + integration tests to validate plugin installation, git-exclude behavior, and the real generated plugin’s event→state mapping/dedup/guards.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/plugins/agent-opencode/src/index.ts | Adds hook plugin source + installer, updates getActivityState to prioritize hook entries, removes recordActivity. |
| packages/plugins/agent-opencode/src/index.test.ts | Adds tests for plugin install + git exclude idempotence; updates recordActivity expectations. |
| packages/plugins/agent-opencode/src/activity-plugin.integration.test.ts | New integration test that executes the generated plugin against synthetic events and asserts JSONL output. |
| .changeset/opencode-activity-hooks.md | Publishes a minor release note for the OpenCode hook-based activity detection change. |
Greptile SummaryThis PR replaces fragile terminal-regex activity inference for the OpenCode agent with an authoritative hook-based model, mirroring what was already shipped for Claude Code (#1941) and Codex. An auto-loaded plugin is installed into
Confidence Score: 5/5Safe to merge — the change is additive and the fallback chain means a plugin install failure cannot regress activity detection below the pre-existing session-list API baseline. The hook priority logic in getActivityState is sound: checkActivityLogState returns waiting_input/blocked first, then step 1b promotes only source === 'hook' entries over the polled API, and the session-list API + age-decay fallback remain for all other cases. No existing behavior is silently removed. No files require special attention.
|
| Filename | Overview |
|---|---|
| packages/plugins/agent-opencode/src/activity-plugin.ts | New file: implements plugin install + git-exclude logic. Uses isWindows() correctly, handles absolute vs relative exclude paths, wraps all I/O in best-effort try/catch where appropriate. |
| packages/plugins/agent-opencode/src/index.ts | getActivityState updated to prefer hook entries over the session-list API (step 1b); recordActivity removed; setupWorkspaceHooks delegates to installOpenCodeActivityPlugin. |
| packages/plugins/agent-opencode/src/activity-plugin.integration.test.ts | New integration test executes the real generated plugin source against synthetic events. Covers event mapping, env guards, and deduplication. |
| packages/plugins/agent-opencode/src/index.test.ts | New tests for setupWorkspaceHooks, hook-priority in getActivityState, and recordActivity removal. Windows branch tested via mockIsWindows. |
| .changeset/opencode-activity-hooks.md | Minor version bump with accurate changelog. |
Reviews (2): Last reviewed commit: "fix(agent-opencode): address PR review o..." | Re-trigger Greptile
- Fix stale step-1 comment in getActivityState (recordActivity is gone; the
hook plugin is the authoritative JSONL writer).
- Pass a realistic WorkspaceHooksConfig ({ dataDir }) in setupWorkspaceHooks
tests instead of an empty object, matching the production caller.
- Restore only the mutated process.env keys in the integration test's afterEach
instead of reassigning process.env wholesale.
- Add a Windows-path test for addToGitExclude covering git returning an
absolute exclude path (used verbatim, not re-joined onto the workspace).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed all review feedback in f7c1fd2:
107/107 tests pass; typecheck/build clean; 0 lint errors. |
Summary
Replaces OpenCode's fragile terminal-regex activity inference with authoritative platform events, mirroring the hook-driven model already shipped for Claude Code (#1941) and Codex. Partially addresses #19 (richer activity detection).
OpenCode exposes a plugin/event system (
.opencode/plugins/, auto-loaded at startup) that streams 25+ lifecycle events. Critically,permission.askedis the only authoritative source ofwaiting_inputOpenCode offers — terminal regex was guessing before.What changed
setupWorkspaceHooksinstalls an auto-loaded activity plugin into the workspace's.opencode/plugins/ao-activity.jsand excludes it from git via a worktree-awareinfo/excludeentry, so it never lands in the agent's PRs (best-effort)..ao/activity.jsonlwithsource: "hook":permission.asked→waiting_inputsession.error→blockedsession.idle→ready(neveridle— AO age-decay derives idle)tool.execute.*/file.edited/message.updated→active(coalesced to bound JSONL growth)AO_SESSION_ID; honorsAO_OPENCODE_HOOK_ACTIVITY=0opt-out.getActivityStatenow prefers fresh hook entries over the polledopencode session listAPI for every state; the session-list API remains the fallback when no hook entry exists.recordActivityremoved — the plugin is the sole JSONL writer, so terminal-derived writes can no longer shadow authoritative hook events (mirrors Claude RFC: replace Claude Code terminal-regex activity detection with hooks (and a hash-delta safety net) #1941). The terminaldetectActivityclassifier stays as the lifecycle's last resort.Design decisions (confirmed with maintainer)
.opencode/plugins/+ gitignored (vs. global install oropencode.jsonedit) — auto-loaded, zero config-file edits.Test plan
pnpm --filter @aoagents/ao-plugin-agent-opencode test— 106/106 passgetActivityState,recordActivityremovalpnpm build+pnpm typecheckgreen across all packages;pnpm lint0 errorswaiting_inputon a permission prompt🤖 Generated with Claude Code