Skip to content

Commit ded6dbb

Browse files
committed
ci(workflow): Support intermediate steps output in Antigravity runner
- Adds secure command policy to the LocalAgentConfig in scripts/run_antigravity.py that denies unsafe command executions and checks for shell injection. - Allows both `gh` and `git` commands to be executed by the agent runner. - Adds custom `fetch_github_issue` and `fetch_github_pr` Python tools to the Antigravity agent runner configuration, using `curl` to enable direct JSON metadata fetches from GitHub without requiring a configured `gh` CLI environment. - Introduces the --show-steps CLI flag to scripts/run_antigravity.py to output intermediate thoughts, tool calls, and tool results (default off). - Updates .github/workflows/issue-analyze.yml to capture stdout from the runner script, run automatic triage for any user's opened issues, and post the report to the triggering GitHub issue as a comment via the `gh` CLI tool. - Updates the adk-issue-analyze and adk-pr-triage skills to prefer using the custom `fetch_github_issue` Python tool over raw `gh` command lines, and strictly enforces that the issue-analyze skill is read-only and must not edit files. Change-Id: I58a9c64f0680a56d07b9877cbcb9ffe027afeee2
1 parent 0287d46 commit ded6dbb

4 files changed

Lines changed: 212 additions & 19 deletions

File tree

.agents/skills/adk-issue-analyze/SKILL.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ description: Analyze and triage a GitHub issue for the adk-python repository. Us
88
This skill provides a structured workflow for analyzing, verifying, and triaging GitHub issues from the `google/adk-python` repository. When instructed to analyze/triage an issue, follow this read-only workflow.
99

1010
> [!IMPORTANT]
11-
> This skill is strictly **read-only**: Do NOT modify any code, create new branches, or write any implementation during this phase.
11+
> **Strict Read-Only Constraint**:
12+
> This skill is strictly **read-only**. You MUST NOT modify any code, create new branches, or write any implementation. Your role is only to analyze the issue and output the report. Do NOT use file creation or editing tools (e.g. `write_to_file`, `replace_file_content`, `edit_file`, etc.).
13+
>
14+
> **Strict Tooling Constraint**:
15+
> Do NOT use `curl`, `wget`, or any HTTP requests to fetch issue/PR content. You MUST parse/extract the issue number and use strictly the custom `fetch_github_issue` / `fetch_github_pr` python tools (or the `gh` command).
1216
1317
## Step 1: Retrieve and Parse the Issue
1418
1. **Extract the issue number**: Parse the number from the link or prompt (e.g., `https://github.com/google/adk-python/issues/5882` -> `5882`).
15-
2. **Fetch issue details**: Use the `gh` CLI tool to fetch issue details in JSON format:
19+
2. **Fetch issue details**: Use the custom python tool `fetch_github_issue(issue_number=<number>)` to get the issue metadata. This is the preferred method as it avoids command execution policy issues.
20+
*If the custom python tool is not available, fall back to running the gh command:*
1621
```bash
1722
gh issue view <issue_number> --repo google/adk-python --json number,title,body,state,labels,comments,assignees,createdAt,closedAt
1823
```
19-
*If the `gh` CLI is not available or errors out, use `read_url_content` to fetch the public GitHub issue page:*
20-
```
21-
https://github.com/google/adk-python/issues/<issue_number>
22-
```
2324

2425
---
2526

@@ -108,6 +109,15 @@ Present your final analysis as a high-quality markdown response using the follow
108109
---
109110

110111
## Tips & Best Practices
112+
> [!IMPORTANT]
113+
> **Command Sandbox Policy**:
114+
> When running commands via `run_command`, you MUST ONLY use `gh` or `git` commands. Commands like `curl`, `wget`, or direct HTTP network requests are strictly forbidden and will be automatically denied.
115+
> Furthermore, you MUST ONLY use simple commands without special characters (such as `;`, `&`, `|`, `$`, `` ` ``, `<`, `>`, `\n`, `\r`, `(`, `)`, `{`, `}`, `\`). The runner environment runs a security policy that automatically denies any commands containing these characters. Always run clean `gh` or `git` commands directly with arguments, without redirections, command chaining, or shell expansions.
116+
117+
> [!IMPORTANT]
118+
> **Strict Read-Only Enforcement**:
119+
> When executing the `adk-issue-analyze` skill, you MUST NOT use any file modification or editing tools (such as `edit_file`, `replace_file_content`, `write_to_file`, `notebook_edit`, etc.). Your output must strictly be a text markdown report following the template provided, without editing any workspace files or writing/fixing code.
120+
111121
> [!TIP]
112122
> Always use explicit repository qualifiers (`--repo google/adk-python`) when running `gh` commands to avoid failures due to custom internal or local git remotes.
113123

.agents/skills/adk-pr-triage/SKILL.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ This skill guides AI assistants in conducting a highly professional, rigorous, a
3030
> Wait for instructions before performing any branch creation or Gerrit push.
3131
---
3232
## Phase 1: Retrieve and Parse the PR & Linked Context (Read-Only)
33+
> [!IMPORTANT]
34+
> **Strict Tooling Constraint**: Do NOT use `curl`, `wget`, or any HTTP requests to fetch PR/issue content. You MUST parse/extract the numbers and use strictly the custom `fetch_github_issue` / `fetch_github_pr` python tools, the `gh` command, or the helper scripts provided.
35+
3336
### Step 1: Extract PR Identifier, Verify CLA Signature & PR Assignment (Mandatory Entry Gate)
3437
1. **Identify the PR identifier**: Parse the PR number or URL from the prompt (e.g., `https://github.com/google/adk-python/pull/5885` -> `5885`).
3538
2. **CRITICAL COMPLIANCE & ASSIGNMENT GATES - Run Verification Script**:
@@ -63,10 +66,11 @@ This skill guides AI assistants in conducting a highly professional, rigorous, a
6366
- **PR IS ALREADY ASSIGNED TO YOU**: Proceed directly with Step 1.3.
6467
3. **Parse PR Details from Script Output**: The verification script in Step 2 outputs the complete PR details JSON directly to standard output, wrapped in `[PR_METADATA_JSON]` and `[/PR_METADATA_JSON]` tags. Do NOT write to or read from local cache files, and do NOT make separate network commands to fetch PR details. Parse the JSON metadata directly from the command's stdout:
6568
* **Key JSON Attributes**: `number`, `title`, `body`, `state`, `url`, `author`, `additions`, `deletions`, `changedFiles`, `labels`, `assignees`, `closingIssuesReferences` (used to locate linked issues).
66-
4. **Locate and Fetch Linked Issue(s)**: Extract linked closing issues directly from the `closingIssuesReferences` array in the parsed JSON metadata from the script's stdout. If any closing issues are linked, fetch their details to understand the original problem statement:
67-
```bash
68-
gh issue view <issue_number> --repo google/adk-python --json number,title,body,state
69-
```
69+
4. **Locate and Fetch Linked Issue(s)**: Extract linked closing issues directly from the `closingIssuesReferences` array in the parsed JSON metadata from the script's stdout. If any closing issues are linked, fetch their details using the custom python tool `fetch_github_issue(issue_number=<number>)`. This is preferred as it avoids command execution policy issues.
70+
*If the custom python tool is not available, run the gh command:*
71+
```bash
72+
gh issue view <issue_number> --repo google/adk-python --json number,title,body,state
73+
```
7074
### Step 2: Retrieve the Complete Diff
7175
1. **Fetch pull request changes**: Run the `gh pr diff` command to view the actual line-by-line diff of the PR:
7276
```bash
@@ -280,6 +284,11 @@ Please let me know if you have any questions on these suggestions, and let's wor
280284
````
281285
---
282286
## Tips & Best Practices
287+
> [!IMPORTANT]
288+
> **Command Sandbox Policy**:
289+
> When running commands via `run_command`, you MUST ONLY use `gh` or `git` commands. Commands like `curl`, `wget`, or direct HTTP network requests are strictly forbidden and will be automatically denied.
290+
> Furthermore, you MUST ONLY use simple commands without special characters (such as `;`, `&`, `|`, `$`, `` ` ``, `<`, `>`, `\n`, `\r`, `(`, `)`, `{`, `}`, `\`). The runner environment runs a security policy that automatically denies any commands containing these characters. Always run clean `gh` or `git` commands directly with arguments, without redirections, command chaining, or shell expansions.
291+
283292
> [!TIP]
284293
> Always verify the baseline behavior in your active workspace before claiming something is a bug or invalid. Reading the current source files using `view_file` gives you full context.
285294
> [!IMPORTANT]

.github/workflows/issue-analyze.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ jobs:
3434
github.event_name == 'workflow_dispatch' ||
3535
(github.event_name == 'issue_comment' &&
3636
!github.event.issue.pull_request &&
37-
startsWith(github.event.comment.body, '/adk-issue-analyze'))
37+
startsWith(github.event.comment.body, '/adk-issue-analyze') && (
38+
github.event.comment.author_association == 'OWNER' ||
39+
github.event.comment.author_association == 'MEMBER' ||
40+
github.event.comment.author_association == 'COLLABORATOR'
41+
))
3842
)
3943
runs-on: ubuntu-latest
4044
permissions:
@@ -63,4 +67,12 @@ jobs:
6367
env:
6468
GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }}
6569
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
66-
run: python scripts/run_antigravity.py "/adk-issue-analyze ${{ github.event.issue.html_url || inputs.issue_url }}"
70+
run: |
71+
python scripts/run_antigravity.py "/adk-issue-analyze ${{ github.event.issue.html_url || inputs.issue_url }}" > triage_report.md
72+
cat triage_report.md
73+
74+
- name: Post Triage Report as Comment
75+
env:
76+
GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }}
77+
run: |
78+
gh issue comment "${{ github.event.issue.html_url || inputs.issue_url }}" --body-file triage_report.md

scripts/run_antigravity.py

Lines changed: 169 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@
1414

1515
"""Runner script to execute prompts/commands via the Antigravity SDK."""
1616

17+
import argparse
1718
import asyncio
1819
import os
20+
import shlex
1921
import sys
22+
from typing import Any
2023

2124
try:
2225
from google.antigravity import Agent
2326
from google.antigravity import CapabilitiesConfig
2427
from google.antigravity import LocalAgentConfig
28+
from google.antigravity.hooks import policy
29+
from google.antigravity.types import Text
30+
from google.antigravity.types import Thought
31+
from google.antigravity.types import ToolCall
32+
from google.antigravity.types import ToolResult
2533
except ImportError:
2634
print(
2735
"Error: google-antigravity package is not installed. Run 'pip install"
@@ -31,12 +39,125 @@
3139
sys.exit(1)
3240

3341

42+
def _is_safe_command(args: dict[str, Any]) -> bool:
43+
"""Validates if the command is a safe 'gh' or 'git' execution with no shell injections."""
44+
cmd = (args.get("command_line") or args.get("CommandLine") or "").strip()
45+
if not cmd:
46+
return False
47+
48+
# Forbid shell metacharacters and control characters
49+
forbidden_chars = {
50+
";",
51+
"&",
52+
"|",
53+
"$",
54+
"`",
55+
"<",
56+
">",
57+
"\n",
58+
"\r",
59+
"(",
60+
")",
61+
"\\",
62+
"{",
63+
"}",
64+
}
65+
if any(char in cmd for char in forbidden_chars):
66+
return False
67+
68+
try:
69+
tokens = shlex.split(cmd)
70+
except ValueError:
71+
return False
72+
73+
if not tokens:
74+
return False
75+
76+
return tokens[0] in {"gh", "git"}
77+
78+
79+
def fetch_github_issue(issue_number: int) -> str:
80+
"""Fetches the details of a GitHub issue from the google/adk-python repository.
81+
82+
Args:
83+
issue_number: The issue number (e.g. 5949).
84+
"""
85+
import subprocess
86+
87+
# Use curl to fetch the issue details.
88+
# This supports running it outside of the gh CLI environment (e.g. without login/remotes setup).
89+
cmd = [
90+
"curl",
91+
"-s",
92+
]
93+
token = os.environ.get("GITHUB_TOKEN")
94+
if token:
95+
cmd.extend(["-H", f"Authorization: token {token}"])
96+
cmd.append(
97+
f"https://api.github.com/repos/google/adk-python/issues/{issue_number}"
98+
)
99+
100+
try:
101+
res = subprocess.run(cmd, capture_output=True, text=True, check=False)
102+
if res.returncode != 0:
103+
return (
104+
f"Error: Failed to fetch issue {issue_number}: {res.stderr.strip()}"
105+
)
106+
return res.stdout.strip()
107+
except Exception as e:
108+
return f"Error: Failed to run curl command: {e}"
109+
110+
111+
def fetch_github_pr(pr_number: int) -> str:
112+
"""Fetches the details of a GitHub Pull Request from the google/adk-python repository.
113+
114+
Args:
115+
pr_number: The PR number (e.g. 5956).
116+
"""
117+
import subprocess
118+
119+
# Use curl to fetch the PR details.
120+
# This supports running it outside of the gh CLI environment (e.g. without login/remotes setup).
121+
cmd = [
122+
"curl",
123+
"-s",
124+
]
125+
token = os.environ.get("GITHUB_TOKEN")
126+
if token:
127+
cmd.extend(["-H", f"Authorization: token {token}"])
128+
cmd.append(
129+
f"https://api.github.com/repos/google/adk-python/pulls/{pr_number}"
130+
)
131+
132+
try:
133+
res = subprocess.run(cmd, capture_output=True, text=True, check=False)
134+
if res.returncode != 0:
135+
return f"Error: Failed to fetch PR {pr_number}: {res.stderr.strip()}"
136+
return res.stdout.strip()
137+
except Exception as e:
138+
return f"Error: Failed to run curl command: {e}"
139+
140+
34141
async def main():
35-
if len(sys.argv) < 2:
36-
print("Usage: python run_antigravity.py <prompt>", file=sys.stderr)
37-
sys.exit(1)
142+
parser = argparse.ArgumentParser(
143+
description=(
144+
"Runner script to execute prompts/commands via the Antigravity SDK."
145+
)
146+
)
147+
parser.add_argument(
148+
"--show-steps",
149+
action="store_true",
150+
help="Show intermediate thoughts, tool calls, and tool results.",
151+
)
152+
parser.add_argument(
153+
"prompt",
154+
nargs="+",
155+
help="The prompt to send to the Antigravity Agent.",
156+
)
157+
parsed_args = parser.parse_args()
38158

39-
prompt = " ".join(sys.argv[1:])
159+
show_steps = parsed_args.show_steps
160+
prompt = " ".join(parsed_args.prompt)
40161

41162
# Ensure GEMINI_API_KEY is set (using GOOGLE_API_KEY as fallback)
42163
if "GOOGLE_API_KEY" in os.environ and "GEMINI_API_KEY" not in os.environ:
@@ -49,16 +170,57 @@ async def main():
49170
)
50171
sys.exit(1)
51172

173+
skills_dir = os.path.abspath(
174+
os.path.join(os.path.dirname(__file__), "..", ".agents", "skills")
175+
)
52176
config = LocalAgentConfig(
53177
capabilities=CapabilitiesConfig(),
178+
tools=[fetch_github_issue, fetch_github_pr],
179+
policies=[
180+
policy.deny(
181+
"run_command",
182+
when=lambda args: not _is_safe_command(args),
183+
name="only_allow_gh_and_git",
184+
),
185+
],
186+
skills_paths=[skills_dir],
54187
)
55188

56189
try:
57190
async with Agent(config) as agent:
58191
response = await agent.chat(prompt)
59-
async for token in response:
60-
sys.stdout.write(token)
61-
sys.stdout.flush()
192+
if show_steps:
193+
in_thinking = False
194+
async for chunk in response.chunks:
195+
if isinstance(chunk, Thought):
196+
if not in_thinking:
197+
sys.stdout.write("[Thinking...]\n")
198+
in_thinking = True
199+
sys.stdout.write(chunk.text)
200+
sys.stdout.flush()
201+
elif isinstance(chunk, ToolCall):
202+
if in_thinking:
203+
sys.stdout.write("\n[End of Thinking]\n")
204+
in_thinking = False
205+
print(
206+
f"\n[Calling Tool: {chunk.name} with args: {chunk.args}]",
207+
flush=True,
208+
)
209+
elif isinstance(chunk, ToolResult):
210+
status = f"Error: {chunk.error}" if chunk.error else "Success"
211+
print(f"\n[Tool {chunk.name} finished: {status}]", flush=True)
212+
elif isinstance(chunk, Text):
213+
if in_thinking:
214+
sys.stdout.write("\n[End of Thinking]\n")
215+
in_thinking = False
216+
sys.stdout.write(chunk.text)
217+
sys.stdout.flush()
218+
if in_thinking:
219+
sys.stdout.write("\n[End of Thinking]\n")
220+
else:
221+
async for token in response:
222+
sys.stdout.write(token)
223+
sys.stdout.flush()
62224
print()
63225
except Exception as e: # pylint: disable=broad-exception-caught
64226
print(f"\nError running Antigravity Agent: {e}", file=sys.stderr)

0 commit comments

Comments
 (0)