Skip to content

feat(opencode): monitor delivery via opencode-sentinel plugin - #547

Closed
tsukimiya wants to merge 6 commits into
fujibee:mainfrom
tsukimiya:feat/opencode-monitor
Closed

feat(opencode): monitor delivery via opencode-sentinel plugin#547
tsukimiya wants to merge 6 commits into
fujibee:mainfrom
tsukimiya:feat/opencode-monitor

Conversation

@tsukimiya

Copy link
Copy Markdown
Contributor

What

Adds monitor mode to the opencode agent type, routing real-time delivery through the opencode-sentinel plugin's sentinel_monitor tool (same shape as Claude Code's Monitor tool). Falls back to turn-mode self-checks when the plugin is absent.

Why

opencode has no built-in Monitor-tool equivalent, so until now it could only use turn or off — messages wait until the next tool call to surface. With the external opencode-sentinel plugin installed, opencode sessions gain a resident command-runner whose stdout lines arrive as steer notifications even in idle sessions — exactly what agmsg's watch.sh needs to stream incoming messages in real time.

The rule instructs the agent to launch watch.sh under sentinel_monitor at session start (or on /agmsg), passing $SENTINEL_SESSION_ID so the watcher persists its watermark per session. watch.sh is runtime-agnostic and tolerates an empty session-id arg, so the command is safe even when the variable is unset. A fallback (turn-mode self-poll) is included for when sentinel_monitor is unavailable.

Scope

  • scripts/drivers/types/opencode/type.conf: monitor=yes, delivery_modes=monitor turn off
  • scripts/drivers/types/opencode/_delivery.sh: monitor case writes the sentinel_monitor rule; status reports mode from rule content
  • scripts/drivers/types/opencode/template.md: monitor added to the mode picker, plus an "ensure monitor is running" step and the actas/drop watcher-switch flow
  • tests/test_delivery.bats: monitor-mode coverage (rule content, status, turn→monitor rewrite, both still rejected)
  • README.md / README.ja.md / docs/opencode.md: monitor listed as supported via the plugin, with the turn-fallback note

Depends on the companion spawn PR (#546) — the two commits below it are that PR's.

Tests

bats tests/test_delivery.bats -f opencode → 9/9 pass.

Related

… run --interactive

opencode 1.17.15's `opencode run --interactive` exits as soon as the boot
prompt's turn completes, so the spawned worker never stays resident and
cannot receive further agmsg messages. `opencode --prompt "<text>"` (TUI
mode) auto-sends the initial prompt and keeps the TUI resident, confirmed
by live testing. Switch the manifest to the existing prompt_arg mechanism
already used by copilot/antigravity instead of a fixed multi-word cli
prefix.
Companion to the previous spawn --prompt switch: updates README.md,
README.ja.md, and docs/opencode.md so they no longer claim spawn is
unsupported for opencode. monitor/both stay listed as unsupported
(real-time push is a separate concern, addressed in a follow-up).
Copilot AI review requested due to automatic review settings July 29, 2026 14:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new monitor delivery mode for the OpenCode agent type by generating an OpenCode rule that instructs use of the external opencode-sentinel plugin’s sentinel_monitor tool to run watch.sh, with a documented fallback to turn-based self-checks when the plugin/tools aren’t available.

Changes:

  • Enable OpenCode monitor delivery mode (delivery_modes=monitor turn off) and generate a corresponding rule file + status detection.
  • Update the OpenCode command template and documentation to describe monitor-mode setup and watcher management flows.
  • Extend Bats coverage for OpenCode delivery and spawn command shape (--prompt).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/test_spawn.bats Updates spawn assertion to expect opencode --prompt form.
tests/test_delivery.bats Adds monitor-mode tests (rule content, status, rewrite behavior).
scripts/drivers/types/opencode/type.conf Enables monitor and switches spawn CLI to opencode + prompt_arg=--prompt.
scripts/drivers/types/opencode/template.md Adds monitor to the mode picker and documents how to start/switch the watcher.
scripts/drivers/types/opencode/_delivery.sh Implements monitor/turn/off rule writing and status derivation from rule content.
README.md Updates OpenCode support statement (monitor via plugin; spawn via --prompt).
README.ja.md Japanese README parity update for OpenCode support statement.
docs/opencode.md Documents monitor mode via opencode-sentinel and spawn via --prompt.
docs/agent-types.md Updates prompt_arg docs to include opencode’s --prompt.
Comments suppressed due to low confidence (2)

scripts/drivers/types/opencode/template.md:128

  • Same as the main monitor command: use "${SENTINEL_SESSION_ID:--}" so watch.sh receives a stable argv even if SENTINEL_SESSION_ID is empty/unset.
      - command: `~/.agents/skills/__SKILL_NAME__/scripts/watch.sh "$SENTINEL_SESSION_ID" "$(pwd)" opencode <name>`

scripts/drivers/types/opencode/template.md:139

  • Same as the main monitor command: use "${SENTINEL_SESSION_ID:--}" so watch.sh receives a stable argv even if SENTINEL_SESSION_ID is empty/unset.
   - command: `~/.agents/skills/__SKILL_NAME__/scripts/watch.sh "$SENTINEL_SESSION_ID" "$(pwd)" opencode`

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/drivers/types/opencode/_delivery.sh Outdated
2. Run `~/.agents/skills/__SKILL_NAME__/scripts/reset.sh "$(pwd)" opencode <name>` to remove that role's registration.
3. If the session's active FROM was `<name>`, clear that state.
4. Tell the user: "Dropped role `<name>` from this project."
4. **If delivery mode is `monitor`**: run `sentinel_list` to find a running monitor described `agmsg inbox stream`; if one is running in this session, stop it with `sentinel_stop` on its id, then relaunch it with the `sentinel_monitor` tool using the default (no 4th arg) subscription so receive covers the project's remaining roles:
Comment on lines +125 to +127
4. **If delivery mode is `monitor`**, switch the watcher to the new role so receive is restricted to it:
a. Run `sentinel_list` to find a running monitor described `agmsg inbox stream`; if one is running in this session, stop it with `sentinel_stop` on its id.
b. Launch a fresh watcher with the `sentinel_monitor` tool:
Comment thread scripts/drivers/types/opencode/template.md Outdated
Comment thread README.md Outdated
|---|---|---|---|
| **`monitor`** (default on Claude Code) | SessionStart hook → Monitor tool → blocking SQLite stream | ~5s | Claude Code users wanting real-time push |
| **`turn`** (default on Codex / Copilot CLI / OpenCode) | Stop hook fires `check-inbox.sh` between assistant turns | until your next interaction | Codex / Copilot CLI / OpenCode (no Monitor tool); Claude Code users on a quieter loop |
| **`turn`** (default on Codex / Copilot CLI / OpenCode) | Stop hook fires `check-inbox.sh` between assistant turns | until your next interaction | Codex / Copilot CLI / OpenCode users not running monitor; Claude Code users on a quieter loop |
Comment thread README.ja.md Outdated
|---|---|---|---|
| **`monitor`**(Claude Codeのデフォルト) | SessionStartフック → Monitorツール → ブロッキングSQLiteストリーム | 約5秒 | リアルタイムプッシュを望むClaude Codeユーザー |
| **`turn`**(Codex / Copilot CLI / OpenCodeのデフォルト) | アシスタントのターン間でStopフックが `check-inbox.sh` を発火 | 次のやり取りまで | Codex / Copilot CLI / OpenCode(Monitorツールなし)、より静かなループを好むClaude Codeユーザー |
| **`turn`**(Codex / Copilot CLI / OpenCodeのデフォルト) | アシスタントのターン間でStopフックが `check-inbox.sh` を発火 | 次のやり取りまで | monitorを実行していないCodex / Copilot CLI / OpenCodeユーザー、より静かなループを好むClaude Codeユーザー |
Without cmd_prefix=$, agmsg_actas_prompt falls back to the default '/',
producing '/agmsg actas <name>' in the boot script — which OpenCode's
TUI does not recognize (it invokes skills via '$agmsg', like codex and
gemini). The spawned worker would boot but never claim its role.

Also tightens the opencode spawn test to assert the '$agmsg actas'
pattern is present and '/<cmd> actas' is absent, mirroring the codex
fujibee#283 test — the old assertion checked --prompt and actas independently,
so a boot script with --prompt followed by a bare-positional
'/agmsg actas' would still pass.

Copilot review feedback on type.conf L7.
Route opencode's real-time delivery through the sentinel_monitor tool
(same shape as Claude Code's Monitor), with a turn-mode fallback when
the tool is unavailable.
Companion to the monitor-delivery implementation: updates README.md,
README.ja.md, and docs/opencode.md so the delivery-modes table and the
OpenCode section list monitor as supported (via the external
opencode-sentinel plugin), describe the turn fallback when the plugin
is absent, and drop the stale 'no Monitor tool' / 'not supported'
claims about monitor and spawn.
- watch.sh command now passes "${SENTINEL_SESSION_ID:--}" (4 sites:
  _delivery.sh + template.md x3) instead of "$SENTINEL_SESSION_ID", so
  launcher shells that drop a quoted-but-empty first arg don't shift
  later watch.sh parameters. watch.sh documents this hazard for
  GROK_SESSION_ID and recommends the same "${VAR:--}" shape.
- template.md actas/drop steps in monitor mode now explicitly say to
  skip the sentinel_* tool calls when the plugin is unavailable, instead
  of assuming the tools exist whenever mode=monitor.
- README.md / README.ja.md delivery-modes table: monitor row notes
  OpenCode requires the plugin; turn row scopes OpenCode to the
  plugin-not-installed case (the template picker now defaults to
  monitor when the plugin is present).

Copilot review feedback on fujibee#547.
@tsukimiya
tsukimiya force-pushed the feat/opencode-monitor branch from 410d8dd to e03fe89 Compare July 29, 2026 15:03
@tsukimiya

Copy link
Copy Markdown
Contributor Author

Thanks @copilot — all six comments addressed in e03fe89:

  • "$SENTINEL_SESSION_ID""${SENTINEL_SESSION_ID:--}" at all four sites (_delivery.sh + template.md x3). watch.sh documents that some launcher shells drop a quoted-but-empty first arg and shift later parameters; it recommends the ${VAR:--} sentinel (same shape it asks grok-build callers to use for GROK_SESSION_ID).
  • template.md actas/drop now explicitly say to skip the sentinel_* tool calls when the plugin is unavailable, instead of assuming the tools exist whenever mode=monitor.
  • README.md / README.ja.md delivery-modes table updated: the monitor row notes OpenCode requires the plugin; the turn row scopes OpenCode to the plugin-not-installed case (the template picker now defaults to monitor when the plugin is present).

The macos-latest failures (watch: restart delivers messages..., watch: persists a watermark file..., launcher: a replacement dispatcher...) are pre-existing flakiness on main#541 is still open for the watch-suite sleeps, and the launcher test is unrelated to this PR's changes. Ubuntu/Windows jobs are green.

fujibee pushed a commit that referenced this pull request Jul 31, 2026
- watch.sh command now passes "${SENTINEL_SESSION_ID:--}" (4 sites:
  _delivery.sh + template.md x3) instead of "$SENTINEL_SESSION_ID", so
  launcher shells that drop a quoted-but-empty first arg don't shift
  later watch.sh parameters. watch.sh documents this hazard for
  GROK_SESSION_ID and recommends the same "${VAR:--}" shape.
- template.md actas/drop steps in monitor mode now explicitly say to
  skip the sentinel_* tool calls when the plugin is unavailable, instead
  of assuming the tools exist whenever mode=monitor.
- README.md / README.ja.md delivery-modes table: monitor row notes
  OpenCode requires the plugin; turn row scopes OpenCode to the
  plugin-not-installed case (the template picker now defaults to
  monitor when the plugin is present).

Copilot review feedback on #547.

(cherry picked from commit e03fe89)
fujibee pushed a commit that referenced this pull request Jul 31, 2026
- watch.sh command now passes "${SENTINEL_SESSION_ID:--}" (4 sites:
  _delivery.sh + template.md x3) instead of "$SENTINEL_SESSION_ID", so
  launcher shells that drop a quoted-but-empty first arg don't shift
  later watch.sh parameters. watch.sh documents this hazard for
  GROK_SESSION_ID and recommends the same "${VAR:--}" shape.
- template.md actas/drop steps in monitor mode now explicitly say to
  skip the sentinel_* tool calls when the plugin is unavailable, instead
  of assuming the tools exist whenever mode=monitor.
- README.md / README.ja.md delivery-modes table: monitor row notes
  OpenCode requires the plugin; turn row scopes OpenCode to the
  plugin-not-installed case (the template picker now defaults to
  monitor when the plugin is present).

Copilot review feedback on #547.

(cherry picked from commit e03fe89)
@fujibee fujibee closed this in #570 Jul 31, 2026
@fujibee

fujibee commented Jul 31, 2026

Copy link
Copy Markdown
Owner

@tsukimiya

Both of these are in. Thank you — between them opencode goes from "spawn does not stay up" to a type with a real-time delivery path, and the analysis in each PR made them quick to review.

They landed from branches in this repository (#569 and #570) rather than by merging your PRs directly. That is mechanical, not a judgement: the bats suite was split into shards after your PRs last ran, which renamed the required status check, so the required context could never report on those heads and GitHub held them blocked regardless of content. The red macOS job you may have seen on #546 was not a real failure either — it was the watch: persists a watermark file flake, from a run that predates the fix for it. Rather than ask you to rebase for our CI churn, we moved the commits.

Your commits were cherry-picked, not rewritten, so they land under your authorship — you can see your name on all six on main. We also checked equivalence rather than assuming it: each commit's git patch-id --stable matches its original.

Two commits on #570 are ours, and we want to be explicit about what they changed and why.

Wording. The docs said monitor "never silently drops messages". agmsg writes the rule but does not detect whether sentinel_monitor exists, so the fallback is an instruction the agent follows rather than a code path agmsg enforces — an agent that ignores it delivers nothing, and nothing reports that. The text now says monitor degrades to turn, states what is and is not guaranteed, and names turn as the mode to choose when delivery must not depend on an agent honouring a rule. The design is unchanged and we think it is the right one for the constraint; only the promise was adjusted to match what the code can keep. We would rather tell you we edited your wording than have you find it later.

Quoting. The generated commands wrapped the project path in literal single quotes. delivery.sh accepts an apostrophe in a path because it is a legal POSIX character, and such a path ended the quoted argument early in both the watcher command and the fallback — which also made shell metacharacters after it live. The path now goes through printf '%q' once and is used in all three generated commands. The regression parses the generated lines with bash -n, so it fails on a broken quote rather than on a wording change. This is the same class as an existing gap we are tracking in #550; opencode is now off that list because it is fixed.

Also noting that the two points from the automated review — the missing cmd_prefix=$ and the test ordering — were already addressed in 762ad02 before we picked it up. That fix is what makes the spawned actas prompt use $agmsg rather than the Claude Code spelling, so it mattered.

Closing both in favour of #569 and #570. The work is yours; only the branches are ours.

@tsukimiya
tsukimiya deleted the feat/opencode-monitor branch July 31, 2026 15:07
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.

3 participants