Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
9c7b558d20 fix(evennia): Bezalel settings — remove bad port tuples, fix crash (#534)
Some checks failed
Self-Healing Smoke / self-healing-smoke (pull_request) Failing after 26s
Smoke Test / smoke (pull_request) Failing after 28s
Agent PR Gate / gate (pull_request) Failing after 37s
Agent PR Gate / report (pull_request) Successful in 7s
- Add evennia/bezalel_world/server/conf/settings.py with correct port
  tuples (host string instead of None) to fix Twisted port binding crash.
- Update scripts/fix_evennia_settings.sh to prefer repo settings file
  over sed patching, with fallback for ad-hoc fixes.
- Add evennia/bezalel_world/server/README.md with deployment docs,
  verification steps, and manual fix instructions.

Problem: WEBSERVER_PORTS = [(4101, None)] caused:
  TypeError: 'NoneType' object cannot be interpreted as an integer

Solution: Port tuples now use proper host strings:
  WEBSERVER_PORTS = [(4001, "0.0.0.0")]
  TELNET_PORTS = [(4000, "0.0.0.0")]
  WEBSOCKET_PORTS = [(4002, "0.0.0.0")]

Closes #534
2026-04-22 03:24:35 -04:00
7 changed files with 185 additions and 311 deletions

View File

@@ -0,0 +1,87 @@
# Bezalel World Server Configuration
This directory contains the Evennia server configuration for Bezalel, the forge-and-testbed wizard house.
## Quick Start
To fix the Evennia settings on the Bezalel VPS (104.131.15.18):
```bash
# SSH to Bezalel and run the fix script
ssh root@104.131.15.18 'bash -s' < scripts/fix_evennia_settings.sh
```
Or manually:
```bash
cd /root/wizards/bezalel/evennia/bezalel_world/server/conf
# Copy the fixed settings
cp ~/timmy-home/evennia/bezalel_world/server/conf/settings.py ./settings.py
# Clean and reinitialize DB
cd /root/wizards/bezalel/evennia/bezalel_world
rm -f server/evennia.db3
/root/wizards/bezalel/evennia/venv/bin/evennia migrate
# Create superuser
/root/wizards/bezalel/evennia/venv/bin/python3 -c "
import sys, os
sys.setrecursionlimit(5000)
os.environ['DJANGO_SETTINGS_MODULE'] = 'server.conf.settings'
import django
django.setup()
from evennia.accounts.accounts import AccountDB
AccountDB.objects.create_superuser('Timmy', 'timmy@tower.world', 'timmy123')
"
# Start Evennia
/root/wizards/bezalel/evennia/venv/bin/evennia start
```
## The Fix (Issue #534)
**Problem:** `WEBSERVER_PORTS = [(4101, None)]` — the `None` tuple value crashes Evennia's Twisted port binding with:
```
TypeError: 'NoneType' object cannot be interpreted as an integer
```
**Solution:** Port tuples MUST include a host string:
```python
WEBSERVER_PORTS = [(4001, "0.0.0.0")]
TELNET_PORTS = [(4000, "0.0.0.0")]
WEBSOCKET_PORTS = [(4002, "0.0.0.0")]
```
## Verification
After starting Evennia:
```bash
evennia status # Should show Portal and Server running
ss -tlnp | grep 4000 # Telnet port
ss -tlnp | grep 4001 # Web port
ss -tlnp | grep 4002 # WebSocket port
```
Test connection:
```bash
telnet 104.131.15.18 4000
```
## File Structure
```
server/
├── conf/
│ ├── __init__.py
│ └── settings.py # Main settings file (FIXED for #534)
├── logs/ # Evennia logs
└── evennia.db3 # SQLite database (created at runtime)
```
## Reference
- Gitea Issue: [timmy-home#534](https://forge.alexanderwhitestone.com/Timmy_Foundation/timmy-home/issues/534)
- Evennia Docs: https://www.evennia.com/docs/latest/Setup/Settings-Default.html
- World Plan: docs/BEZALEL_EVENNIA_WORLD.md

View File

@@ -0,0 +1,87 @@
r"""
Evennia settings file for Bezalel World.
This is the sovereign Evennia configuration for the Bezalel forge-and-testbed wizard.
Reference: timmy-home#534
The available options are found in the default settings file found here:
https://www.evennia.com/docs/latest/Setup/Settings-Default.html
"""
# Use the defaults from Evennia unless explicitly overridden
from evennia.settings_default import *
######################################################################
# Evennia base server config
######################################################################
# Server name
SERVERNAME = "bezalel_world"
######################################################################
# Network ports - FIXED for #534
# Port tuples MUST include a host string, not None
######################################################################
# Web server port (HTTP)
WEBSERVER_PORTS = [(4001, "0.0.0.0")]
# Telnet server port
TELNET_PORTS = [(4000, "0.0.0.0")]
# WebSocket port for webclient
WEBSOCKET_PORTS = [(4002, "0.0.0.0")]
######################################################################
# Database configuration
# Using SQLite for sovereign local deployment
######################################################################
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(GAME_DIR, 'server', 'evennia.db3'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': ''
}
}
######################################################################
# Security settings
######################################################################
# Lockdown mode for VPS - only bind to localhost unless needed
# To allow external connections, use 0.0.0.0 in port tuples above
ALLOWED_HOSTS = ['*'] # VPS needs this for external access
######################################################################
# Game world defaults
######################################################################
# Start location for new characters
DEFAULT_HOME = "#2" # Limbo
# Start location for guests
GUEST_HOME = "#2"
######################################################################
# Telnet settings
######################################################################
TELNET_INTERFACES = ['0.0.0.0']
######################################################################
# Web server settings
######################################################################
WEBSERVER_INTERFACES = ['0.0.0.0']
######################################################################
# Settings given in secret_settings.py override those in this file.
######################################################################
try:
from server.conf.secret_settings import *
except ImportError:
print("secret_settings.py file not found or failed to import.")

View File

@@ -1,111 +0,0 @@
#!/bin/bash
# ============================================================================
# Agent Dispatch — One-shot prompt generator for fleet workers
# ============================================================================
# Refs: timmy-home #512
#
# Packages context, token, repo, issue, and Git/Gitea commands into a
# copy-pasteable prompt for any agent (Claude, Sonnet, Kimi, Grok, etc.).
#
# Usage:
# scripts/agent-dispatch.sh <agent> <repo> <issue#> [<org>]
#
# Supported agents:
# sonnet, claude, kimi, grok, gemini, ezra, bezalel, allegro, timmy
#
# Example:
# scripts/agent-dispatch.sh sonnet the-nexus 844 Timmy_Foundation
# ============================================================================
set -euo pipefail
AGENT="${1:-}"
REPO="${2:-}"
ISSUE="${3:-}"
ORG="${4:-Timmy_Foundation}"
TOKEN="${GITEA_TOKEN:-$(cat ~/.config/gitea/token 2>/dev/null || true)}"
FORGE="https://forge.alexanderwhitestone.com"
if [ -z "$AGENT" ] || [ -z "$REPO" ] || [ -z "$ISSUE" ]; then
echo "Usage: $0 <agent> <repo> <issue#> [<org>]"
echo ""
echo "Supported agents:"
echo " sonnet — Anthropic Claude Sonnet (cloud, high-reasoning)"
echo " claude — Anthropic Claude (general)"
echo " kimi — Moonshot Kimi K2.5 (cloud, long-context)"
echo " grok — xAI Grok (cloud, real-time)"
echo " gemini — Google Gemini (cloud, multimodal)"
echo " ezra — Local archivist house (read-before-write)"
echo " bezalel — Local artificer house (proof-required)"
echo " allegro — Local dispatch house (tempo-and-routing)"
echo " timmy — Local sovereign house (final review)"
exit 1
fi
# Validate agent
VALID_AGENTS="sonnet claude kimi grok gemini ezra bezalel allegro timmy"
if ! echo "$VALID_AGENTS" | grep -qw "$AGENT"; then
echo "ERROR: Unknown agent '$AGENT'"
echo "Valid agents: $VALID_AGENTS"
exit 1
fi
# Fetch issue details
if [ -n "$TOKEN" ]; then
ISSUE_JSON=$(curl -s -H "Authorization: token ${TOKEN}" \
"${FORGE}/api/v1/repos/${ORG}/${REPO}/issues/${ISSUE}" 2>/dev/null || true)
ISSUE_TITLE=$(echo "$ISSUE_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('title',''))" 2>/dev/null || true)
ISSUE_BODY=$(echo "$ISSUE_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('body',''))" 2>/dev/null || true)
else
echo "WARNING: No Gitea token found. Issue details will be blank."
ISSUE_TITLE=""
ISSUE_BODY=""
fi
cat <<EOF
================================================================================
DISPATCH PROMPT — ${AGENT} → ${ORG}/${REPO}#${ISSUE}
================================================================================
Agent: ${AGENT}
Repo: ${ORG}/${REPO}
Issue: #${ISSUE}
Title: ${ISSUE_TITLE}
--- ISSUE BODY ---
${ISSUE_BODY}
--- INSTRUCTIONS ---
1. Clone the repo:
git clone --depth 1 "https://\${TOKEN}@forge.alexanderwhitestone.com/${ORG}/${REPO}.git"
cd ${REPO}
2. Create branch:
git checkout -b ${AGENT}/${REPO}-${ISSUE}
3. Read the issue, implement the fix or feature.
4. Test your changes locally.
5. Commit and push:
git add -A
git commit -m "[${AGENT}] ${ISSUE_TITLE} (#${ISSUE})"
git push origin ${AGENT}/${REPO}-${ISSUE}
6. Open PR via Gitea API:
curl -X POST \\
-H "Authorization: token \${TOKEN}" \\
-H "Content-Type: application/json" \\
"${FORGE}/api/v1/repos/${ORG}/${REPO}/pulls" \\
-d '{"title":"[${AGENT}] ${ISSUE_TITLE}","head":"${AGENT}/${REPO}-${ISSUE}","base":"main","body":"Closes #${ISSUE}"}'
7. File new issues for anything discovered.
Token: \${GITEA_TOKEN} or ~/.config/gitea/token
Forge: ${FORGE}
Sovereignty and service always.
================================================================================
EOF

View File

@@ -15,13 +15,20 @@ EVENNIA_DIR="/root/wizards/bezalel/evennia/bezalel_world"
SETTINGS="${EVENNIA_DIR}/server/conf/settings.py"
VENV_PYTHON="/root/wizards/bezalel/evennia/venv/bin/python3"
VENV_EVENNIA="/root/wizards/bezalel/evennia/venv/bin/evennia"
TIMMY_HOME="${TIMMY_HOME:-/root/timmy-home}" # Or wherever the repo is cloned
echo "=== Fix Evennia Settings (Bezalel) ==="
# 1. Fix settings.py — remove bad port tuples
# 1. Fix settings.py — prefer repo version, fallback to sed patch
echo "Fixing settings.py..."
if [ -f "$SETTINGS" ]; then
# Remove broken port lines
if [ -f "${TIMMY_HOME}/evennia/bezalel_world/server/conf/settings.py" ]; then
# Use the fixed settings from the repo
mkdir -p "$(dirname "$SETTINGS")"
cp "${TIMMY_HOME}/evennia/bezalel_world/server/conf/settings.py" "$SETTINGS"
echo "Copied fixed settings from timmy-home repo."
elif [ -f "$SETTINGS" ]; then
# Fallback: patch in place
echo "Patching existing settings..."
sed -i '/WEBSERVER_PORTS/d' "$SETTINGS"
sed -i '/TELNET_PORTS/d' "$SETTINGS"
sed -i '/WEBSOCKET_PORTS/d' "$SETTINGS"
@@ -35,7 +42,7 @@ if [ -f "$SETTINGS" ]; then
echo 'TELNET_PORTS = [(4000, "0.0.0.0")]' >> "$SETTINGS"
echo 'WEBSOCKET_PORTS = [(4002, "0.0.0.0")]' >> "$SETTINGS"
echo "Settings fixed."
echo "Patched existing settings file."
else
echo "ERROR: Settings file not found at $SETTINGS"
exit 1

View File

@@ -1,195 +0,0 @@
#!/bin/bash
# ============================================================================
# Sonnet Workforce Smoke Test
# ============================================================================
# Refs: timmy-home #512
#
# Validates that the Sonnet workforce agent can perform the full
# clone → code → commit → push → PR workflow via Gitea HTTP.
#
# Usage:
# scripts/sonnet-smoke-test.sh [--cleanup]
#
# Exit codes:
# 0 — all checks passed
# 1 — one or more checks failed
# ============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
TOKEN="${GITEA_TOKEN:-$(cat ~/.config/gitea/token 2>/dev/null || true)}"
FORGE="https://forge.alexanderwhitestone.com"
ORG="Timmy_Foundation"
REPO="timmy-home"
TEST_BRANCH="smoke/sonnet-$(date +%s)"
# Colors
GREEN='\\033[0;32m'
RED='\\033[0;31m'
YELLOW='\\033[0;33m'
NC='\\033[0m'
PASS=0
FAIL=0
log_pass() { echo -e "${GREEN}${NC} $1"; PASS=$((PASS + 1)); }
log_fail() { echo -e "${RED}${NC} $1"; FAIL=$((FAIL + 1)); }
log_info() { echo -e "${YELLOW}${NC} $1"; }
# ── Prerequisites ──────────────────────────────────────────────────────────────────────────────────────
log_info "Checking prerequisites..."
if [ -z "$TOKEN" ]; then
log_fail "Gitea token not found (checked GITEA_TOKEN env and ~/.config/gitea/token)"
exit 1
fi
if ! command -v git &>/dev/null; then
log_fail "git not installed"
exit 1
fi
if ! command -v curl &>/dev/null; then
log_fail "curl not installed"
exit 1
fi
if ! command -v python3 &>/dev/null; then
log_fail "python3 not installed"
exit 1
fi
log_pass "Prerequisites OK"
# ── 1. Clone via Gitea HTTP ───────────────────────────────────────────────────────────────────────────────────────────────────────
log_info "Step 1: Clone repo via Gitea HTTP..."
TMPDIR=$(mktemp -d)
CLONE_URL="${FORGE}/${ORG}/${REPO}.git"
cd "$TMPDIR"
if git clone --depth 1 "https://${TOKEN}@${FORGE#https://}/${ORG}/${REPO}.git" smoke-clone 2>/dev/null; then
log_pass "Clone via Gitea HTTP"
else
log_fail "Clone via Gitea HTTP"
rm -rf "$TMPDIR"
exit 1
fi
# ── 2. Commit ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
log_info "Step 2: Create branch and commit..."
cd "$TMPDIR/smoke-clone"
git checkout -b "$TEST_BRANCH" 2>/dev/null || true
# Make a harmless change
printf "# Sonnet smoke test marker\\n# timestamp: %s\\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > SONNET_SMOKE_MARKER.md
git add SONNET_SMOKE_MARKER.md
if git -c user.email="sonnet@timmy.local" -c user.name="Sonnet Smoke Test" \
commit -m "test: sonnet smoke test marker" 2>/dev/null; then
log_pass "Commit created"
else
log_fail "Commit failed"
rm -rf "$TMPDIR"
exit 1
fi
# ── 3. Push ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
log_info "Step 3: Push branch..."
if git push origin "$TEST_BRANCH" 2>/dev/null; then
log_pass "Push to origin"
else
log_fail "Push to origin"
rm -rf "$TMPDIR"
exit 1
fi
# ── 4. Create PR ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
log_info "Step 4: Create PR via Gitea API..."
PR_RESPONSE=$(curl -s -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE}/api/v1/repos/${ORG}/${REPO}/pulls" \
-d "{
\"title\": \"test: sonnet smoke test ${TEST_BRANCH}\",
\"head\": \"${TEST_BRANCH}\",
\"base\": \"main\",
\"body\": \"Automated smoke test verifying Sonnet can clone, commit, push, and open a PR.\\n\\nRefs #512\"
}" 2>/dev/null)
PR_NUMBER=$(echo "$PR_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('number',''))")
if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "None" ]; then
log_pass "PR created (#${PR_NUMBER})"
PR_URL="${FORGE}/${ORG}/${REPO}/pulls/${PR_NUMBER}"
echo " URL: $PR_URL"
else
log_fail "PR creation failed"
echo " Response: $PR_RESPONSE"
rm -rf "$TMPDIR"
exit 1
fi
# ── 5. Verify PR exists ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
log_info "Step 5: Verify PR exists via API..."
PR_CHECK=$(curl -s -H "Authorization: token ${TOKEN}" \
"${FORGE}/api/v1/repos/${ORG}/${REPO}/pulls/${PR_NUMBER}" 2>/dev/null)
PR_STATE=$(echo "$PR_CHECK" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('state',''))")
if [ "$PR_STATE" = "open" ]; then
log_pass "PR verified open via API"
else
log_fail "PR state is '$PR_STATE', expected 'open'"
fi
# ── Cleanup (optional) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
if [ "${1:-}" = "--cleanup" ]; then
log_info "Cleaning up smoke test artifacts..."
curl -s -X PATCH -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE}/api/v1/repos/${ORG}/${REPO}/pulls/${PR_NUMBER}" \
-d '{"state":"closed"}' >/dev/null 2>&1 || true
git push origin --delete "$TEST_BRANCH" 2>/dev/null || true
log_pass "Cleanup complete"
fi
rm -rf "$TMPDIR"
# ── Summary ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
echo ""
echo "================================================================"
echo " Sonnet Smoke Test Summary"
echo "================================================================"
echo -e " Passed: ${GREEN}${PASS}${NC}"
echo -e " Failed: ${RED}${FAIL}${NC}"
echo ""
if [ "$FAIL" -gt 0 ]; then
echo -e "${RED}RESULT: FAILED${NC}"
exit 1
else
echo -e "${GREEN}RESULT: PASSED${NC}"
echo ""
echo "Sonnet workforce is verified end-to-end:"
echo " ✓ Clone via Gitea HTTP"
echo " ✓ Branch + commit"
echo " ✓ Push to origin"
echo " ✓ Open PR via API"
echo " ✓ Verify PR state"
exit 0
fi

View File

@@ -38,7 +38,6 @@ class House(Enum):
EZRA = "ezra" # Archivist, reader
BEZALEL = "bezalel" # Artificer, builder
ALLEGRO = "allegro" # Tempo-and-dispatch, connected
SONNET = "sonnet" # Anthropic Claude Sonnet (cloud, high-reasoning)
class Mode(Enum):