Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ while [[ $# -gt 0 ]]; do
echo "Options:"
echo " --cmd <name> Command & skill folder name (default: agmsg)"
echo " Claude Code: /<cmd>, Codex/Gemini/Antigravity: \$<cmd>"
echo " --agent-type <t> Agent type: claude-code, codex, gemini, antigravity, opencode, hermes, cursor"
echo " --agent-type <t> Agent type: claude-code, codex, gemini, antigravity, opencode, hermes, cursor, grok-build"
echo " Selects which template becomes SKILL.md (matches the"
echo " <type> arg passed to join.sh / whoami.sh)"
echo " --update Update skill scripts only (preserve DB and teams)"
Expand Down Expand Up @@ -232,6 +232,8 @@ if [ "$UPDATE_ONLY" = true ]; then
AGENT_TYPE="antigravity"
elif grep -q "whoami.sh.*gemini" "$SKILL_DIR/SKILL.md" 2>/dev/null; then
AGENT_TYPE="gemini"
elif grep -q "whoami.sh.*grok-build" "$SKILL_DIR/SKILL.md" 2>/dev/null; then
AGENT_TYPE="grok-build"
else
AGENT_TYPE="codex"
fi
Expand All @@ -241,7 +243,7 @@ if [ "$UPDATE_ONLY" = true ]; then
# shared SKILL.md; their dedicated copies are dropped separately below.)
TPL_TYPE="codex"
case "$AGENT_TYPE" in
gemini|antigravity|opencode|hermes|cursor) TPL_TYPE="$AGENT_TYPE" ;;
gemini|antigravity|opencode|hermes|cursor|grok-build) TPL_TYPE="$AGENT_TYPE" ;;
esac
sed "s/__SKILL_NAME__/$SKILL_NAME/g" "$(agmsg_type_template_path "$TPL_TYPE")" > "$SKILL_DIR/SKILL.md"
# Recursive copy so nested helper dirs (scripts/lib/, scripts/drivers/types/)
Expand Down Expand Up @@ -280,6 +282,12 @@ if [ "$UPDATE_ONLY" = true ]; then
mkdir -p "$HERMES_SKILL_DIR"
sed "s/__SKILL_NAME__/$SKILL_NAME/g" "$(agmsg_type_template_path hermes)" > "$HERMES_SKILL_DIR/SKILL.md"
fi
# Refresh / install the Grok Build skill (same reasoning as Copilot above).
GROK_SKILL_DIR="$HOME/.grok/skills/$SKILL_NAME"
if [ -d "$HOME/.grok" ]; then
mkdir -p "$GROK_SKILL_DIR"
sed "s/__SKILL_NAME__/$SKILL_NAME/g" "$(agmsg_type_template_path grok-build)" > "$GROK_SKILL_DIR/SKILL.md"
fi
cp "$SCRIPT_DIR/openai.yaml" "$SKILL_DIR/agents/openai.yaml" 2>/dev/null || true
chmod +x "$SKILL_DIR/scripts/"*.sh
chmod +x "$SKILL_DIR/scripts/drivers/types/codex/"*.sh 2>/dev/null || true
Expand Down Expand Up @@ -337,7 +345,7 @@ mkdir -p "$SKILL_DIR"/{scripts,types,db,agents}
# codex template by default; gemini/antigravity/opencode get their own.
TPL_TYPE="codex"
case "$AGENT_TYPE" in
gemini|antigravity|opencode|hermes|cursor) TPL_TYPE="$AGENT_TYPE" ;;
gemini|antigravity|opencode|hermes|cursor|grok-build) TPL_TYPE="$AGENT_TYPE" ;;
esac
sed "s/__SKILL_NAME__/$CMD_NAME/g" "$(agmsg_type_template_path "$TPL_TYPE")" > "$SKILL_DIR/SKILL.md"
# Recursive copy so nested helper dirs (scripts/lib/, scripts/drivers/types/) ship
Expand Down Expand Up @@ -423,6 +431,19 @@ if [ -d "$HOME/.hermes" ]; then
echo " + installed /$CMD_NAME skill to ~/.hermes/skills/"
fi

# --- Install Grok Build skill ---
# Grok Build reads skills from ~/.grok/skills/<name>/SKILL.md (it also accepts
# the cross-vendor ~/.agents/skills/ fallback, but the shared SKILL.md is
# Codex-typed and would mis-identify a Grok session — keep the Grok copy
# separate, same pattern as Copilot). Delivery (turn) registers a Stop hook under
# ~/.grok/hooks/ via `delivery.sh set` per project.
GROK_SKILL_DIR="$HOME/.grok/skills/$CMD_NAME"
if [ -d "$HOME/.grok" ]; then
mkdir -p "$GROK_SKILL_DIR"
sed "s/__SKILL_NAME__/$CMD_NAME/g" "$(agmsg_type_template_path grok-build)" > "$GROK_SKILL_DIR/SKILL.md"
echo " + installed /$CMD_NAME skill to ~/.grok/skills/"
fi

# Codex sandbox writable_roots are configured by configure_codex_sandbox() at
# the "Done" step below — the single source of truth for db/, teams/, and run/.
# (A legacy inline copy used to run here too, which double-mutated the array and
Expand Down
10 changes: 8 additions & 2 deletions scripts/check-inbox.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ if echo "$INPUT" | grep -q '"stop_hook_active"[[:space:]]*:[[:space:]]*true' 2>/
fi

# Defer to the monitor watcher when one is alive for this session.
# Avoids double-delivery when delivery.mode = both. session_id is sent in
# the hook input JSON for Stop events.
# Avoids double-delivery when delivery.mode = both. The session id field name
# differs by vendor: Claude Code emits snake_case "session_id"; Grok Build (and
# Cursor) emit camelCase "sessionId". Try snake first (claude-code unaffected),
# then camel, then the GROK_SESSION_ID env Grok injects into every hook.
SESSION_ID=$(printf '%s' "$INPUT" \
| sed -n 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
| head -1)
[ -z "$SESSION_ID" ] && SESSION_ID=$(printf '%s' "$INPUT" \
| sed -n 's/.*"sessionId"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
| head -1)
[ -z "$SESSION_ID" ] && SESSION_ID="${GROK_SESSION_ID:-}"
if [ -n "$SESSION_ID" ]; then
# The monitor watcher keys its pidfile (and its actas owner, below) on the
# per-process instance id (#93), not the bare session_id. Normalize to the
Expand Down
60 changes: 60 additions & 0 deletions scripts/drivers/types/grok-build/_delivery.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# grok-build delivery plug — markdown rule file (.grok/rules/agmsg.md).
#
# Why a rule file (not a hook): Grok Build's passive hooks (SessionStart/Stop)
# discard their stdout — they cannot inject anything into the conversation. A
# Stop hook running check-inbox.sh would therefore deliver NOTHING while still
# marking messages read = silent loss. So grok integrates the same way as
# gemini / antigravity / opencode: a markdown rule under <project>/.grok/rules/,
# which Grok always scans into context each turn. The rule tells the agent to
# poll its own inbox; the agent runs the check as a tool call and reads the
# output — the one delivery path Grok actually supports.
#
# The rule points at inbox.sh (not check-inbox.sh): inbox.sh prints the unread
# messages in plain text AND marks them read in the same call, so the agent sees
# exactly what gets consumed (loss-safe). check-inbox.sh wraps its output in
# Claude hook-control JSON and carries hook-only cooldown/watcher logic that is
# wrong for an agent reading tool output.
#
# Rule files need no folder-trust (Grok's trust gate is for execution —
# hooks/MCP/LSP — not rules), and a project-level .grok/rules is read even
# outside a git repo, so this also works for spawned sessions. delivery_modes is
# "turn off": turn => rule present (self-poll active), off => rule removed.
# Uses resolve_hooks_file + SKILL_DIR from delivery.sh's sourced context.
agmsg_delivery_apply() {
local type="$1"
local project="$2"
local mode="$3"
local rule_file
rule_file=$(resolve_hooks_file "$type" "$project")

# Always start clean; turn rewrites the rule, off leaves it absent.
rm -f "$rule_file"

if [ "$mode" = "turn" ]; then
mkdir -p "$(dirname "$rule_file")"
cat <<EOF > "$rule_file"
# agmsg — check your inbox each turn

You belong to one or more agmsg teams. Before you respond to the user on each
turn, check your agmsg inbox so you never miss a teammate's message.

1. Identify yourself (once per session is enough):
\`$SKILL_DIR/scripts/whoami.sh '$project' $type\`
It prints your \`agent=\` name and \`teams=\` list.
2. For each team, show and consume unread messages:
\`$SKILL_DIR/scripts/inbox.sh <team> <your-agent-name>\`
This prints unread messages AND marks them read in the same call, so nothing
is lost.
3. If any messages were shown, relay them to the user before continuing with
their request.

There is no background watcher for Grok Build — this self-check is how delivery
works. Removing this file turns automatic delivery off.
EOF
fi
}

# Status is the rule file's presence: present => turn, absent => off (no monitor
# for a self-poll type). Same shared helper the other rule-file types use.
agmsg_delivery_status() { rulefile_status "$@"; }
137 changes: 137 additions & 0 deletions scripts/drivers/types/grok-build/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
name: __SKILL_NAME__
description: Cross-agent messaging via SQLite. Send messages between Claude Code, Codex, Gemini CLI, Grok Build, and other agents. No daemon, no network, no dependencies beyond bash and sqlite3.
---

Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.**

## Identity

If you already know your AGENT and TEAMS from a previous `/__SKILL_NAME__` call in this session, skip to **Execute** below.

Otherwise, run: `~/.agents/skills/__SKILL_NAME__/scripts/whoami.sh "$(pwd)" grok-build`

Four possible outputs:

**A) Single identity:**
`agent=<name> teams=<t1,t2,...> type=grok-build project=<path>`
→ Remember AGENT and TEAMS, then go to **Execute**.

**B) Multiple identities:**
`multiple=true agents=<n1,n2,...> teams=<t1,t2,...> type=grok-build project=<path>`
→ Ask the user which agent name to use for this session, then go to **Execute**.

**C) Not in a team:**
`not_joined=true available_teams=<t1,t2,...>` (or `available_teams=none`)
→ Show the user the available teams from the output, then:

> **First-time setup required.**
> Joining a team so this agent can send and receive messages.
> - **Team name**: a group of agents that can message each other (available: <list from output>)
> - **Agent name**: this agent's identity within the team

1. Ask: "Enter a team name (joins existing or creates new)"
2. Ask: "Enter a name for this agent"
3. **You MUST use join.sh** — run: `~/.agents/skills/__SKILL_NAME__/scripts/join.sh <team> <agent_name> grok-build "$(pwd)"`
4. Show the result and explain:

> **Joined!** You can now use `/__SKILL_NAME__` to check and send messages.
> - `/__SKILL_NAME__` — check inbox
> - `/__SKILL_NAME__ send <agent> <message>` — send a message
> - `/__SKILL_NAME__ team` — list team members
> - `/__SKILL_NAME__ history` — message history

5. **REQUIRED — Do NOT skip this step.** Ask the user to pick a delivery mode using exactly this prompt:

```
Choose delivery mode for incoming messages:

1) turn — Check inbox at the end of each assistant turn
A .grok/rules/agmsg.md rule has you self-check inbox.sh
each turn (Grok hooks can't push, so delivery is self-poll).

2) off — No automatic delivery
Manual /__SKILL_NAME__ only.

[1]:
```

- **Wait for the user's answer before proceeding.** Empty input means `1` (turn).
- Map the chosen number to a mode (`1`→`turn`, `2`→`off`) and run:
`~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set <mode> grok-build "$(pwd)"`
- Grok Build has no Monitor-tool equivalent, so `monitor` and `both` modes are not offered here.

6. Then check inbox for the newly joined team.

**D) Suggestions for reuse:**
`suggest=true agents=<n1,n2,...> teams=<t1,t2,...> type=grok-build project=<path> available_teams=<t1,t2,...>`
→ No exact registration exists for this project, but there are same-type agent names registered elsewhere.

1. Show the suggested agent names to the user.
2. Ask whether to reuse one of those names or choose a new one.
3. Ask for the team name to join (existing or new).
4. Run: `~/.agents/skills/__SKILL_NAME__/scripts/join.sh <team> <agent_name> grok-build "$(pwd)"`
5. Then continue with the normal post-join flow above.

## Execute

**Only use scripts in `~/.agents/skills/__SKILL_NAME__/scripts/` — do not read or modify files under `teams/` or `db/` directly.**

**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.
3. If there are messages, read and respond appropriately. To reply:
`~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT <to_agent> "<message>"`

If argument is "history":
1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT`

If argument is "team":
1. For each TEAM, run: `~/.agents/skills/__SKILL_NAME__/scripts/team.sh $TEAM`

If argument starts with "send" (e.g. "send misaki check the server"):
1. Parse target agent and message from the arguments
2. Determine which team the target agent belongs to, then run:
`~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT <to_agent> "<message>"`

If argument is "config":
1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show`
2. Show the output to the user.

If argument starts with "config set" (e.g. "config set hook.check_interval 30"):
1. Parse key and value from the arguments.
2. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh set <key> <value>`


If argument starts with "actas" followed by an agent name (e.g. "actas alice"):
1. Parse the new role name.
2. Run `~/.agents/skills/__SKILL_NAME__/scripts/identities.sh "$(pwd)" grok-build` 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 <team> <name> grok-build "$(pwd)"`. For multiple teams, ask the user which team to join the new role into.
4. Set the session's active FROM to `<name>` for every `send.sh` call until another `actas`.
5. Tell the user: "Now acting as `<name>`. Sends will use `<name>` as the from agent. (Grok Build has no Monitor tool, so receive still covers all of your registered roles in this project.)"

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)" grok-build <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."

If argument is "mode" (no further args):
1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh status grok-build "$(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. Grok Build supports only `turn` and `off` — reject `monitor` and `both` with: "Grok Build has no Monitor tool; only `turn` or `off` modes are supported."
2. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set <mode> grok-build "$(pwd)"`

If argument is "hook on" (legacy alias):
1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set turn grok-build "$(pwd)"`
2. Tell the user: "Delivery mode set to 'turn' (legacy hook on behavior)."

If argument is "hook off" (legacy alias):
1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set off grok-build "$(pwd)"`
2. Tell the user: "Delivery mode set to 'off'."

If argument is "reset":
1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/reset.sh "$(pwd)" grok-build`
2. Tell the user the result.
10 changes: 10 additions & 0 deletions scripts/drivers/types/grok-build/type.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# agmsg agent-type manifest — read-only key=value DATA. NEVER sourced.
name=grok-build
template=template.md
cli=grok
spawnable=yes
detect=GROK_SESSION_ID
detect_proc=grok grok-*
hooks_file=.grok/rules/agmsg.md
monitor=no
delivery_modes=turn off
11 changes: 9 additions & 2 deletions scripts/session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,22 @@ else
agmsg_session_start_default
fi

# Read hook input JSON from stdin. session_id field is sent for SessionStart.
# Read hook input JSON from stdin. The session id field name differs by vendor:
# Claude Code emits snake_case "session_id"; Grok Build (and Cursor) emit
# camelCase "sessionId". Try snake first (claude-code unaffected), then camel,
# then the GROK_SESSION_ID env Grok injects into every hook.
INPUT=$(cat 2>/dev/null || true)
SESSION_ID=""
if [ -n "$INPUT" ]; then
SESSION_ID=$(printf '%s' "$INPUT" \
| sed -n 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
| head -1)
[ -z "$SESSION_ID" ] && SESSION_ID=$(printf '%s' "$INPUT" \
| sed -n 's/.*"sessionId"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
| head -1)
fi
# Fallback so the instruction is still actionable even outside CC's hook flow.
[ -z "$SESSION_ID" ] && SESSION_ID="${GROK_SESSION_ID:-}"
# Fallback so the instruction is still actionable even outside a hook flow.
[ -z "$SESSION_ID" ] && SESSION_ID="unknown-$$"

mkdir -p "$RUN_DIR" 2>/dev/null || true
Expand Down
Loading
Loading