Skip to content
Open
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ brew uninstall hcom # or: rm $(which hcom)
| Cursor CLI | automatic | `hcom cursor-agent` |
| Kimi | automatic | `hcom kimi` |
| Copilot CLI | automatic | `hcom copilot` |
| Grok Build | automatic | `hcom grok` |
| Anything else | manual via `hcom listen` | `hcom start` (run inside tool) |

```bash
Expand Down Expand Up @@ -270,7 +271,7 @@ What you might type from a shell. Agents run their own commands that they learn
### Spawn

```bash
hcom [N] claude|gemini|codex|agy|opencode|kilo|pi|omp|cursor-agent|kimi|copilot # launch N agents
hcom [N] claude|gemini|codex|agy|opencode|kilo|pi|omp|cursor-agent|kimi|copilot|grok # launch N agents
hcom r <name|session_id> # resume agent
hcom f <name|session_id> # fork session
hcom kill <name|tag:T|all> # kill + close terminal pane
Expand Down Expand Up @@ -334,7 +335,7 @@ hcom config -i <name> <key> <value> # per-agent override at runtime
| `terminal` | Where new agent windows open (`hcom config terminal --info`) |
| `timeout` | Idle timeout for headless/vanilla Claude (seconds) |
| `subagent_timeout` | Keep-alive for Claude subagents (seconds) |
| `claude_args` / `gemini_args` / `codex_args` / `opencode_args` / `kilo_args` / `pi_args` / `omp_args` / `cursor_args` / `kimi_args` / `copilot_args` | Default args passed to the tool |
| `claude_args` / `gemini_args` / `codex_args` / `opencode_args` / `kilo_args` / `pi_args` / `omp_args` / `cursor_args` / `kimi_args` / `copilot_args` / `grok_args` | Default args passed to the tool |

### Scope

Expand Down
6 changes: 4 additions & 2 deletions skills/hcom-agent-messaging/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ AI agents running in separate terminals are isolated. hcom connects them via hoo

```bash
curl -fsSL https://github.com/aannoo/hcom/releases/latest/download/hcom-installer.sh | sh
hcom claude # or: hcom gemini, hcom codex, hcom opencode, hcom kilo, hcom pi, hcom omp, hcom agy, hcom cursor-agent, hcom kimi, hcom copilot
hcom claude # or: hcom gemini, hcom codex, hcom opencode, hcom kilo, hcom pi, hcom omp, hcom agy, hcom cursor-agent, hcom kimi, hcom copilot, hcom grok
hcom # TUI dashboard
```

Expand Down Expand Up @@ -59,6 +59,8 @@ run `hcom --help` for full command syntax and flags.
| kilo code | automatic | `hcom kilo` |
| antigravity | automatic | `hcom agy` |
| cursor | automatic | `hcom cursor-agent` |
| copilot | automatic | `hcom copilot` |
| grok build | automatic | `hcom grok` |
| any other ai tool | manual via `hcom listen` | `hcom start` (run inside tool) |

session binding (hcom transcript, hcom r/f by session id) happens on first message or first prompt for all hcom-launched tools.
Expand Down Expand Up @@ -173,7 +175,7 @@ with `HCOM_DIR` set, uses that path instead of `~/.hcom`.
| file | when to read |
|------|-------------|
| `references/patterns.md` | writing multi-agent scripts — 6 tested patterns with full code and real event JSON |
| `references/cross-tool.md` | claude + codex + gemini + opencode + kilo + pi + omp + antigravity + cursor + kimi + copilot collaboration details and per-tool quirks |
| `references/cross-tool.md` | claude + codex + gemini + opencode + kilo + pi + omp + antigravity + cursor + kimi + copilot + grok collaboration details and per-tool quirks |
| `references/gotchas.md` | debugging scripts — timing, message delivery, intent system, cleanup |
| `references/script-template.md` | writing a new script from scratch — full template with commentary |
| `references/scripts/` | 6 tested, working example scripts |
Expand Down
15 changes: 14 additions & 1 deletion skills/hcom-agent-messaging/references/cross-tool.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Cross-Tool Patterns: Claude + Codex + Gemini + OpenCode + Kilo Code + Pi + OMP + Antigravity + Cursor + Kimi + Copilot
# Cross-Tool Patterns: Claude + Codex + Gemini + OpenCode + Kilo Code + Pi + OMP + Antigravity + Cursor + Kimi + Copilot + Grok

Verified behavior when mixing different AI coding tools via hcom.

Expand Down Expand Up @@ -66,6 +66,19 @@ Verified behavior when mixing different AI coding tools via hcom.
- **Fork**: not supported (cursor-agent has no native branch primitive — only `--resume`/`--continue`); resume preserved.
- **Transcript**: cursor-agent writes JSONL under `~/.cursor/projects/<slug>/agent-transcripts/<uuid>/<uuid>.jsonl`. Parser support is limited: no timestamps, `cwd`, or tool-result blocks; user prompts require wrapper removal.

### Grok Build
- **Hooks**: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStart, SubagentStop, SessionEnd — native `~/.grok/hooks/hcom.json` (`$GROK_HOME`).
- **Payload**: JSON via stdin. Observe events discard stdout; only Stop / SubagentStop parse `hookSpecificOutput.additionalContext`.
- **Bootstrap**: launch `--rules` (system prompt append). SessionStart does not inject bootstrap.
- **Message delivery**: managed `hcom grok` and `hcom grok --headless` sessions use a thin native ACP client connected to the same Grok leader/session. Message bodies enter Grok's native queue; no composer wake text or Enter is injected. Stop observes activity but does not consume the ACP-owned mailbox. Unmanaged hook-only sessions retain Stop additionalContext delivery.
- **Delivery confirmation**: only a matching successful `end_turn` advances the existing mailbox cursor. Queue notifications alone do not acknowledge delivery. Cancellation, failed/ambiguous writes, disconnect, or a session change during delivery retain unread mail and stop automatic replay (`acp_unacknowledged`); no input-box fallback occurs. Inspect the session before restarting, since an interrupted transport may already have delivered the message.
- **Composer and permissions**: the native TUI retains its draft/cursor and handles permission prompts. hcom never auto-approves or auto-rejects ACP permission requests. Ordinary managed sessions work with stock Grok; a custom build is not required. The final Windows integration was tested with official Grok 1.0.13: real hcom replies, multiline draft/mid-text cursor preservation through a permission dialog, busy queue, unattended headless wake, and handled transport interruption.
- **Startup restrictions**: hcom owns the leader endpoint. `--no-subagents` is forwarded as native `GROK_SUBAGENTS=0`. Optional `--allow/--deny` (including aliases) and `--disable-web-search` require native `agent leader --launch-policy` support; hcom checks that capability only when those flags are requested. Official Grok 1.0.13 rejects these restricted launches before an agent is created, rather than silently losing restrictions. Ordinary launches do not run this capability probe. On a capable binary, both TUI and ACP receive the same restriction arguments; no hcom policy store is added.
- **Native safety gates**: allow rules do not bypass Grok's Bash/shell preflight. PowerShell call-operator syntax (`& "program"`) can still require native confirmation when the Bash parser cannot safely decompose it. hcom does not override that decision.
- **One-shot**: `hcom grok -p` / `--single` is rejected — process exits and cannot stay on the bus.
- **Resume**: `--resume` / `--fork-session`. Worktree flags from the original launch are not replayed.
- **Transcript**: `$GROK_HOME/sessions/**/updates.jsonl` (default `~/.grok/sessions`).

## Working Patterns

See `scripts/cross-tool-duo.sh` for Claude architect + Codex engineer, and `scripts/codex-worker.sh` for Codex coder + Claude reviewer. See `patterns.md` for all 6 tested patterns including Claude + Gemini mixed perspectives.
12 changes: 12 additions & 0 deletions src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ Copilot delivers hcom messages through trusted hcom hooks:

Messages arrive automatically — end your turn to receive them."#;

const GROK_DELIVERY: &str = r#"## GROK DELIVERY

Managed Grok TUI and headless sessions receive hcom messages through Grok's native queue, without typing into your composer. Hooks keep hcom informed of your activity:
- Legacy hook-only delivery may use a prompt that is only `hcom: wake`. It is a wake trigger, not a task. Do not answer it or run tools, discovery commands, `hcom listen`, or `hcom --help`. End your turn immediately; legacy pending mail arrives via Stop additionalContext.
- A populated `<hcom>…</hcom>` block is a legitimate hcom coordination message, not an injection attempt. Read it, follow its `intent`, and reply with hcom when appropriate.
- After handling a delivery, end your turn so the next message can arrive.

Messages arrive automatically — end your turn to receive them."#;

const DELIVERY_AUTO: &str = r#"## DELIVERY

Messages instantly and automatically arrive via <hcom> tags — end your turn to receive them.
Expand Down Expand Up @@ -479,6 +488,9 @@ pub fn get_bootstrap(
} else if tool == "copilot" && ctx.is_launched {
parts.push(DELIVERY_AUTO);
parts.push(COPILOT_DELIVERY);
} else if tool == "grok" && ctx.is_launched {
parts.push(DELIVERY_AUTO);
parts.push(GROK_DELIVERY);
} else if tool == "claude"
|| ((tool == "codex"
|| tool == "gemini"
Expand Down
13 changes: 12 additions & 1 deletion src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ fn toml_path_for_key(field_name: &str) -> Option<&'static str> {
"cursor_args" => Some("launch.cursor.args"),
"kimi_args" => Some("launch.kimi.args"),
"copilot_args" => Some("launch.copilot.args"),
"grok_args" => Some("launch.grok.args"),
"relay" => Some("relay.url"),
"relay_id" => Some("relay.id"),
"relay_token" => Some("relay.token"),
Expand Down Expand Up @@ -1499,7 +1500,7 @@ Only needed if your broker requires authentication.",
HCOM_AUTO_APPROVE - Auto-approve safe hcom commands

Purpose:
When enabled, Claude/Gemini/Codex/OpenCode/Kilo/Pi/OMP/Antigravity/Cursor/Kimi/Copilot auto-approve \"safe\" hcom commands
When enabled, Claude/Gemini/Codex/OpenCode/Kilo/Pi/OMP/Antigravity/Cursor/Kimi/Copilot/Grok auto-approve \"safe\" hcom commands
without requiring user confirmation.

Usage:
Expand Down Expand Up @@ -1637,6 +1638,16 @@ HCOM_CURSOR_ARGS - Default args passed to cursor-agent on launch
Example: hcom config cursor_args \"--model auto\"
Clear: hcom config cursor_args \"\"

Prepended to launch-time cli args.",
),

"HCOM_GROK_ARGS" => Some(
"\
HCOM_GROK_ARGS - Default args passed to grok on launch

Example: hcom config grok_args \"--model grok-build --always-approve\"
Clear: hcom config grok_args \"\"

Prepended to launch-time cli args.",
),

Expand Down
25 changes: 14 additions & 11 deletions src/commands/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,11 @@ const LIST_HELP: &[HelpEntry] = &[
("Tool labels:", ""),
(
"",
"[CLAUDE] [GEMINI] [CODEX] [OPENCODE] [KILO] [PI] [OMP] [ANTIGRAVITY] [CURSOR] [KIMI] [COPILOT] hcom-launched (PTY + hooks)",
"[CLAUDE] [GEMINI] [CODEX] [OPENCODE] [KILO] [PI] [OMP] [ANTIGRAVITY] [CURSOR] [KIMI] [COPILOT] [GROK] hcom-launched (PTY + hooks)",
),
(
"",
"[claude] [gemini] [codex] [opencode] [kilo] [pi] [omp] [antigravity] [cursor] [kimi] [copilot] vanilla (hooks only)",
"[claude] [gemini] [codex] [opencode] [kilo] [pi] [omp] [antigravity] [cursor] [kimi] [copilot] [grok] vanilla (hooks only)",
),
("", "[AD-HOC] manual polling"),
];
Expand Down Expand Up @@ -447,7 +447,7 @@ const RESET_HELP: &[HelpEntry] = &[
),
(
"",
" HCOM_DIR=$PWD/.hcom -> $PWD/.claude, .gemini, .codex, .opencode, .kilo, .pi, .omp, .antigravity, .cursor, .kimi, .copilot",
" HCOM_DIR=$PWD/.hcom -> $PWD/.claude, .gemini, .codex, .opencode, .kilo, .pi, .omp, .antigravity, .cursor, .kimi, .copilot, .grok",
),
("", ""),
("", "To remove local setup:"),
Expand Down Expand Up @@ -481,7 +481,7 @@ const CONFIG_HELP: &[HelpEntry] = &[
"Subagent keep-alive seconds after task",
),
(
" claude_args / gemini_args / codex_args / opencode_args / kilo_args / pi_args / omp_args / cursor_args / kimi_args / copilot_args",
" claude_args / gemini_args / codex_args / opencode_args / kilo_args / pi_args / omp_args / cursor_args / kimi_args / copilot_args / grok_args",
"",
),
(" auto_approve", "Auto-approve safe hcom commands"),
Expand Down Expand Up @@ -868,6 +868,8 @@ pub const COMMAND_NAMES: &[&str] = &[
"cursor-agent",
"kimi",
"copilot",
"grok",
"grok-build",
];

fn resumable_tool_names() -> String {
Expand Down Expand Up @@ -1257,15 +1259,16 @@ mod tests {
#[test]
fn top_level_help_scopes_fork_to_supported_tools() {
let help = get_help_text();
assert!(
help.contains(
"claude|gemini|codex|opencode|kilo|pi|omp|antigravity|cursor|kimi|copilot"
)
);
assert!(help.contains(
"hcom f <name> Fork agent session (claude/codex/opencode/kilo/pi/omp)"
"claude|gemini|codex|opencode|kilo|pi|omp|antigravity|cursor|kimi|copilot|grok"
));
assert!(help.contains(
"hcom f <name> Fork agent session (claude/codex/opencode/kilo/pi/omp/grok)"
));
assert!(!help.contains("Fork agent session (claude/codex/opencode/kilo/pi/omp/kimi)"));
assert_eq!(forkable_tool_names(), "claude/codex/opencode/kilo/pi/omp");
assert_eq!(
forkable_tool_names(),
"claude/codex/opencode/kilo/pi/omp/grok"
);
}
}
3 changes: 3 additions & 0 deletions src/commands/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ pub(crate) fn print_launch_preview(preview: LaunchPreview<'_>) {
"cursor" | "cursor-agent" => preview.config.cursor_args.as_str(),
"copilot" => preview.config.copilot_args.as_str(),
"kimi" => preview.config.kimi_args.as_str(),
"grok" | "grok-build" => preview.config.grok_args.as_str(),
_ => "",
}
} else {
Expand Down Expand Up @@ -530,6 +531,7 @@ pub(crate) fn merge_tool_args(
append_config_args(&config.cursor_args, cli_args)
}
LaunchTool::Copilot => append_config_args(&config.copilot_args, cli_args),
LaunchTool::Grok => append_config_args(&config.grok_args, cli_args),
LaunchTool::Pi => append_config_args(&config.pi_args, cli_args),
LaunchTool::Omp => append_config_args(&config.omp_args, cli_args),
LaunchTool::OpenCode => append_config_args(&config.opencode_args, cli_args),
Expand Down Expand Up @@ -562,6 +564,7 @@ pub(crate) fn is_background_from_args(tool: &LaunchTool, args: &[String]) -> boo
| LaunchTool::Cursor
| LaunchTool::Kimi
| LaunchTool::Copilot
| LaunchTool::Grok
| LaunchTool::Omp => false,
}
}
Expand Down
139 changes: 139 additions & 0 deletions src/commands/resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ fn merge_resume_args(tool: &str, original: &[String], resume: &[String]) -> Vec<
crate::tool::Tool::Cursor => merge_cursor_args(original, resume),
crate::tool::Tool::Kimi => merge_kimi_args(original, resume),
crate::tool::Tool::Copilot => merge_copilot_args(original, resume),
crate::tool::Tool::Grok => merge_grok_args(original, resume),
crate::tool::Tool::Pi => merge_pi_args(original, resume),
crate::tool::Tool::Omp => merge_omp_args(original, resume),
crate::tool::Tool::Adhoc => {
Expand All @@ -1047,6 +1048,114 @@ fn merge_resume_args(tool: &str, original: &[String], resume: &[String]) -> Vec<
}
}

/// Merge grok original launch args with resume args.
///
/// Drop session selectors, one-shot flags, and worktree flags (the session
/// already lives in that tree). `-w`/`--worktree` take an optional value.
fn merge_grok_args(original: &[String], resume: &[String]) -> Vec<String> {
const VALUE_FLAGS: &[&str] = &[
"--model",
"-m",
"--cwd",
"--rules",
"--agent",
"--permission-mode",
"--reasoning-effort",
"--effort",
"--max-turns",
"--output-format",
"--disallowed-tools",
"--tools",
"--allow",
"--deny",
"--sandbox",
"--leader-socket",
"--debug-file",
"--system-prompt-override",
];
const DROP_WITH_VALUE: &[&str] = &[
"--resume",
"-r",
"--session-id",
"-s",
"--single",
"-p",
"--prompt-file",
"--prompt-json",
"--worktree",
"-w",
"--worktree-ref",
"--ref",
];
const DROP_BOOLEAN: &[&str] = &["--continue", "-c", "--fork-session", "--restore-code"];

let is_flag = |t: &str| t.starts_with('-');

let mut resume_flags: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut skip_next = false;
for token in resume {
if skip_next {
skip_next = false;
continue;
}
if is_flag(token) {
let lower = token.to_lowercase();
let bare = lower.split('=').next().unwrap_or(&lower).to_string();
if VALUE_FLAGS.contains(&bare.as_str()) {
skip_next = !token.contains('=');
}
if !DROP_WITH_VALUE.contains(&bare.as_str()) && !DROP_BOOLEAN.contains(&bare.as_str()) {
resume_flags.insert(bare);
}
}
}

let mut filtered_original: Vec<String> = Vec::new();
let mut i = 0;
while i < original.len() {
let token = &original[i];
if is_flag(token) {
let lower = token.to_lowercase();
let (bare, has_eq_value) = if let Some(pos) = lower.find('=') {
(lower[..pos].to_string(), true)
} else {
(lower.clone(), false)
};
if DROP_WITH_VALUE.contains(&bare.as_str()) {
i += 1;
if !has_eq_value && i < original.len() && !is_flag(&original[i]) {
i += 1;
}
continue;
}
if DROP_BOOLEAN.contains(&bare.as_str()) {
i += 1;
continue;
}
if resume_flags.contains(&bare) {
i += 1;
if !has_eq_value && VALUE_FLAGS.contains(&bare.as_str()) && i < original.len() {
i += 1;
}
continue;
}
filtered_original.push(token.clone());
i += 1;
if !has_eq_value && VALUE_FLAGS.contains(&bare.as_str()) && i < original.len() {
filtered_original.push(original[i].clone());
i += 1;
}
} else {
// Drop bare positional task prompt from original launch.
i += 1;
}
}

let mut result = resume.to_vec();
result.extend(filtered_original);
result
}

/// Merge copilot original launch args with resume args.
///
/// copilot launch_args bake in `HCOM_COPILOT_ARGS` (e.g. `--model
Expand Down Expand Up @@ -3567,6 +3676,36 @@ mod tests {
assert_eq!(args, s(&["--resume", "sess-abc"]));
}

#[test]
fn test_merge_grok_args_drops_worktree_and_keeps_rules() {
let original = s(&["--worktree", "feat", "--rules", "BOOT", "--always-approve"]);
let resume = s(&["--resume", "sess-1"]);
let merged = merge_resume_args("grok", &original, &resume);
assert!(!merged.iter().any(|t| t == "--worktree" || t == "feat"));
assert!(merged.contains(&"--rules".to_string()));
assert!(merged.contains(&"BOOT".to_string()));
assert!(merged.contains(&"--always-approve".to_string()));
}

#[test]
fn test_merge_grok_args_bare_worktree_does_not_eat_model() {
let original = s(&["--worktree", "--model", "grok-build"]);
let resume = s(&["--resume", "sess-1"]);
let merged = merge_resume_args("grok", &original, &resume);
assert!(!merged.contains(&"--worktree".to_string()));
assert!(merged.contains(&"--model".to_string()));
assert!(merged.contains(&"grok-build".to_string()));
}

#[test]
fn test_merge_grok_args_short_worktree_drops_name() {
let original = s(&["-w", "mytree", "--always-approve"]);
let resume = s(&["--resume", "sess-1"]);
let merged = merge_resume_args("grok", &original, &resume);
assert!(!merged.iter().any(|t| t == "-w" || t == "mytree"));
assert!(merged.contains(&"--always-approve".to_string()));
}

#[test]
fn test_merge_copilot_args_preserves_model_drops_prompt() {
let original = s(&["--model", "claude-haiku-4.5", "-i", "do a task"]);
Expand Down
Loading