Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
297452c
feat: Increase reaction image size to 200px max height in comments
Aug 30, 2025
fa1bc3b
feat: Add markdown image rendering and Selenium UI test suite
Sep 3, 2025
635affc
feat: Add responsive widescreen layout and comprehensive UI tests
Sep 3, 2025
5e06517
fix: Fix HTML rendering in MySpace-style agent profiles
Sep 3, 2025
abd49e7
Fix all linting issues for clean commit
Sep 3, 2025
ccce00a
feat: Add markdown code block rendering and clickable username profil…
Sep 3, 2025
c2dc925
fix: Address Gemini's code review feedback and fix lint errors
Sep 3, 2025
ee1bcaf
fix: Address Gemini's security feedback
Sep 3, 2025
43e34b0
fix: Address critical security vulnerabilities from Gemini review
Sep 4, 2025
98e1933
fix: Address Gemini's minor feedback on test infrastructure
Sep 4, 2025
5eb2f6f
fix: Improve pre-commit hook reliability and debugging
Sep 4, 2025
d537e72
fix: Remove remaining error suppression in pre-commit hook
Sep 4, 2025
bbe9615
fix: Comprehensive security improvements per Gemini review
Sep 4, 2025
6d2be59
feat: Add comprehensive seeding API and test data infrastructure
Sep 4, 2025
a62b120
fix: Resolve mypy type hints and UI smoke test issues
Sep 4, 2025
6d03f2a
fix: Fix markdown code block rendering with proper triple backtick su…
Sep 4, 2025
c7efe0c
fix: Address Gemini's security review with comprehensive improvements
Sep 4, 2025
58d9092
feat: Add server-side markdown rendering and syntax highlighting tests
Sep 4, 2025
442ea73
fix: Address Gemini's code review suggestions
Sep 4, 2025
9ad29f9
fix: Improve build stability and security documentation per Gemini re…
Sep 4, 2025
b4a74a8
fix: Resolve lint errors and suppress Docker Compose warnings
Sep 4, 2025
9a0c411
fix: Address Gemini security review and improve CI stability
Sep 4, 2025
2f153de
refactor: Improve code organization and add UI test linting
Sep 4, 2025
74c9c61
fix: Resolve lint issues and exclude external folders from linting
Sep 4, 2025
8b72137
fix: Exclude packages/github_ai_agents from linting
Sep 4, 2025
1f642d3
fix: Enable reaction image rendering in comments
Sep 4, 2025
f6c994b
fix: Enable retro MySpace-style HTML effects in user profiles
Sep 4, 2025
c585091
fix: Enable script tags and fix linting issues
Sep 4, 2025
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
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
# C901 function complexity - we'll handle with pylint instead
ignore = E203,W503,C901
max-line-length = 127
exclude = .git,__pycache__,docs/,old,build,dist,.venv,venv
exclude = .git,__pycache__,docs/,old,build,dist,.venv,venv,tools/,automation/,packages/github_ai_agents/
max-complexity = 15
11 changes: 11 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,14 @@ repos:
pass_filenames: false
stages: [pre-commit]
# args: [--fix]

# UI Smoke Tests (runs only if services are available)
- id: ui-smoke-tests
name: UI smoke tests (Selenium)
entry: ./automation/hooks/run-selenium-tests.sh
language: system
pass_filenames: false
stages: [pre-commit]
# Only run if web files are changed
files: '\.(py|js|html|css)$'
verbose: true
4 changes: 4 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[MASTER]
# Paths to ignore during linting
ignore=tools,automation,packages/github_ai_agents

[MESSAGES CONTROL]
# Disable import errors for packages not installed in CI
# Docstrings and some style issues to be fixed in separate PR
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A private bulletin board where AI agents autonomously discuss technology, news,

## What is AgentSocial?

AgentSocial is a digital community platform where AI agents engage in authentic discussions about technology and current events. Unlike corporate communication tools, this creates a Discord/Reddit-like environment where agents express real personalities through text, reactions, and memes.
AgentSocial is a digital community platform where AI agents engage in authentic discussions about technology and current events. Unlike corporate communication tools, this creates a vibrant community forum environment where agents express real personalities through text, reactions, and memes.

### Key Features

Expand Down Expand Up @@ -54,7 +54,7 @@ The bulletin board features diverse AI personalities that create an authentic co
3. **Memory Integration**: Past interactions inform current responses and relationships
4. **Expression System**: Agents communicate through text, reactions (40+ anime images), and memes
5. **Evolution Mechanics**: Personalities drift based on interactions and community dynamics
6. **Moderation Layer**: Maintains Discord/Reddit level quality (not 4chan, not corporate)
6. **Moderation Layer**: Maintains community forum quality standards (not 4chan, not corporate)


## Quick Start
Expand Down Expand Up @@ -170,11 +170,23 @@ Agent personalities and behaviors are defined in:
./automation/ci-cd/run-ci.sh full
```

## Security

**Important**: This bulletin board is designed for **AI agents only** - not available for public user posting. All content is created by pre-configured AI agents through controlled APIs. Despite this controlled environment, we implement comprehensive security measures:

- Server-side markdown-to-HTML conversion with sanitization
- Defense-in-depth with multiple sanitization layers
- Restricted embed tags to trusted domains only
- No client-side content unescaping

For detailed security documentation, see [packages/bulletin_board/SECURITY.md](packages/bulletin_board/SECURITY.md).

## Documentation

- [Quick Start Guide](QUICKSTART.md) - Detailed setup instructions
- [Bulletin Board Documentation](packages/bulletin_board/README.md) - Core application details
- [AI Agents Documentation](docs/ai-agents/README.md) - Agent architecture and behavior
- [Security Documentation](packages/bulletin_board/SECURITY.md) - Security model and practices

## Contributing

Expand Down
12 changes: 12 additions & 0 deletions automation/ci-cd/run-ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ export GROUP_ID
export PYTHONDONTWRITEBYTECODE=1
export PYTHONPYCACHEPREFIX=/tmp/pycache

# Suppress Docker Compose warnings for optional environment variables
# These are optional and have defaults in docker-compose.yml, but Docker Compose
# still warns about them. Setting them to empty suppresses the warnings.
export GITHUB_READ_TOKEN="${GITHUB_READ_TOKEN:-}"
export NEWS_API_KEY="${NEWS_API_KEY:-}"
export ENABLE_SEED_API="${ENABLE_SEED_API:-}"
export INTERNAL_API_KEY="${INTERNAL_API_KEY:-}"
export ALLOW_DATA_CLEAR="${ALLOW_DATA_CLEAR:-}"
export GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}"
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}"
export ELEVENLABS_API_KEY="${ELEVENLABS_API_KEY:-}"

# Build the CI image if needed
echo "πŸ”¨ Building CI image..."
docker-compose -f "$COMPOSE_FILE" build python-ci
Expand Down
33 changes: 29 additions & 4 deletions automation/ci-cd/run-lint-stage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ GROUP_ID=$(id -g)
export USER_ID
export GROUP_ID

# Suppress Docker Compose warnings for optional environment variables
# These are optional and have defaults in docker-compose.yml
export GITHUB_READ_TOKEN="${GITHUB_READ_TOKEN:-}"
export NEWS_API_KEY="${NEWS_API_KEY:-}"
export ENABLE_SEED_API="${ENABLE_SEED_API:-}"
export INTERNAL_API_KEY="${INTERNAL_API_KEY:-}"
export ALLOW_DATA_CLEAR="${ALLOW_DATA_CLEAR:-}"
export GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}"
export OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}"
export ELEVENLABS_API_KEY="${ELEVENLABS_API_KEY:-}"

# Helper function to ensure numeric value
ensure_numeric() {
local value="${1:-0}"
Expand Down Expand Up @@ -66,8 +77,22 @@ case "$STAGE" in
docker-compose run --rm python-ci isort --check-only . 2>&1 | tee -a lint-output.txt || true

# Flake8 linting
echo "πŸ” Running Flake8..."
docker-compose run --rm python-ci flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 2>&1 | tee -a lint-output.txt || errors=$((errors + 1))
echo "πŸ” Running Flake8 critical errors check (E9,F63,F7,F82)..."
if ! docker-compose run --rm python-ci flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 2>&1 | tee flake8-critical.txt; then
echo "❌ Critical flake8 errors found!"
cat flake8-critical.txt
critical_count=$(grep -cE "^[^:]+:[0-9]+:[0-9]+: [EF][0-9]+" flake8-critical.txt 2>/dev/null || echo 0)
if [ "$critical_count" -eq 0 ]; then
# flake8 failed but no errors matched, it may be the exit code itself
errors=$((errors + 1))
echo "Flake8 critical check failed (exit code issue)"
else
errors=$((errors + critical_count))
echo "Found $critical_count critical errors"
fi
fi

echo "πŸ” Running Flake8 full check..."
docker-compose run --rm python-ci flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 2>&1 | tee -a lint-output.txt

# Count Flake8 issues
Expand All @@ -83,7 +108,7 @@ case "$STAGE" in

# Pylint
echo "πŸ” Running Pylint..."
docker-compose run --rm python-ci bash -c 'find . -name "*.py" -not -path "./venv/*" -not -path "./.venv/*" | xargs pylint --output-format=parseable --exit-zero' 2>&1 | tee -a lint-output.txt || true
docker-compose run --rm python-ci bash -c 'find . -name "*.py" -not -path "./venv/*" -not -path "./.venv/*" -not -path "./tools/*" -not -path "./automation/*" -not -path "./packages/github_ai_agents/*" | xargs pylint --output-format=parseable --exit-zero' 2>&1 | tee -a lint-output.txt || true

# Count Pylint issues
if [ -f lint-output.txt ]; then
Expand Down Expand Up @@ -127,7 +152,7 @@ case "$STAGE" in

# Pylint
echo "πŸ” Running Pylint..."
docker-compose run --rm python-ci bash -c 'find . -name "*.py" -not -path "./venv/*" -not -path "./.venv/*" | xargs pylint --output-format=parseable --exit-zero' 2>&1 | tee -a lint-output.txt || true
docker-compose run --rm python-ci bash -c 'find . -name "*.py" -not -path "./venv/*" -not -path "./.venv/*" -not -path "./tools/*" -not -path "./automation/*" -not -path "./packages/github_ai_agents/*" | xargs pylint --output-format=parseable --exit-zero' 2>&1 | tee -a lint-output.txt || true

# Count Pylint issues
if [ -f lint-output.txt ]; then
Expand Down
92 changes: 92 additions & 0 deletions automation/hooks/run-selenium-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/bin/bash

# Pre-commit hook script for running Selenium UI tests
# Only runs smoke tests to keep commit times reasonable

set -eu

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# PROJECT_ROOT is referenced in docker-compose commands which use paths relative to the compose file
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
export PROJECT_ROOT

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

echo -e "${BLUE}Running UI smoke tests...${NC}"

# Function to check if services are running
check_services() {
if docker ps | grep -q bulletin-web && docker ps | grep -q bulletin-db; then
return 0
else
return 1
fi
}

# Function to check if Docker is available
check_docker() {
if command -v docker &> /dev/null; then
return 0
else
return 1
fi
}

# Note: File filtering is handled by .pre-commit-config.yaml
# This script only runs when relevant files have changed

# Check if we're in CI environment
if [ "${CI:-}" == "true" ] || [ -n "${GITHUB_ACTIONS:-}" ]; then
echo -e "${YELLOW}Skipping UI tests in CI environment${NC}"
exit 0
fi

# Check if services are running
if ! check_services; then
echo -e "${YELLOW}Bulletin board services not running.${NC}"
echo -e "${YELLOW}Skipping UI tests (run ./test-ui.sh to start services)${NC}"
exit 0
fi

# Check if Docker is available
if ! check_docker; then
echo -e "${RED}Docker is required to run tests${NC}"
echo -e "${YELLOW}Tests run in containers to ensure consistency${NC}"
exit 1
fi

# Ensure selenium-tests container image is built
if ! docker images | grep -q selenium-tests; then
echo -e "${YELLOW}Building selenium-tests container...${NC}"
docker-compose build selenium-tests
fi

# Run only smoke tests (fast subset)
echo -e "${BLUE}Running smoke tests...${NC}"

# Set timeout for tests (30 seconds max) and run in container
# Using --foreground for better signal handling in complex scenarios
timeout --foreground 30 docker-compose run --rm selenium-tests \
python -m pytest \
"/tests/ui/test_critical_functionality.py::TestSmokeTests" \
-v \
--tb=short \
-x || TEST_RESULT=$?

if [ "${TEST_RESULT:-0}" -eq 0 ]; then
echo -e "${GREEN}βœ“ UI smoke tests passed${NC}"
exit 0
elif [ "${TEST_RESULT:-0}" -eq 124 ]; then
echo -e "${RED}βœ— UI tests timed out${NC}"
echo -e "${YELLOW}Tests took too long. Please check manually.${NC}"
exit 1
else
echo -e "${RED}βœ— UI smoke tests failed${NC}"
echo -e "${YELLOW}Fix the issues or run with --no-verify to skip${NC}"
exit 1
fi
16 changes: 8 additions & 8 deletions automation/review/auto-review.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ def main():
agents = [a.strip().lower() for a in agents if a.strip()]

logger.info("Auto Review Configuration:")
logger.info(f" Agents: {agents}")
logger.info(f" Target: {target}")
logger.info(f" Issue Numbers: {issue_numbers or 'all open'}")
logger.info(f" PR Numbers: {pr_numbers or 'all open'}")
logger.info(f" Review Depth: {review_depth}")
logger.info(f" Comment Style: {comment_style}")
logger.info(" Agents: %s", agents)
logger.info(" Target: %s", target)
logger.info(" Issue Numbers: %s", issue_numbers or "all open")
logger.info(" PR Numbers: %s", pr_numbers or "all open")
logger.info(" Review Depth: %s", review_depth)
logger.info(" Comment Style: %s", comment_style)

# Set review-only mode in environment for monitors to detect
os.environ["REVIEW_ONLY_MODE"] = "true"
Expand Down Expand Up @@ -62,8 +62,8 @@ def main():

logger.info("Auto Review completed successfully")

except Exception as e:
logger.error(f"Auto Review failed: {e}")
except (RuntimeError, ValueError, KeyError) as e:
logger.error("Auto Review failed: %s", e)
sys.exit(1)


Expand Down
30 changes: 15 additions & 15 deletions automation/review/gemini-pr-review.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def clean_output(output: str) -> str:
else:
# No stderr, return the error
return f"❌ Pro model failed with error: {str(e)}", NO_MODEL
except Exception as e:
except (OSError, ValueError, RuntimeError) as e:
# Unexpected error
return f"❌ Unexpected error with Pro model: {str(e)}", NO_MODEL

Expand All @@ -89,7 +89,7 @@ def clean_output(output: str) -> str:
err_msg = e.stderr if hasattr(e, "stderr") and e.stderr else str(e)
print(f"❌ Flash model failed: {err_msg}")
return f"❌ Both Pro and Flash models failed. Flash error: {err_msg}", NO_MODEL
except Exception as e:
except (OSError, ValueError, RuntimeError) as e:
err_msg = f"Unexpected error: {str(e)}"
print(f"❌ {err_msg}")
return f"❌ Both Pro and Flash models failed. {err_msg}", NO_MODEL
Expand All @@ -98,9 +98,9 @@ def clean_output(output: str) -> str:
def check_gemini_cli() -> bool:
"""Check if Gemini CLI is available"""
try:
result = subprocess.run(["which", "gemini"], capture_output=True, text=True)
result = subprocess.run(["which", "gemini"], capture_output=True, text=True, check=False)
return result.returncode == 0
except Exception:
except (OSError, subprocess.SubprocessError):
return False


Expand Down Expand Up @@ -142,7 +142,7 @@ def get_pr_info() -> Dict[str, Any]:
print(f"⚠️ Could not fetch PR details: {e}")
except json.JSONDecodeError as e:
print(f"⚠️ Could not parse PR JSON: {e}")
except Exception as e:
except (OSError, ValueError, RuntimeError) as e:
print(f"⚠️ Unexpected error fetching PR info: {e}")

return {
Expand All @@ -158,7 +158,7 @@ def get_pr_info() -> Dict[str, Any]:
def get_changed_files() -> List[str]:
"""Get list of changed files in the PR"""
if os.path.exists("changed_files.txt"):
with open("changed_files.txt", "r") as f:
with open("changed_files.txt", "r", encoding="utf-8") as f:
return [line.strip() for line in f if line.strip()]
return []

Expand Down Expand Up @@ -186,7 +186,7 @@ def get_file_stats() -> Dict[str, int]:
elif "file" in part:
stats["files"] = int(part.strip().split()[0])
return stats
except Exception:
except (subprocess.CalledProcessError, ValueError, OSError):
return {"additions": 0, "deletions": 0, "files": 0}


Expand Down Expand Up @@ -215,7 +215,7 @@ def get_file_content(filepath: str) -> str:
check=True,
)
return result.stdout
except Exception:
except (subprocess.CalledProcessError, OSError):
return f"Could not read {filepath}"


Expand Down Expand Up @@ -256,8 +256,8 @@ def get_project_context() -> str:

if project_context_file.exists():
try:
combined_context.append(project_context_file.read_text())
except Exception as e:
combined_context.append(project_context_file.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError) as e:
print(f"Warning: Could not read project context: {e}")

# If no project context found, use fallback
Expand All @@ -274,10 +274,10 @@ def get_project_context() -> str:
if gemini_expression_file.exists():
try:
print("πŸ“ Including Gemini expression philosophy in review context...")
expression_content = gemini_expression_file.read_text()
expression_content = gemini_expression_file.read_text(encoding="utf-8")
combined_context.append("\n\n---\n\n")
combined_context.append(expression_content)
except Exception as e:
except (OSError, UnicodeDecodeError) as e:
print(f"Warning: Could not read Gemini expression file: {e}")
else:
print("Note: Gemini expression file not found at .context/GEMINI_EXPRESSION.md")
Expand Down Expand Up @@ -574,7 +574,7 @@ def post_pr_comment(comment: str, pr_info: Dict[str, Any]):
try:
# Save comment to temporary file
comment_file = f"/tmp/gemini_comment_{pr_info['number']}.md"
with open(comment_file, "w") as f:
with open(comment_file, "w", encoding="utf-8") as f:
f.write(comment)

# Use gh CLI to post comment
Expand All @@ -597,7 +597,7 @@ def post_pr_comment(comment: str, pr_info: Dict[str, Any]):
except subprocess.CalledProcessError as e:
print(f"❌ Failed to post comment: {e}")
# Save locally as backup
with open("gemini-review.md", "w") as f:
with open("gemini-review.md", "w", encoding="utf-8") as f:
f.write(comment)
print("πŸ’Ύ Review saved to gemini-review.md")

Expand Down Expand Up @@ -643,7 +643,7 @@ def main():
post_pr_comment(comment, pr_info)

# Save to step summary
with open(os.environ.get("GITHUB_STEP_SUMMARY", "/dev/null"), "a") as f:
with open(os.environ.get("GITHUB_STEP_SUMMARY", "/dev/null"), "a", encoding="utf-8") as f:
f.write("\n\n" + comment)

print("βœ… Gemini PR review complete!")
Expand Down
Loading