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
11 changes: 8 additions & 3 deletions .github/harness/prompts/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ Review this GitHub PR: {pr_url}
You have tools to fetch the PR diff, read files, search the web, and post comments on the PR.

You have these repos cloned locally for context:

- /opt/workspace/agentcore-cli — aws/agentcore-cli
- /opt/workspace/agentcore-l3-cdk-constructs — aws/agentcore-l3-cdk-constructs

Before reviewing, read all existing comments on the PR to understand what has already been discussed. Do not repeat or re-post issues that have already been raised in existing comments.
Before reviewing, read all existing comments on the PR to understand what has already been discussed. Do not repeat or
re-post issues that have already been raised in existing comments.

Review the PR. If there are any serious issues that require code changes before merging, post a comment on the PR for each issue explaining the problem. If there are multiple ways to fix an issue, list the options so the author can choose. Skip style nits and minor suggestions — only flag things that actually need to change.
Review the PR. If there are any serious issues that require code changes before merging, post a comment on the PR for
each issue explaining the problem. If there are multiple ways to fix an issue, list the options so the author can
choose. Skip style nits and minor suggestions — only flag things that actually need to change.

If all serious issues have already been raised in existing comments, or if you found no new issues, post a single comment on the PR saying it looks good to merge (or that all issues have already been flagged).
If all serious issues have already been raised in existing comments, or if you found no new issues, post a single
comment on the PR saying it looks good to merge (or that all issues have already been flagged).
10 changes: 7 additions & 3 deletions .github/harness/prompts/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ This workspace contains two repos for developing and testing the AgentCore CLI.

### agentcore-cli/ (`aws/agentcore-cli`)

The terminal experience for creating, developing, and deploying AI agents to AgentCore. Node.js/TypeScript CLI built with Ink (React-based TUI).
The terminal experience for creating, developing, and deploying AI agents to AgentCore. Node.js/TypeScript CLI built
with Ink (React-based TUI).

### agentcore-l3-cdk-constructs/ (`aws/agentcore-l3-cdk-constructs`)

AWS CDK L3 constructs for declaring and deploying AgentCore infrastructure. Used by agentcore-cli to vend CDK projects when users run `agentcore create`.
AWS CDK L3 constructs for declaring and deploying AgentCore infrastructure. Used by agentcore-cli to vend CDK projects
when users run `agentcore create`.

## How they relate

`agentcore-cli` is the main product. It vends CDK projects using constructs from `agentcore-l3-cdk-constructs`.

## Testing with a bundled distribution

Run `npm run bundle` in `agentcore-cli/` to create a tar distribution that includes the packaged `agentcore-l3-cdk-constructs`. You can then install it globally with `npm install -g <path-to-tar>` to test the CLI end-to-end.
Run `npm run bundle` in `agentcore-cli/` to create a tar distribution that includes the packaged
`agentcore-l3-cdk-constructs`. You can then install it globally with `npm install -g <path-to-tar>` to test the CLI
end-to-end.
13 changes: 13 additions & 0 deletions .github/scripts/prompts/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Review this GitHub PR: {pr_url}

You have tools to fetch the PR diff, read files, search the web, and post comments on the PR.

You have these repos cloned locally for context:
- /opt/workspace/agentcore-cli — aws/agentcore-cli
- /opt/workspace/agentcore-l3-cdk-constructs — aws/agentcore-l3-cdk-constructs

Before reviewing, read all existing comments on the PR to understand what has already been discussed. Do not repeat or re-post issues that have already been raised in existing comments.

Review the PR. If there are any serious issues that require code changes before merging, post a comment on the PR for each issue explaining the problem. If there are multiple ways to fix an issue, list the options so the author can choose. Skip style nits and minor suggestions — only flag things that actually need to change.

If all serious issues have already been raised in existing comments, or if you found no new issues, post a single comment on the PR saying it looks good to merge (or that all issues have already been flagged).
21 changes: 21 additions & 0 deletions .github/scripts/prompts/system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# AgentCore CLI Development Workspace

This workspace contains two repos for developing and testing the AgentCore CLI.

## Repositories

### agentcore-cli/ (`aws/agentcore-cli`)

The terminal experience for creating, developing, and deploying AI agents to AgentCore. Node.js/TypeScript CLI built with Ink (React-based TUI).

### agentcore-l3-cdk-constructs/ (`aws/agentcore-l3-cdk-constructs`)

AWS CDK L3 constructs for declaring and deploying AgentCore infrastructure. Used by agentcore-cli to vend CDK projects when users run `agentcore create`.

## How they relate

`agentcore-cli` is the main product. It vends CDK projects using constructs from `agentcore-l3-cdk-constructs`.

## Testing with a bundled distribution

Run `npm run bundle` in `agentcore-cli/` to create a tar distribution that includes the packaged `agentcore-l3-cdk-constructs`. You can then install it globally with `npm install -g <path-to-tar>` to test the CLI end-to-end.
217 changes: 217 additions & 0 deletions .github/scripts/python/harness_review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
"""Invoke Bedrock AgentCore Harness to review a GitHub PR.

Reads PR_URL from the environment. Streams harness output to stdout.
Uses raw HTTP with SigV4 signing — no custom service model needed.
"""

import json
import os
import sys
import time
import uuid

import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from botocore.eventstream import EventStreamBuffer
from urllib.parse import quote
import urllib3

# ANSI color codes
CYAN = "\033[36m"
YELLOW = "\033[33m"
GREEN = "\033[32m"
RED = "\033[31m"
DIM = "\033[2m"
RESET = "\033[0m"

SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..")


def read_prompt(filename):
"""Read a prompt template from the prompts directory."""
path = os.path.join(SCRIPTS_DIR, "prompts", filename)
with open(path) as f:
return f.read()


def invoke_harness(harness_arn, body, region):
"""Send a SigV4-signed request to the harness invoke endpoint. Returns a streaming response.

InvokeHarness is not in standard boto3, so we call the REST API directly.
boto3 is only used to resolve AWS credentials (from env vars, OIDC, etc.)
and sign the request with SigV4. The response is an AWS binary event stream.
"""
session = boto3.Session(region_name=region)
credentials = session.get_credentials().get_frozen_credentials()
url = f"https://bedrock-agentcore.{region}.amazonaws.com/harnesses/invoke?harnessArn={quote(harness_arn, safe='')}"
request = AWSRequest(method="POST", url=url, data=body, headers={
"Content-Type": "application/json",
"Accept": "application/vnd.amazon.eventstream",
})
SigV4Auth(credentials, "bedrock-agentcore", region).add_auth(request)
return urllib3.PoolManager().urlopen(
"POST", url, body=body,
headers=dict(request.headers),
preload_content=False,
timeout=urllib3.Timeout(connect=10, read=600),
)


def parse_events(http_response):
"""Yield (event_type, payload) tuples from the harness binary event stream.

The response arrives as raw bytes in AWS binary event stream format.
EventStreamBuffer reassembles complete events from the 4KB chunks,
and we decode each event's JSON payload before yielding it.
"""
event_buffer = EventStreamBuffer()
for chunk in http_response.stream(4096):
event_buffer.add_data(chunk)
for event in event_buffer:
if event.headers.get(":message-type") == "exception":
payload = json.loads(event.payload.decode("utf-8"))
print(f"\n{RED}ERROR: {payload}{RESET}", file=sys.stderr)
sys.exit(1)
event_type = event.headers.get(":event-type", "")
if event.payload:
yield event_type, json.loads(event.payload.decode("utf-8"))


def print_stream(http_response):
"""Display harness events with GitHub Actions log groups.

The harness streams events as the agent works:
contentBlockStart — a new block begins (text or tool call)
contentBlockDelta — incremental chunks of text or tool input JSON
contentBlockStop — block complete, we now have full tool input to display
messageStop — agent finished
internalServerException — server error

Tool calls are wrapped in ::group::/::endgroup:: for collapsible sections
in the GitHub Actions log UI. Agent reasoning text is printed inline in dim.
"""
start_time = time.time()
iteration = 0
tool_name = None
tool_input = ""
tool_start = 0.0
in_group = False
text_buffer = ""

def close_group():
nonlocal in_group
if in_group:
print("::endgroup::", flush=True)
in_group = False

def flush_text():
nonlocal text_buffer
if text_buffer:
for line in text_buffer.splitlines():
print(f"{DIM}{line}{RESET}", flush=True)
text_buffer = ""

for event_type, payload in parse_events(http_response):

if event_type == "contentBlockStart":
start = payload.get("start", {})
if "toolUse" in start:
tool_name = start["toolUse"].get("name", "unknown")
tool_input = ""
tool_start = time.time()
iteration += 1

elif event_type == "contentBlockDelta":
delta = payload.get("delta", {})
if "text" in delta:
close_group()
text_buffer += delta["text"]
if "toolUse" in delta:
tool_input += delta["toolUse"].get("input", "")

elif event_type == "contentBlockStop":
flush_text()
if tool_name:
elapsed = time.time() - tool_start
try:
parsed = json.loads(tool_input)
except (json.JSONDecodeError, TypeError):
parsed = tool_input

close_group()

cmd = parsed.get("command") if isinstance(parsed, dict) else None
header = f"{CYAN}[{iteration}]{RESET} {YELLOW}{tool_name}{RESET} {DIM}({elapsed:.1f}s){RESET}"
if cmd:
header += f": $ {cmd}"

print(f"::group::{header}", flush=True)
in_group = True

if isinstance(parsed, dict):
for k, v in parsed.items():
if k != "command":
print(f" {DIM}{k}:{RESET} {str(v)[:300]}", flush=True)

tool_name = None
tool_input = ""

elif event_type == "messageStop":
flush_text()
close_group()
if payload.get("stopReason") == "end_turn":
total = time.time() - start_time
print(f"\n\n{GREEN}{'=' * 50}", flush=True)
print(f" Done ({int(total // 60)}m {int(total % 60)}s)", flush=True)
print(f"{'=' * 50}{RESET}", flush=True)

elif event_type == "internalServerException":
close_group()
print(f"\n{RED}ERROR: {payload}{RESET}", file=sys.stderr)
sys.exit(1)

close_group()
total = time.time() - start_time
print(f"\n{GREEN}Review complete.{RESET} {DIM}({iteration} tool calls, {int(total)}s total){RESET}")


# --- Main ---

# All config comes from environment variables (set via GitHub secrets/workflow)
MODEL_ID = os.environ.get("HARNESS_MODEL_ID", "us.anthropic.claude-opus-4-7")
HARNESS_ARN = os.environ.get("HARNESS_ARN", "")
PR_URL = os.environ.get("PR_URL", "")

for name, val in [("HARNESS_ARN", HARNESS_ARN), ("PR_URL", PR_URL)]:
if not val:
print(f"{RED}ERROR: {name} environment variable is required{RESET}", file=sys.stderr)
sys.exit(1)

# Extract region from the ARN (arn:aws:bedrock-agentcore:{region}:{account}:harness/{id})
REGION = HARNESS_ARN.split(":")[3]
SESSION_ID = str(uuid.uuid4()).upper()

print(f"{CYAN}Session:{RESET} {SESSION_ID}")
print(f"{CYAN}PR:{RESET} {PR_URL}")
print(f"{CYAN}Harness:{RESET} {HARNESS_ARN}")
print()

SYSTEM_PROMPT = read_prompt("system.md")
REVIEW_PROMPT = read_prompt("review.md").format(pr_url=PR_URL)

request_body = json.dumps({
"runtimeSessionId": SESSION_ID,
"systemPrompt": [{"text": SYSTEM_PROMPT}],
"messages": [{"role": "user", "content": [{"text": REVIEW_PROMPT}]}],
"model": {"bedrockModelConfig": {"modelId": MODEL_ID}},
})

http_response = invoke_harness(HARNESS_ARN, request_body, REGION)

if http_response.status != 200:
error = http_response.read().decode("utf-8")
print(f"{RED}ERROR: HTTP {http_response.status}: {error}{RESET}", file=sys.stderr)
sys.exit(1)

print_stream(http_response)
5 changes: 3 additions & 2 deletions .github/workflows/e2e-tests-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
fail-fast: false
matrix:
cdk-source: [npm, main]
shard: ['1/6', '2/6', '3/6', '4/6', '5/6', '6/6']
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -70,15 +71,15 @@ jobs:
CDK_REPO: ${{ secrets.CDK_REPO_NAME }}
- name: Install CLI globally
run: npm install -g "$(npm pack | tail -1)"
- name: Run E2E tests (${{ matrix.cdk-source }})
- name: Run E2E tests (${{ matrix.cdk-source }}, shard ${{ matrix.shard }})
env:
AWS_ACCOUNT_ID: ${{ steps.aws.outputs.account_id }}
AWS_REGION: ${{ inputs.aws_region || 'us-east-1' }}
ANTHROPIC_API_KEY: ${{ env.E2E_ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ env.E2E_OPENAI_API_KEY }}
GEMINI_API_KEY: ${{ env.E2E_GEMINI_API_KEY }}
CDK_TARBALL: ${{ env.CDK_TARBALL }}
run: npm run test:e2e
run: npx vitest run --project e2e --shard=${{ matrix.shard }}
browser-tests:
runs-on: ubuntu-latest
environment: e2e-testing
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/slack-issue-notification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send issue details to Slack
uses: slackapi/slack-github-action@v3.0.2
uses: slackapi/slack-github-action@v2.1.1
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: webhook-trigger
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/slack-open-prs-notification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
);
- name: Send open PRs summary to Slack
uses: slackapi/slack-github-action@v3.0.2
uses: slackapi/slack-github-action@v2.1.1
with:
webhook: ${{ secrets.SLACK_OPEN_PRS_WEBHOOK_URL }}
webhook-type: webhook-trigger
Expand Down
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ ProtocolTesting/
.cdk-constructs-clone/
.omc/

# E2E test artifacts
e2e-tests/fixtures/import/bugbash-resources.json

# oh-my-claudecode
.omc/
# Browser tests
browser-tests/.browser-test-env
browser-tests/test-results/
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CHANGELOG.md
src/assets/**/*.md
.github/harness/prompts/
.github/scripts/prompts/
Loading
Loading