Skip to content

feat(agent-opencode): hooks-based activity detection#2030

Open
harshitsinghbhandari wants to merge 3 commits into
AgentWrapper:mainfrom
harshitsinghbhandari:session/ao-185
Open

feat(agent-opencode): hooks-based activity detection#2030
harshitsinghbhandari wants to merge 3 commits into
AgentWrapper:mainfrom
harshitsinghbhandari:session/ao-185

Conversation

@harshitsinghbhandari
Copy link
Copy Markdown
Contributor

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.asked is the only authoritative source of waiting_input OpenCode offers — terminal regex was guessing before.

What changed

  • setupWorkspaceHooks installs an auto-loaded activity plugin into the workspace's .opencode/plugins/ao-activity.js and excludes it from git via a worktree-aware info/exclude entry, so it never lands in the agent's PRs (best-effort).
  • The plugin maps events → states and appends to .ao/activity.jsonl with source: "hook":
    • permission.askedwaiting_input
    • session.errorblocked
    • session.idleready (never idle — AO age-decay derives idle)
    • tool.execute.* / file.edited / message.updatedactive (coalesced to bound JSONL growth)
    • No-ops without AO_SESSION_ID; honors AO_OPENCODE_HOOK_ACTIVITY=0 opt-out.
  • getActivityState now prefers fresh hook entries over the polled opencode session list API for every state; the session-list API remains the fallback when no hook entry exists.
  • recordActivity removed — 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 terminal detectActivity classifier stays as the lifecycle's last resort.

Design decisions (confirmed with maintainer)

  • Plugin installed project-local in .opencode/plugins/ + gitignored (vs. global install or opencode.json edit) — auto-loaded, zero config-file edits.
  • Hooks win over the polled API for all states, not just the actionable ones.

Test plan

  • pnpm --filter @aoagents/ao-plugin-agent-opencode test — 106/106 pass
  • New integration test executes the real generated plugin against synthetic events (mapping, env guards, dedup)
  • New unit tests: plugin install + git-exclude (idempotent, best-effort), hook-priority in getActivityState, recordActivity removal
  • pnpm build + pnpm typecheck green across all packages; pnpm lint 0 errors
  • Manual: run an OpenCode session and confirm dashboard reflects waiting_input on a permission prompt

Note: pre-existing local tracker-linear test failures are an unrelated missing optional dep (@composio/core), not touched by this PR.

🤖 Generated with Claude Code

…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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.js plugin via setupWorkspaceHooks, and best-effort exclude it from git via info/exclude.
  • Prefer source: "hook" JSONL entries in getActivityState (including for active/ready/idle via age-decay fallback), while removing recordActivity to 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.

Comment thread packages/plugins/agent-opencode/src/index.test.ts
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This 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 .opencode/plugins/ao-activity.js that maps OpenCode's 25+ lifecycle events to AO activity states and appends them to .ao/activity.jsonl with source: "hook". getActivityState is updated to prefer fresh hook entries over the polled opencode session list API.

  • Plugin install & git exclude: setupWorkspaceHooks now calls installOpenCodeActivityPlugin, which writes the plugin source and, best-effort, adds it to the worktree-aware info/exclude so it doesn't appear in agent PRs.
  • Event → state mapping: permission.askedwaiting_input, session.errorblocked, session.idleready, tool/file/message events → active (coalesced at 5 s); no-ops without AO_SESSION_ID and honors AO_OPENCODE_HOOK_ACTIVITY=0.
  • recordActivity removed: the plugin is the sole JSONL writer; terminal-derived writes that could shadow authoritative hook events are gone.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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

Comment thread packages/plugins/agent-opencode/src/activity-plugin.ts
- 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>
@harshitsinghbhandari
Copy link
Copy Markdown
Contributor Author

Addressed all review feedback in f7c1fd2:

  • Stale step-1 comment (index.ts): rewritten — the JSONL is now written by the hook plugin (not the removed recordActivity), and waiting_input/blocked have an authoritative hook source.
  • WorkspaceHooksConfig (index.test.ts): tests now pass { dataDir: ws } instead of {}, matching the production caller.
  • process.env restore (integration test): restores only the mutated keys individually instead of reassigning process.env wholesale.
  • Windows path coverage (addToGitExclude): added a test forcing the Windows branch and feeding git an absolute exclude path, asserting it's used verbatim.

107/107 tests pass; typecheck/build clean; 0 lint errors.

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.

2 participants