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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,9 +496,11 @@ This means any `kiro-cli` pane can be discovered and switched to without naming

## GitHub Copilot CLI

An interactive `copilot` process (or `copilot.exe` on Windows) in a tmux pane is discovered as Copilot in `list`, the chooser/popup, and `status`. Use `--agent copilot` to filter navigation; `inspect <target>` shows the command detection reason and confidence. No Copilot hooks or configuration are required.
An interactive `copilot` process (or `copilot.exe` on Windows) in a tmux pane is discovered in `list`, the chooser/popup, and `status`. Use `--agent copilot` to filter navigation. No hooks are required: without them, activity and status remain `unknown` rather than guessing a wait. Title text and arbitrary `copilot-*` commands are not detected. The npm-installed CLI's Node wrapper is recognized by its executable path; generic wrapper support is tracked separately in #19.

Copilot runtime state is deliberately coarse: its session identity comes from the tmux pane, while activity and status remain `unknown`. Merely running the CLI does not mean a turn is running or needs attention, so Copilot panes do not appear in `--busy`, `--running`, or `--waiting` views. Title text, arbitrary `copilot-*` commands, and wrapped launches are not detected; generic wrapper support is tracked separately in #19.
For optional, **local Copilot CLI only** turn and prompt state, preview the configuration with `coding-agents-tmux copilot-hooks-template`, then explicitly run `coding-agents-tmux install-copilot`. This creates only `${COPILOT_HOME:-~/.copilot}/hooks/coding-agents-tmux.json`; other user hooks are untouched. Restart existing Copilot CLI sessions so hooks load. Do not put these hooks in `.github/hooks/` (repository hooks also execute in Copilot cloud agent). The tmux plugin never auto-installs Copilot hooks.

The adapter correlates `TMUX_PANE`, the hook process's Copilot parent PID, and the Copilot `sessionId`, including concurrent sessions in one directory and `/clear` (which ends the old session and starts another). An exited or backgrounded Copilot process invalidates its hook state even if another CLI starts in the same pane. `userPromptSubmitted` means running; `agentStop` means idle. Only `notification` types `permission_prompt` and `elicitation_dialog` assert waiting-question and waiting-input respectively; automatic permission checks and `preToolUse` never assert a wait. An elicitation notification does not reveal whether its form contains multiple choices, so it is conservatively labeled input. Resolved prompts clear at the next turn boundary (`agentStop`); hooks do not expose an immediate resolution event, so the state can briefly lag after an answer. Hook state expires after 60 seconds without a subsequent event (including an unanswered prompt), reverting to the coarse navigable state instead of retaining a false wait. `inspect` reports hook source, age and expiry or command-only fallback. If hooks are disabled, folder trust blocks them, tmux pane identity is missing, or a hook is missed, no directory-only match is made. `COPILOT_HOME` selects the hook directory; `CODING_AGENTS_TMUX_COPILOT_STATE_DIR` overrides the local state directory. The integration neither grants permissions nor injects context.

## How this compares to Herdr

Expand Down
31 changes: 31 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ import {
installCodexIntegration,
persistCodexHookState,
} from "./core/codex.ts";
import {
buildCopilotHooksTemplate,
installCopilotIntegration,
persistCopilotHookState,
} from "./core/copilot.ts";
import { notifyIntegration } from "./core/notifications.ts";
import { detectOpenCodeVersion } from "./core/opencode-generation.ts";
import { installOpenCodeIntegration } from "./core/opencode-install.ts";
Expand Down Expand Up @@ -642,6 +647,16 @@ async function runInstallClaudeCommand(_options: InstallClaudeOptions): Promise<
console.log("Restart Claude Code sessions so new hooks are loaded");
}

async function runCopilotHookStateCommand(eventName: string): Promise<void> {
await persistCopilotHookState(await readStdinText(), { eventName });
}

async function runInstallCopilotCommand(): Promise<void> {
const result = installCopilotIntegration(CLI_PATH);
console.log(`Updated ${result.hooksPath}`);
console.log("Restart Copilot CLI sessions so new hooks are loaded");
}

async function runInstallOpenCodeCommand(_options: InstallOpenCodeOptions): Promise<void> {
const detected = await detectOpenCodeVersion();
const result = installOpenCodeIntegration(detected);
Expand Down Expand Up @@ -1010,6 +1025,22 @@ async function main(): Promise<void> {
.description("Install or update Claude Code hook configuration under ~/.claude")
.action(runInstallClaudeCommand);

program
.command("copilot-hooks-template")
.description("Print optional user-level Copilot CLI hook configuration")
.action(() => console.log(buildCopilotHooksTemplate(CLI_PATH)));

program
.command("copilot-hook-state")
.description("Ingest a Copilot CLI hook event from stdin (used by installed hooks)")
.argument("<event>", "Copilot CLI lifecycle event name")
.action(runCopilotHookStateCommand);

program
.command("install-copilot")
.description("Explicitly install Copilot CLI user-level hooks; never run automatically")
.action(runInstallCopilotCommand);

program
.command("popup")
.description("Open a tmux popup chooser for switching between discovered coding agent panes")
Expand Down
Loading
Loading