Skip to content

[Feat]: Add gemini cli plugin#912

Open
priyanshuharshbodhi1 wants to merge 10 commits intoComposioHQ:mainfrom
priyanshuharshbodhi1:feat/agent-gemini-plugin
Open

[Feat]: Add gemini cli plugin#912
priyanshuharshbodhi1 wants to merge 10 commits intoComposioHQ:mainfrom
priyanshuharshbodhi1:feat/agent-gemini-plugin

Conversation

@priyanshuharshbodhi1
Copy link
Copy Markdown

@priyanshuharshbodhi1 priyanshuharshbodhi1 commented Apr 4, 2026

Fixes #931

What

Adds @composio/ao-plugin-agent-gemini - full support for Google Gemini CLI as an agent in Agent Orchestrator. Any project can now set agent: gemini in agent-orchestrator.yaml.

Why

Gemini CLI is a first-class AI coding agent alongside Claude Code, Codex, Aider, and OpenCode. This rounds out the default agent roster.

Key Design Decisions

promptDelivery: "post-launch" - Gemini's -p flag exits after one response; AO would mark the session killed. Instead, AO launches Gemini as an interactive REPL in tmux and sends the task prompt after startup via runtime.sendMessage().

Absolute binary resolution - resolveGeminiBinarySync() calls which gemini inside the CLI process (which has nvm in PATH). The resolved absolute path is used in the launch command so tmux shells without nvm still find the real binary.

Hook trust pre-population - AO writes ~/.gemini/trusted_hooks.json when setting up workspace hooks so Gemini skips its "trust these hooks?" warning dialog before the REPL is ready.

Session file path - ~/.gemini/tmp/<sha256(projectRoot)>/chats/session-*.json. Hash is the full 64-char SHA-256 hex, verified against google-gemini/gemini-cli source.

Metadata hooks - AfterTool hook in .gemini/settings.json fires after every run_shell_command. Detects gh pr create, git checkout -b, and gh pr merge to auto-update AO session metadata. Uses a relative path for symlink safety across worktrees.

Activity classification - reads lastUpdated from session JSON (more accurate than file mtime). 30-second session file cache prevents double readdir per poll cycle.

Testing

84 unit tests covering: manifest, binary resolution, launch command flags, activity states, session info/cost extraction, restore command, workspace hooks (write, merge, no-duplicate, hook trust), postLaunchSetup REPL readiness polling.

priyanshuharshbodhi1 and others added 3 commits April 4, 2026 22:49
Add @composio/ao-plugin-agent-gemini workspace package with package.json
and tsconfig.json. Register it in:
- packages/cli/package.json (workspace dependency)
- packages/core/src/plugin-registry.ts (BUILTIN_PLUGINS)
- packages/cli/src/lib/plugins.ts (static import map)
- packages/cli/src/lib/detect-agent.ts (ao init detection list)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full plugin implementation (~450 lines):

- promptDelivery: "post-launch" — Gemini runs as interactive REPL in tmux.
  Using -p causes Gemini to exit after one response (AO marks session killed).
- getLaunchCommand: resolves absolute binary path at creation time so tmux
  shells without nvm in PATH still find the real binary, not a guard wrapper.
- getActivityState: reads lastUpdated from session JSON (more accurate than
  file mtime on network filesystems). 30-second session file cache prevents
  redundant readdir per poll cycle.
- getSessionInfo: extracts summary and per-message token cost from session
  JSON. Falls back to first user message as summary if no .summary field.
- getRestoreCommand: gemini --resume <session-id>, AO sends task post-launch.
- postLaunchSetup: writes workspace hooks, pre-trusts hook in
  trusted_hooks.json, polls tmux pane for REPL prompt (❯) before the
  session manager sends the task — prevents prompt injection into bash.
- detect(): execFileSync("gemini", ["--version"]) binary presence check.

Session path: ~/.gemini/tmp/<sha256(projectRoot)>/chats/session-*.json
Project hash: full 64-char SHA-256 hex, verified against Gemini CLI source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Coverage:
- manifest shape and plugin export
- getGeminiProjectHash / getGeminiChatsDir path construction
- getLaunchCommand: base, permissionless/auto-edit/default, model, system
  prompt, no -p flag (post-launch delivery)
- getActivityState: active/ready/idle/exited, process check, session cache
- getSessionInfo: summary, cost extraction, first-message fallback
- getRestoreCommand: uses resolved binary + --resume flag
- setupWorkspaceHooks: creates dir, writes script, registers AfterTool hook,
  merges existing settings, no duplicate hooks, pre-trusts in trusted_hooks.json
- postLaunchSetup: writes hooks, polls for ❯ REPL prompt, early exit on guard
  message or tmux failure, uses relative path in settings.json
- METADATA_UPDATER_SCRIPT: bash shebang, run_shell_command filter,
  gh pr create / git checkout / gh pr merge detection, path traversal guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
if (config.issueId) {
env["AO_ISSUE_ID"] = config.issueId;
}
return env;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Missing buildAgentPath — every non-Claude-Code agent (codex, aider, opencode) prepends ~/.ao/bin to PATH so the AO gh/git wrappers intercept commands and update session metadata. Without this, PRs created by Gemini won't reliably appear in the dashboard.

import { buildAgentPath, setupPathWrapperWorkspace, ... } from "@composio/ao-core";                          
                                                                                                             
// in getEnvironment:                                                                                        
env["PATH"] = buildAgentPath(process.env["PATH"]);                                                           
                                                                                                             
See agent-codex/src/index.ts:389, agent-aider/src/index.ts:149, agent-opencode/src/index.ts:291 for the      
pattern.

},

async setupWorkspaceHooks(workspacePath: string, _config: WorkspaceHooksConfig): Promise<void> {
await writeGeminiHooks(workspacePath);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also needs setupPathWrapperWorkspace(workspacePath) here alongside writeGeminiHooks. This installs the ~/.ao/bin/gh and ~/.ao/bin/git wrappers and writes .ao/AGENTS.md context. Every other PATH-wrapper agent calls both their native hooks AND this function in setupWorkspaceHooks and postLaunchSetup.

async setupWorkspaceHooks(workspacePath: string, _config: WorkspaceHooksConfig): Promise {
await writeGeminiHooks(workspacePath);
await setupPathWrapperWorkspace(workspacePath); // <-- add this
},
Same addition needed in postLaunchSetup at line 773. See agent-codex/src/index.ts:616,633 for the pattern.

try {
let content = pendingSystemPrompt ?? "";
if (pendingSystemPromptFile) {
content = readFileSync(pendingSystemPromptFile, "utf-8");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This uses readFileSync (sync, blocking) inside an async function. The async readFile from node:fs/promises is already imported at line 23, use that instead:

content = await readFile(pendingSystemPromptFile, "utf-8");

This keeps the async function non-blocking and consistent with the rest of the file.

@priyanshuharshbodhi1 priyanshuharshbodhi1 changed the title [Feat]: Add gemini plugin [Feat]: Add gemini cli plugin Apr 6, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4e2d06a. Configure here.

for (const msg of session.messages) {
if (msg.type === "gemini" && msg.tokens) {
inputTokens += msg.tokens.input ?? 0;
inputTokens += msg.tokens.cached ?? 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cached tokens double-counted inflating cost estimates

Medium Severity

extractCost adds msg.tokens.cached to inputTokens on line 303, but per the Gemini API, cached_content_token_count is a subset of prompt_token_count (input), not additive. This double-counts cached tokens, inflating both the reported inputTokens and estimatedCostUsd. The test data uses cached: 0 which masks the bug — any session with non-zero cached usage will report incorrect values.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4e2d06a. Configure here.

const lastLine = lines[lines.length - 1]?.trim() ?? "";

// REPL prompt visible → agent is idle and waiting for input
if (/^(gemini\s+)?[❯>]\s*$/.test(lastLine)) return "idle";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Activity classifier matches bare > unlike REPL detector

Low Severity

classifyGeminiOutput matches [❯>] at line 446, treating bare > as an idle REPL prompt. However, postLaunchSetup at line 837 intentionally matches only , with a comment explaining that > causes false positives from bash heredocs and PS1 prompts. The same false-positive risk applies to ongoing activity detection — terminal output ending with > would be misclassified as idle.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4e2d06a. Configure here.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 built-in Agent Orchestrator agent plugin that integrates Google Gemini CLI, enabling projects to configure agent: gemini and providing session/activity detection, restore support, and workspace hook setup.

Changes:

  • Introduces @composio/ao-plugin-agent-gemini with launch/env wiring, activity detection, session metadata extraction, and Gemini hook setup.
  • Registers Gemini as a built-in agent across core + CLI (plugin registry, plugin wiring, agent detection).
  • Updates Codex agent session-meta parsing to support newer Codex JSONL shape (payload.cwd).

Reviewed changes

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

Show a summary per file
File Description
pnpm-lock.yaml Adds workspace link entries for the new agent-gemini package.
packages/plugins/agent-gemini/package.json New Gemini agent plugin package metadata, scripts, and deps.
packages/plugins/agent-gemini/tsconfig.json TypeScript build config for the new plugin package.
packages/plugins/agent-gemini/src/index.ts Implements Gemini agent behavior, session parsing, activity detection, and Gemini AfterTool hook script generation.
packages/plugins/agent-gemini/src/index.test.ts Unit tests covering manifest, launch/env, activity/session parsing, hook setup, and post-launch readiness polling.
packages/plugins/agent-codex/src/index.ts Updates session meta matching logic to support payload.cwd in newer Codex CLI versions.
packages/core/src/plugin-registry.ts Registers gemini in the built-in plugin list for automatic discovery/loading.
packages/cli/src/lib/plugins.ts Adds static import + registration mapping for Gemini agent in the CLI.
packages/cli/src/lib/detect-agent.ts Adds Gemini to the CLI agent auto-detection list.
packages/cli/package.json Adds the Gemini agent plugin as a workspace dependency.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

else
tool_name=\$(echo "\$input" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4 || echo "")
command=\$(echo "\$input" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4 || echo "")
output=\$(echo "\$input" | grep -o '"llmContent"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4 || echo "")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

In the jq-absent fallback parser, output is extracted only from the llmContent field. When Gemini’s hook payload provides the command output under tool_response (string) without llmContent, this fallback will silently miss PR URLs and metadata won’t update. Consider expanding the fallback to also extract tool_response (and/or tool_response.llmContent) similarly to the jq path.

Suggested change
output=\$(echo "\$input" | grep -o '"llmContent"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4 || echo "")
output=\$(echo "\$input" | grep -o '"llmContent"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4 || echo "")
if [[ -z "\$output" ]]; then
output=\$(echo "\$input" | grep -o '"tool_response"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4 || echo "")
fi

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +56
# Input : JSON on stdin (session_id, tool_name, tool_input, tool_response, …)
# Output: JSON on stdout (empty {} by default, systemMessage on updates)

set -euo pipefail

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The hook script updates AO metadata based on the detected command, but it never checks the tool’s exit status. This can incorrectly mark status=merged / status=pr_open or update branch metadata even when the underlying gh/git command failed. Consider parsing exit_code from the hook JSON (Gemini includes it) and only applying metadata updates when it is 0 (similar to the existing wrapper/hook logic elsewhere in the repo).

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +96
# Confirm metadata file is still inside ao_dir after symlink resolution
real_ao_dir=\$(cd "\$AO_DATA_DIR" 2>/dev/null && pwd -P) || { echo '{}'; exit 0; }
real_dir=\$(cd "\$(dirname "\$metadata_file")" 2>/dev/null && pwd -P) || { echo '{}'; exit 0; }
[[ "\$real_dir" == "\$real_ao_dir"* ]] || { echo '{}'; exit 0; }
[[ -f "\$metadata_file" ]] || { echo '{}'; exit 0; }
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The path allowlist for AO_DATA_DIR is checked before canonicalization, but after pwd -P the script does not re-validate the canonical real_ao_dir against the allowlist. This leaves a bypass where a value like /tmp/../../home/user/.agent-orchestrator/... passes the initial /tmp/* check but resolves outside /tmp. Consider re-validating real_ao_dir after canonicalization (as done in packages/core/src/agent-workspace-hooks.ts) before allowing metadata writes.

Copilot uses AI. Check for mistakes.
Comment on lines 38 to 42
{ slot: "agent", name: "claude-code", pkg: "@composio/ao-plugin-agent-claude-code" },
{ slot: "agent", name: "codex", pkg: "@composio/ao-plugin-agent-codex" },
{ slot: "agent", name: "aider", pkg: "@composio/ao-plugin-agent-aider" },
{ slot: "agent", name: "gemini", pkg: "@composio/ao-plugin-agent-gemini" },
{ slot: "agent", name: "opencode", pkg: "@composio/ao-plugin-agent-opencode" },
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Adding Gemini to BUILTIN_PLUGINS means loadBuiltins() will now attempt to import @composio/ao-plugin-agent-gemini. The existing unit test packages/core/src/__tests__/plugin-registry.test.ts stubs importFn for only claude-code/codex/opencode and will start failing unless it’s updated to handle the new package. Also note that the Next.js web app registers a hardcoded subset of agent plugins in packages/web/src/lib/services.ts and transpiles a hardcoded list in packages/web/next.config.js; if the web dashboard is expected to support agent: gemini, those lists need to include the new plugin as well.

Copilot uses AI. Check for mistakes.
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.

Add Gemini CLI plugin

3 participants