[Feat]: Add gemini plugin#912
Conversation
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>
b68b4f4 to
06d75cd
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 414b215. Configure here.
| * flag — GEMINI.md is the correct way to inject system context. | ||
| */ | ||
| let pendingSystemPromptFile: string | null = null; | ||
| let pendingSystemPrompt: string | null = null; |
There was a problem hiding this comment.
Shared mutable state causes system prompt race condition
High Severity
pendingSystemPromptFile and pendingSystemPrompt are closure variables on a single Agent instance. The plugin registry calls create() once and shares that instance across all sessions via registry.get(). When two Gemini sessions spawn concurrently, the second getLaunchCommand call overwrites the first session's pending prompt, and the first postLaunchSetup then clears the variables — leaving the second session with no system prompt at all.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 414b215. Configure here.
| case "\$AO_DATA_DIR" in | ||
| "\$HOME"/.ao/* | "\$HOME"/.agent-orchestrator/* | /tmp/*) ;; | ||
| *) echo '{}'; exit 0 ;; | ||
| esac |
There was a problem hiding this comment.
Hook script default AO_DATA_DIR fails its own whitelist
Medium Severity
The bash hook script's default AO_DATA_DIR of $HOME/.ao-sessions doesn't match the whitelist on lines 82–85. The glob "$HOME"/.ao/* requires .ao/ (with a slash), but .ao-sessions starts with .ao- (a hyphen). When AO_DATA_DIR is unset, the script silently exits without updating metadata. The shared agent-workspace-hooks.ts avoids this by using an empty default ("${AO_DATA_DIR:-}").
Reviewed by Cursor Bugbot for commit 414b215. Configure here.
| if (config.issueId) { | ||
| env["AO_ISSUE_ID"] = config.issueId; | ||
| } | ||
| return env; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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.


What
Adds
@composio/ao-plugin-agent-gemini- full support for Google Gemini CLI as an agent in Agent Orchestrator. Any project can now setagent: geminiinagent-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-pflag 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 viaruntime.sendMessage().Absolute binary resolution -
resolveGeminiBinarySync()callswhich geminiinside 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.jsonwhen 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 againstgoogle-gemini/gemini-clisource.Metadata hooks -
AfterToolhook in.gemini/settings.jsonfires after everyrun_shell_command. Detectsgh pr create,git checkout -b, andgh pr mergeto auto-update AO session metadata. Uses a relative path for symlink safety across worktrees.Activity classification - reads
lastUpdatedfrom session JSON (more accurate than file mtime). 30-second session file cache prevents doublereaddirper 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),
postLaunchSetupREPL readiness polling.