From dcab52cd56761be4d483c8c004f0aea955be4f15 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 18 Aug 2026 05:20:57 +0900 Subject: [PATCH 1/3] feat(cursor): offer monitor delivery and name cursor-agent / grok pids (#856) Builtin cursor monitor is not liveness-gated unless the pid walk sees cursor-agent; grok-build has the same hole. Generated commands keep the existing '$project' wrap; apostrophe-safe quoting stays with #550. Co-authored-by: Cursor --- scripts/drivers/types/cursor/_delivery.sh | 70 +++++++++++++++++++++-- scripts/drivers/types/cursor/template.md | 44 ++++++++++---- scripts/drivers/types/cursor/type.conf | 3 +- scripts/lib/resolve-project.sh | 2 + tests/test_delivery.bats | 46 +++++++++++++-- tests/test_resolve_project.bats | 6 ++ 6 files changed, 149 insertions(+), 22 deletions(-) mode change 100644 => 100755 scripts/drivers/types/cursor/_delivery.sh diff --git a/scripts/drivers/types/cursor/_delivery.sh b/scripts/drivers/types/cursor/_delivery.sh old mode 100644 new mode 100755 index 1db4ba12e..c974974b8 --- a/scripts/drivers/types/cursor/_delivery.sh +++ b/scripts/drivers/types/cursor/_delivery.sh @@ -4,10 +4,10 @@ # The Cursor CLI auto-loads project rules from .cursor/rules/*.mdc. An .mdc with # `alwaysApply: true` in its frontmatter is applied on every turn, which is the # always-on instruction channel agmsg needs — the cursor-agent equivalent of -# gemini/opencode's markdown rules file. Only turn|off reach this function: -# cursor's manifest declares delivery_modes=turn off, so delivery.sh's central -# gate rejects monitor/both first. Uses resolve_hooks_file + SKILL_DIR from -# delivery.sh's sourced context. +# gemini/opencode's markdown rules file. delivery_modes is "turn monitor off" +# (both is rejected by delivery.sh before apply). monitor writes the same file +# telling the agent to launch watch.sh via Shell notify_on_output. Uses +# resolve_hooks_file + SKILL_DIR from delivery.sh's sourced context. agmsg_delivery_apply() { local type="$1" local project="$2" @@ -28,7 +28,67 @@ alwaysApply: true ## PostToolUse After each tool call, automatically check the agmsg inbox for unread messages. - Command: '$SKILL_DIR/scripts/check-inbox.sh' '$type' '$project' +EOF + elif [ "$mode" = "monitor" ]; then + mkdir -p "$(dirname "$rule_file")" + # The watch.sh command below passes "${CURSOR_CONVERSATION_ID:--}" + # (sentinel `-`), NOT "$CURSOR_CONVERSATION_ID": a host that re-evaluates + # the command with the var unset would drop a quoted-empty first arg and + # shift project/type. `-` survives as a real argument and watch.sh folds + # it into the empty-session-id path. + cat < "$rule_file" +--- +alwaysApply: true +--- + +# agmsg Integration Rule + +## Real-time inbox watcher + +At session start (or the first time you run /agmsg this session), launch a +resident inbox watcher with Shell + \`notify_on_output\` so incoming agmsg +messages wake this idle CLI session: + +- command: '$SKILL_DIR/scripts/watch.sh' "\${CURSOR_CONVERSATION_ID:--}" '$project' $type +- title: agmsg inbox stream +- notify_on_output.pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}T|^ERROR: + +Do not launch it more than once per session — if a watcher is already +streaming, skip this step. Do not use a plain background shell or \`/loop\`. EOF fi } -agmsg_delivery_status() { rulefile_status "$@"; } + +agmsg_delivery_status() { + local type="$1" project="$2" + local rule_file + rule_file="$(resolve_hooks_file "$type" "$project")" + if [ ! -f "$rule_file" ]; then + echo "mode: off" + elif grep -q "agmsg-delivery-mode: monitor" "$rule_file" 2>/dev/null; then + echo "mode: monitor" + else + echo "mode: turn" + fi +} + +agmsg_delivery_on_enable() { + local mode="$1" type="$2" project="$3" + [ "$mode" = "monitor" ] || return 0 + cat < cursor "$(pwd)"` - - Cursor CLI has no Monitor tool, so `monitor` and `both` modes are not offered here. + - `both` is not supported. + - If you chose `monitor`, follow the "Ensure monitor is running first" step below now. 6. Then check inbox for the newly joined team. @@ -80,6 +86,14 @@ Four possible outputs: **Only use scripts in `~/.agents/skills/__SKILL_NAME__/scripts/` — do not read or modify files under `teams/` or `db/` directly.** +**Ensure monitor is running first (monitor mode only).** If the project's delivery mode is `monitor` (check via `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh status cursor "$(pwd)"`) and no `agmsg inbox stream` watcher is already streaming into this session, launch one now with Shell + `notify_on_output` (`block_until_ms: 0`): + +- command: `~/.agents/skills/__SKILL_NAME__/scripts/watch.sh "${CURSOR_CONVERSATION_ID:--}" "$(pwd)" cursor` +- title: `agmsg inbox stream` +- `notify_on_output.pattern`: `^[0-9]{4}-[0-9]{2}-[0-9]{2}T|^ERROR:` + +Launch the watcher only once per session — if it is already streaming, do not start a second one. In `turn`/`off` mode there is no watcher; skip this. + **If no arguments provided (DEFAULT action — always do this when the command is invoked without arguments):** 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. @@ -113,22 +127,32 @@ If argument starts with "actas" followed by an agent name (e.g. "actas alice"): 1. Parse the new role name. If none was given (e.g. bare "actas", or the user asks you to suggest one), run `~/.agents/skills/__SKILL_NAME__/scripts/team.sh ` for each TEAM to see the current roster. Look for a naming convention already in play (e.g. a shared base name with role and number suffixes (`-`), or names derived from the team name) and, when one exists, propose 2-3 unused names that extend it; otherwise propose 2-3 short, distinctive identity names (not a bare tool-type label). Either way, names must not collide with the roster. Ask the user to pick one or type their own before continuing. 2. Run `~/.agents/skills/__SKILL_NAME__/scripts/identities.sh "$(pwd)" cursor` to see whether the role is already registered for this (project, type). 3. If the name does not appear in the output, join under the existing team. For a single team, run `~/.agents/skills/__SKILL_NAME__/scripts/join.sh cursor "$(pwd)"`. For multiple teams, ask the user which team to join the new role into. -4. Set the session's active FROM to `` for every `send.sh` call until another `actas`. -5. Tell the user: "Now acting as ``. Sends will use `` as the from agent. (Cursor CLI has no Monitor tool, so receive still covers all of your registered roles in this project.)" +4. **If delivery mode is `monitor`**, switch the watcher to the new role so receive is restricted to it: + a. If an `agmsg inbox stream` watcher is already running in this session, stop that background Shell. + b. Launch a fresh watcher with Shell + `notify_on_output`: + - command: `~/.agents/skills/__SKILL_NAME__/scripts/watch.sh "${CURSOR_CONVERSATION_ID:--}" "$(pwd)" cursor ` + - title: `agmsg inbox stream` + The 4th argument restricts the subscription to messages addressed to `` only. In `turn`/`off` mode there is no watcher to switch — skip this step. +5. Set the session's active FROM to `` for every `send.sh` call until another `actas`. +6. Tell the user: "Now acting as ``. Sends use `` as from. In monitor mode, receive is restricted to ``; in turn/off mode receive still covers all your registered roles." If argument starts with "drop" followed by an agent name (e.g. "drop alice"): 1. Parse the role name. 2. Run `~/.agents/skills/__SKILL_NAME__/scripts/reset.sh "$(pwd)" cursor ` to remove that role's registration. 3. If the session's active FROM was ``, clear that state. -4. Tell the user: "Dropped role `` from this project." +4. **If delivery mode is `monitor`** and an `agmsg inbox stream` watcher is running, stop it, then relaunch with the default (no 4th arg) subscription: + - command: `~/.agents/skills/__SKILL_NAME__/scripts/watch.sh "${CURSOR_CONVERSATION_ID:--}" "$(pwd)" cursor` + - title: `agmsg inbox stream` +5. Tell the user: "Dropped role `` from this project." If argument is "mode" (no further args): 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh status cursor "$(pwd)"` 2. Show the output to the user. If argument starts with "mode" followed by a mode name (e.g. "mode turn"): -1. Parse the mode. Cursor CLI supports only `turn` and `off` — reject `monitor` and `both` with: "Cursor CLI has no Monitor tool; only `turn` or `off` modes are supported." +1. Parse the mode. Cursor CLI supports `turn`, `monitor`, and `off` — reject `both` with: "Cursor CLI does not support `both`; use `turn`, `monitor`, or `off`." 2. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set cursor "$(pwd)"` +3. If the mode is `monitor`, follow the "Ensure monitor is running first" step above. If `turn` or `off` and a watcher is streaming, stop that background Shell. If argument is "hook on" (legacy alias): 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set turn cursor "$(pwd)"` diff --git a/scripts/drivers/types/cursor/type.conf b/scripts/drivers/types/cursor/type.conf index 72c5774d2..1fa3e3da5 100644 --- a/scripts/drivers/types/cursor/type.conf +++ b/scripts/drivers/types/cursor/type.conf @@ -4,7 +4,8 @@ template=template.md cli=cursor-agent spawnable=yes model_arg=--model +detect=CURSOR_AGENT CURSOR_CONVERSATION_ID detect_proc=cursor-agent cursor-agent-* hooks_file=.cursor/rules/agmsg.mdc monitor=no -delivery_modes=turn off +delivery_modes=turn monitor off diff --git a/scripts/lib/resolve-project.sh b/scripts/lib/resolve-project.sh index e18e22cdf..36a4155a9 100644 --- a/scripts/lib/resolve-project.sh +++ b/scripts/lib/resolve-project.sh @@ -236,6 +236,8 @@ _agmsg_agent_binaries() { antigravity) echo "antigravity" ;; copilot) echo "copilot" ;; opencode) echo "opencode" ;; + cursor) echo "cursor-agent" ;; + grok-build) echo "grok" ;; *) echo "claude codex gemini" ;; esac } diff --git a/tests/test_delivery.bats b/tests/test_delivery.bats index d99f562d3..7daa84dec 100644 --- a/tests/test_delivery.bats +++ b/tests/test_delivery.bats @@ -2078,11 +2078,38 @@ JSON [ ! -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] } -@test "cursor rejects monitor mode" { +@test "delivery set monitor (cursor): writes a monitor rule and emits the launch directive" { run bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" - [ "$status" -ne 0 ] - [[ "$output" =~ "not supported" ]] - [ ! -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] + [ "$status" -eq 0 ] + [[ "$output" =~ "Delivery mode set to 'monitor'" ]] + [[ "$output" == *"AGMSG-DIRECTIVE"* ]] + [[ "$output" == *"watch.sh"* ]] + [[ "$output" == *"notify_on_output"* ]] + [[ "$output" == *"CURSOR_CONVERSATION_ID:--"* ]] + local rule_file="$TEST_PROJECT/.cursor/rules/agmsg.mdc" + [ -f "$rule_file" ] + run cat "$rule_file" + [[ "$output" == *"agmsg-delivery-mode: monitor"* ]] + [[ "$output" == *"notify_on_output"* ]] + [[ "$output" == *"watch.sh"* ]] + [[ "$output" == *"CURSOR_CONVERSATION_ID:--"* ]] + [[ "$output" == *"Do not use a plain background shell or \`/loop\`"* ]] +} + +@test "delivery status (cursor): reports monitor when the monitor rule is present" { + bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" >/dev/null + run bash "$SCRIPTS/delivery.sh" status cursor "$TEST_PROJECT" + [[ "$output" =~ "mode: monitor" ]] +} + +@test "delivery set turn then monitor (cursor): rewrites the rule from turn to monitor" { + bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" >/dev/null + run bash "$SCRIPTS/delivery.sh" status cursor "$TEST_PROJECT" + [[ "$output" =~ "mode: turn" ]] + bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" >/dev/null + run bash "$SCRIPTS/delivery.sh" status cursor "$TEST_PROJECT" + [[ "$output" =~ "mode: monitor" ]] + grep -q "agmsg-delivery-mode: monitor" "$TEST_PROJECT/.cursor/rules/agmsg.mdc" } @test "cursor rejects both mode" { @@ -2092,10 +2119,10 @@ JSON [ ! -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] } -@test "cursor rejects monitor: does NOT delete an existing turn rule" { +@test "cursor rejects both: does NOT delete an existing turn rule" { bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" >/dev/null [ -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] - run bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" + run bash "$SCRIPTS/delivery.sh" set both cursor "$TEST_PROJECT" [ "$status" -ne 0 ] [ -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] } @@ -2110,6 +2137,13 @@ JSON [ "$count" -eq 1 ] } +@test "cursor template offers monitor via Shell notify_on_output (#856)" { + local template="$BATS_TEST_DIRNAME/../scripts/drivers/types/cursor/template.md" + grep -q "notify_on_output" "$template" + grep -q 'CURSOR_CONVERSATION_ID:--' "$template" + grep -q 'reject `both`' "$template" +} + @test "antigravity supports off mode: removes rule file" { bash "$SCRIPTS/delivery.sh" set turn antigravity "$TEST_PROJECT" [ -f "$TEST_PROJECT/.agent/rules/agmsg.md" ] diff --git a/tests/test_resolve_project.bats b/tests/test_resolve_project.bats index cd4c7d476..45adb3e01 100644 --- a/tests/test_resolve_project.bats +++ b/tests/test_resolve_project.bats @@ -435,3 +435,9 @@ setup_git_repo() { [ "$result" = "$base/parent" ] rm -rf "$base" } + +@test "agent-binaries: cursor and grok-build map to their own process names (#856)" { + [ "$(_agmsg_agent_binaries cursor)" = "cursor-agent" ] + [ "$(_agmsg_agent_binaries grok-build)" = "grok" ] + [ "$(_agmsg_agent_binaries claude-code)" = "claude" ] +} From cfe73093ff4098996bbbd7555a23c3b85eb26b97 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 18 Aug 2026 05:38:47 +0900 Subject: [PATCH 2/3] test(cursor): use grep for new monitor assertions and lower the baseline New non-last [[ ]] raised the enforced-assertions count. grep -q fails on bash 3.2. Removing the old cursor-rejects-monitor [[ burns the baseline 638 -> 637. Co-authored-by: Cursor --- .github/enforced-assertions-baseline | 2 +- tests/test_delivery.bats | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/enforced-assertions-baseline b/.github/enforced-assertions-baseline index 231a7d578..aa1044913 100644 --- a/.github/enforced-assertions-baseline +++ b/.github/enforced-assertions-baseline @@ -1 +1 @@ -638 +637 diff --git a/tests/test_delivery.bats b/tests/test_delivery.bats index 7daa84dec..8c7ca6ded 100644 --- a/tests/test_delivery.bats +++ b/tests/test_delivery.bats @@ -2081,34 +2081,34 @@ JSON @test "delivery set monitor (cursor): writes a monitor rule and emits the launch directive" { run bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" [ "$status" -eq 0 ] - [[ "$output" =~ "Delivery mode set to 'monitor'" ]] - [[ "$output" == *"AGMSG-DIRECTIVE"* ]] - [[ "$output" == *"watch.sh"* ]] - [[ "$output" == *"notify_on_output"* ]] - [[ "$output" == *"CURSOR_CONVERSATION_ID:--"* ]] + grep -q "Delivery mode set to 'monitor'" <<<"$output" + grep -q "AGMSG-DIRECTIVE" <<<"$output" + grep -q "watch.sh" <<<"$output" + grep -q "notify_on_output" <<<"$output" + grep -q "CURSOR_CONVERSATION_ID:--" <<<"$output" local rule_file="$TEST_PROJECT/.cursor/rules/agmsg.mdc" [ -f "$rule_file" ] run cat "$rule_file" - [[ "$output" == *"agmsg-delivery-mode: monitor"* ]] - [[ "$output" == *"notify_on_output"* ]] - [[ "$output" == *"watch.sh"* ]] - [[ "$output" == *"CURSOR_CONVERSATION_ID:--"* ]] - [[ "$output" == *"Do not use a plain background shell or \`/loop\`"* ]] + grep -q "agmsg-delivery-mode: monitor" <<<"$output" + grep -q "notify_on_output" <<<"$output" + grep -q "watch.sh" <<<"$output" + grep -q "CURSOR_CONVERSATION_ID:--" <<<"$output" + grep -q "Do not use a plain background shell or \`/loop\`" <<<"$output" } @test "delivery status (cursor): reports monitor when the monitor rule is present" { bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" >/dev/null run bash "$SCRIPTS/delivery.sh" status cursor "$TEST_PROJECT" - [[ "$output" =~ "mode: monitor" ]] + grep -q "mode: monitor" <<<"$output" } @test "delivery set turn then monitor (cursor): rewrites the rule from turn to monitor" { bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" >/dev/null run bash "$SCRIPTS/delivery.sh" status cursor "$TEST_PROJECT" - [[ "$output" =~ "mode: turn" ]] + grep -q "mode: turn" <<<"$output" bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" >/dev/null run bash "$SCRIPTS/delivery.sh" status cursor "$TEST_PROJECT" - [[ "$output" =~ "mode: monitor" ]] + grep -q "mode: monitor" <<<"$output" grep -q "agmsg-delivery-mode: monitor" "$TEST_PROJECT/.cursor/rules/agmsg.mdc" } From 2cfe82d9b9331dc44028afb9c68271834c9d5904 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 18 Aug 2026 05:46:04 +0900 Subject: [PATCH 3/3] fix(resolve): keep only the cursor-agent pid arm in this PR (#856) The grok-build mapping is orthogonal and moves to its own change (#859). Co-authored-by: Cursor --- scripts/lib/resolve-project.sh | 1 - tests/test_resolve_project.bats | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/lib/resolve-project.sh b/scripts/lib/resolve-project.sh index 36a4155a9..fe5d9bf48 100644 --- a/scripts/lib/resolve-project.sh +++ b/scripts/lib/resolve-project.sh @@ -237,7 +237,6 @@ _agmsg_agent_binaries() { copilot) echo "copilot" ;; opencode) echo "opencode" ;; cursor) echo "cursor-agent" ;; - grok-build) echo "grok" ;; *) echo "claude codex gemini" ;; esac } diff --git a/tests/test_resolve_project.bats b/tests/test_resolve_project.bats index 45adb3e01..afef19c97 100644 --- a/tests/test_resolve_project.bats +++ b/tests/test_resolve_project.bats @@ -436,8 +436,7 @@ setup_git_repo() { rm -rf "$base" } -@test "agent-binaries: cursor and grok-build map to their own process names (#856)" { +@test "agent-binaries: cursor maps to cursor-agent (#856)" { [ "$(_agmsg_agent_binaries cursor)" = "cursor-agent" ] - [ "$(_agmsg_agent_binaries grok-build)" = "grok" ] [ "$(_agmsg_agent_binaries claude-code)" = "claude" ] }