Merge pull request #60 from AIOSAI/fix/stale-dev-central-cleanup
fix(ai_mail,trigger): replace stale dev_central with devpulse
This commit is contained in:
@@ -26,7 +26,7 @@ from typing import Dict, Any, Optional, List
|
||||
if hasattr(signal, 'SIGPIPE'):
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
|
||||
# Dashboard integration (optional, requires dev_central package)
|
||||
# Dashboard integration (optional, provided by prax)
|
||||
_UPDATE_SECTION = None # type: ignore
|
||||
|
||||
# AIPass infrastructure imports
|
||||
|
||||
@@ -482,7 +482,7 @@ def spawn_agent(
|
||||
|
||||
def is_protected_branch(branch_email: str) -> bool:
|
||||
"""Check if a branch is protected from auto-dispatch."""
|
||||
return branch_email == "@dev_central"
|
||||
return branch_email == "@devpulse"
|
||||
|
||||
|
||||
def _read_session_type(pid_str: str) -> str:
|
||||
|
||||
@@ -309,7 +309,7 @@ def resolve_branch(branch_email: str) -> Optional[Tuple[Path, str]]:
|
||||
|
||||
def wake_branch(branch_email: str, custom_message: Optional[str] = None,
|
||||
fresh: bool = False, auto: bool = False,
|
||||
sender: str = "@dev_central") -> Tuple[DispatchStatus, bool]:
|
||||
sender: str = "@devpulse") -> Tuple[DispatchStatus, bool]:
|
||||
"""
|
||||
Spawn a Claude agent at the target branch with step-by-step status.
|
||||
|
||||
@@ -488,7 +488,7 @@ if __name__ == "__main__":
|
||||
print("Flags:")
|
||||
print(" --fresh Start fresh session (claude -p) instead of resuming (claude -c -p)")
|
||||
print(" --auto Respect autonomous_pause (used by daemon). Manual wake ignores it.")
|
||||
print(" --sender @branch Set return-to-sender for bounce emails (default: @dev_central)")
|
||||
print(" --sender @branch Set return-to-sender for bounce emails (default: @devpulse)")
|
||||
print()
|
||||
print("Output: Step-by-step status of the dispatch pipeline:")
|
||||
print(" ✅ resolve → @branch found at /path/to/branch")
|
||||
@@ -508,7 +508,7 @@ if __name__ == "__main__":
|
||||
# Parse flags
|
||||
use_fresh = "--fresh" in args
|
||||
use_auto = "--auto" in args
|
||||
use_sender = "@dev_central"
|
||||
use_sender = "@devpulse"
|
||||
|
||||
if "--sender" in args:
|
||||
idx = args.index("--sender")
|
||||
|
||||
@@ -455,7 +455,7 @@ def _send_desktop_notification(sender: str, recipient: str, subject: str, messag
|
||||
Gracefully handles cases where notify-send is not available.
|
||||
|
||||
Args:
|
||||
sender: Email sender address (e.g., @dev_central)
|
||||
sender: Email sender address (e.g., @devpulse)
|
||||
recipient: Email recipient address (e.g., @ai_mail)
|
||||
subject: Email subject line
|
||||
message: Email body (first ~100 chars shown in notification)
|
||||
|
||||
@@ -53,7 +53,7 @@ def build_error_report(to_branch: str, subject: str, error_msg: str) -> Dict[str
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"auto_execute": False,
|
||||
"priority": "normal",
|
||||
"reply_to": "@dev_central",
|
||||
"reply_to": "@devpulse",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ def send_reply(
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
# Get reply destination - use reply_to if set, otherwise use original sender
|
||||
# This allows emails to specify where replies should go (e.g., broadcasts from dev_central)
|
||||
# This allows emails to specify where replies should go (e.g., broadcasts from devpulse)
|
||||
reply_destination = original_email.get("reply_to") or original_email.get("from", "")
|
||||
if not reply_destination:
|
||||
return False, "Original email has no sender or reply_to address", None
|
||||
|
||||
@@ -82,7 +82,7 @@ def parse_send_args(args: List[str]) -> Dict[str, Any]:
|
||||
"subject": None,
|
||||
"message": None,
|
||||
"mode": "error",
|
||||
"error": "--reply-to requires a branch address (e.g., --reply-to @dev_central)",
|
||||
"error": "--reply-to requires a branch address (e.g., --reply-to @devpulse)",
|
||||
}
|
||||
|
||||
# Separate recipients from subject/message
|
||||
|
||||
@@ -66,20 +66,20 @@ Error message:
|
||||
{message}
|
||||
|
||||
---
|
||||
INVESTIGATION STEPS:
|
||||
Investigation steps:
|
||||
1. Check the log file for context around this error
|
||||
2. Identify root cause
|
||||
|
||||
DECISION TREE:
|
||||
- SIMPLE FIX (typo, missing import, config issue):
|
||||
-> Fix it yourself, then report what you did to @dev_central
|
||||
- COMPLEX/UNCLEAR (needs research, affects multiple files):
|
||||
-> Report findings only to @dev_central, recommend action, don't fix
|
||||
- CRITICAL (data loss risk, security, system stability):
|
||||
-> STOP immediately, escalate to @dev_central with full context
|
||||
Decision tree:
|
||||
- Simple fix (typo, missing import, config issue):
|
||||
-> Fix it yourself, then report what you did to @devpulse
|
||||
- Complex/unclear (needs research, affects multiple files):
|
||||
-> Report findings only to @devpulse, recommend action, don't fix
|
||||
- Critical (data loss risk, security, system stability):
|
||||
-> Stop immediately, escalate to @devpulse with full context
|
||||
|
||||
REPORT TO @dev_central:
|
||||
ai_mail send @dev_central "ERROR {error_hash} - [STATUS]" "Findings..."
|
||||
Report to @devpulse:
|
||||
ai_mail send @devpulse "ERROR {error_hash} - [STATUS]" "Findings..."
|
||||
|
||||
Include: Error ID, severity (low/medium/high/critical), what you found, action taken or recommended.
|
||||
"""
|
||||
@@ -162,7 +162,7 @@ def handle_error_detected(
|
||||
'timestamp': timestamp,
|
||||
'auto_execute': True,
|
||||
'priority': 'normal',
|
||||
'reply_to': '@dev_central'
|
||||
'reply_to': '@devpulse'
|
||||
}
|
||||
|
||||
# Deliver via inbox.json
|
||||
|
||||
@@ -160,7 +160,7 @@ def _orchestrate_wake(args: List[str]) -> bool:
|
||||
|
||||
# Parse --fresh and --sender flags
|
||||
use_fresh = "--fresh" in args
|
||||
use_sender = "@dev_central"
|
||||
use_sender = "@devpulse"
|
||||
filtered = []
|
||||
i = 0
|
||||
while i < len(args):
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AIPass Hello World — A fancy terminal demo.
|
||||
|
||||
Shows off the AIPass multi-agent ecosystem with a live animated dashboard.
|
||||
Built by @devpulse as a demo for friends.
|
||||
"""
|
||||
|
||||
import time
|
||||
import random
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# ── ANSI Colors ──────────────────────────────────────────────────────
|
||||
RESET = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
DIM = "\033[2m"
|
||||
CYAN = "\033[36m"
|
||||
GREEN = "\033[32m"
|
||||
YELLOW = "\033[33m"
|
||||
MAGENTA = "\033[35m"
|
||||
BLUE = "\033[34m"
|
||||
WHITE = "\033[97m"
|
||||
RED = "\033[31m"
|
||||
BG_DARK = "\033[48;5;235m"
|
||||
CLEAR = "\033[2J\033[H"
|
||||
|
||||
# ── Branch Data ──────────────────────────────────────────────────────
|
||||
BRANCHES = [
|
||||
("drone", "Command Router", "Routes commands to branches", CYAN),
|
||||
("seedgo", "Standards Engine", "21-standard compliance pack", GREEN),
|
||||
("prax", "Logging System", "Stack-aware dual routing", YELLOW),
|
||||
("cli", "CLI Framework", "Argument parsing & registry", BLUE),
|
||||
("flow", "Plan Manager", "FPLANs + DPLANs", MAGENTA),
|
||||
("ai_mail", "Communications", "Inter-branch email & dispatch", CYAN),
|
||||
("spawn", "Branch Lifecycle", "Create, update, delete", GREEN),
|
||||
("trigger", "Event System", "12 events, error registry", YELLOW),
|
||||
("api", "API Layer", "External interfaces", BLUE),
|
||||
("devpulse", "Orchestration Hub", "You are here", MAGENTA),
|
||||
("memory", "Memory Bank", "ChromaDB vector search", CYAN),
|
||||
("daemon", "Background Sched", "Cron, plugins, monitoring", GREEN),
|
||||
("backup", "Backup Utils", "Snapshot & restore", YELLOW),
|
||||
("commons", "Shared Library", "Cross-branch utilities", BLUE),
|
||||
("skills", "Skill Catalog", "Reusable AI capabilities", MAGENTA),
|
||||
]
|
||||
|
||||
ACTIVITIES = [
|
||||
"Processing dispatch...",
|
||||
"Running seedgo audit...",
|
||||
"Indexing memories...",
|
||||
"Routing command...",
|
||||
"Checking standards...",
|
||||
"Syncing mailbox...",
|
||||
"Updating passport...",
|
||||
"Logging event...",
|
||||
"Refreshing dashboard...",
|
||||
"Scanning for changes...",
|
||||
"Compiling plan status...",
|
||||
"Waking branch agent...",
|
||||
]
|
||||
|
||||
|
||||
def print_slow(text, delay=0.02):
|
||||
"""Print text character by character for dramatic effect."""
|
||||
for char in text:
|
||||
sys.stdout.write(char)
|
||||
sys.stdout.flush()
|
||||
time.sleep(delay)
|
||||
print()
|
||||
|
||||
|
||||
def draw_box(title, content_lines, width=60, color=CYAN):
|
||||
"""Draw a bordered box with title."""
|
||||
print(f" {color}┌{'─' * (width - 2)}┐{RESET}")
|
||||
print(f" {color}│{RESET} {BOLD}{WHITE}{title.center(width - 4)}{RESET} {color}│{RESET}")
|
||||
print(f" {color}├{'─' * (width - 2)}┤{RESET}")
|
||||
for line in content_lines:
|
||||
padded = f"{line:<{width - 4}}"[:width - 4]
|
||||
print(f" {color}│{RESET} {padded} {color}│{RESET}")
|
||||
print(f" {color}└{'─' * (width - 2)}┘{RESET}")
|
||||
|
||||
|
||||
def animate_startup():
|
||||
"""Animated boot sequence."""
|
||||
print(CLEAR)
|
||||
print()
|
||||
|
||||
# ASCII art title
|
||||
title = f"""
|
||||
{CYAN}{BOLD} █████╗ ██╗██████╗ █████╗ ███████╗███████╗
|
||||
██╔══██╗██║██╔══██╗██╔══██╗██╔════╝██╔════╝
|
||||
███████║██║██████╔╝███████║███████╗███████╗
|
||||
██╔══██║██║██╔═══╝ ██╔══██║╚════██║╚════██║
|
||||
██║ ██║██║██║ ██║ ██║███████║███████║
|
||||
╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝{RESET}
|
||||
"""
|
||||
print(title)
|
||||
time.sleep(0.5)
|
||||
|
||||
print_slow(f" {DIM}Multi-Agent Framework — Where AI Citizens Live{RESET}", 0.03)
|
||||
print()
|
||||
time.sleep(0.3)
|
||||
|
||||
# Boot sequence
|
||||
steps = [
|
||||
("Initializing drone router", 0.4),
|
||||
("Loading 15 branch identities", 0.3),
|
||||
("Connecting AI Mail network", 0.3),
|
||||
("Mounting .trinity/ memories", 0.4),
|
||||
("Starting Prax logging", 0.2),
|
||||
("Seedgo standards: 21 loaded", 0.3),
|
||||
("System ready", 0.1),
|
||||
]
|
||||
|
||||
for step, duration in steps:
|
||||
sys.stdout.write(f" {DIM}[{YELLOW}...{DIM}]{RESET} {step}")
|
||||
sys.stdout.flush()
|
||||
time.sleep(duration)
|
||||
sys.stdout.write(f"\r {DIM}[{GREEN} ✓ {DIM}]{RESET} {step}\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
time.sleep(0.5)
|
||||
print()
|
||||
|
||||
|
||||
def show_branch_grid():
|
||||
"""Display all branches in a nice grid."""
|
||||
print(f" {BOLD}{WHITE}┌─ REGISTERED BRANCHES {'─' * 36}┐{RESET}")
|
||||
print()
|
||||
|
||||
for name, role, _, color in BRANCHES:
|
||||
status = "●" if random.random() > 0.1 else "○"
|
||||
status_color = GREEN if status == "●" else RED
|
||||
|
||||
bar_len = random.randint(2, 12)
|
||||
bar = f"{'█' * bar_len}{'░' * (12 - bar_len)}"
|
||||
|
||||
is_devpulse = " ◀ YOU" if name == "devpulse" else ""
|
||||
|
||||
print(f" {status_color}{status}{RESET} {color}{BOLD}@{name:<10}{RESET} {DIM}{role:<18}{RESET} {CYAN}{bar}{RESET}{YELLOW}{is_devpulse}{RESET}")
|
||||
time.sleep(0.08)
|
||||
|
||||
print()
|
||||
print(f" {BOLD}{WHITE}└{'─' * 58}┘{RESET}")
|
||||
print()
|
||||
|
||||
|
||||
def show_live_activity():
|
||||
"""Simulate live system activity for a few seconds."""
|
||||
print(f" {BOLD}{WHITE}── LIVE ACTIVITY ──{RESET}")
|
||||
print()
|
||||
|
||||
for _ in range(8):
|
||||
branch = random.choice(BRANCHES)
|
||||
activity = random.choice(ACTIVITIES)
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
print(f" {DIM}{timestamp}{RESET} {branch[3]}@{branch[0]:<10}{RESET} {activity}")
|
||||
time.sleep(0.4)
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def show_dispatch_demo():
|
||||
"""Simulate a dispatch cycle."""
|
||||
print(f" {BOLD}{WHITE}── DISPATCH DEMO ──{RESET}")
|
||||
print()
|
||||
|
||||
# Simulate sending
|
||||
print_slow(f" {CYAN}📤 drone @ai_mail send @spawn \"Build greeting module\" --dispatch{RESET}", 0.02)
|
||||
time.sleep(0.5)
|
||||
print(f" {GREEN} ✓ Email dispatched to @spawn{RESET}")
|
||||
time.sleep(0.3)
|
||||
|
||||
print_slow(f" {CYAN}🔔 drone @ai_mail dispatch wake @spawn{RESET}", 0.02)
|
||||
time.sleep(0.5)
|
||||
print(f" {GREEN} ✓ @spawn is awake and processing...{RESET}")
|
||||
time.sleep(0.8)
|
||||
|
||||
# Simulate work
|
||||
work_steps = [
|
||||
"Reading inbox...",
|
||||
"Found dispatch: 'Build greeting module'",
|
||||
"Creating FPLAN-0099...",
|
||||
"Deploying build agent...",
|
||||
"Writing src/aipass/spawn/apps/handlers/greeting.py...",
|
||||
"Running seedgo audit... 100% compliant",
|
||||
"Emailing results back to @devpulse...",
|
||||
]
|
||||
|
||||
for step in work_steps:
|
||||
sys.stdout.write(f" {DIM} ↳ {step}{RESET}")
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.5)
|
||||
print()
|
||||
|
||||
print()
|
||||
print(f" {GREEN}{BOLD} 📨 Reply from @spawn: \"Greeting module built. 12 tests passing.\"{RESET}")
|
||||
print()
|
||||
|
||||
|
||||
def show_stats():
|
||||
"""Final stats box."""
|
||||
stats = [
|
||||
f"{WHITE}Branches: {CYAN}15 registered, 15 operational{RESET}",
|
||||
f"{WHITE}Standards: {GREEN}21 seedgo checks active{RESET}",
|
||||
f"{WHITE}Sessions: {YELLOW}32 completed (devpulse alone){RESET}",
|
||||
f"{WHITE}Architecture:{MAGENTA} Citizens + Agents + Dispatch{RESET}",
|
||||
f"{WHITE}Built with: {CYAN}Python, Rich, Claude Code{RESET}",
|
||||
f"{WHITE}Memory: {GREEN}.trinity/ — persistent identity{RESET}",
|
||||
]
|
||||
draw_box("SYSTEM STATS", stats, 56, CYAN)
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the full demo."""
|
||||
try:
|
||||
animate_startup()
|
||||
show_branch_grid()
|
||||
time.sleep(0.5)
|
||||
show_live_activity()
|
||||
time.sleep(0.5)
|
||||
show_dispatch_demo()
|
||||
time.sleep(0.5)
|
||||
show_stats()
|
||||
|
||||
print()
|
||||
print_slow(f" {BOLD}{CYAN}AIPass{RESET}{DIM} — Where AI citizens live, work, and remember.{RESET}", 0.03)
|
||||
print()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n\n {DIM}Demo interrupted. Goodbye!{RESET}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -292,19 +292,19 @@ INVESTIGATION STEPS:
|
||||
|
||||
DECISION TREE:
|
||||
- SIMPLE FIX (typo, missing import, config issue):
|
||||
-> Fix it yourself, then report what you did to @dev_central
|
||||
-> Fix it yourself, then report what you did to @devpulse
|
||||
- COMPLEX/UNCLEAR (needs research, affects multiple files):
|
||||
-> Report findings only to @dev_central, recommend action, don't fix
|
||||
-> Report findings only to @devpulse, recommend action, don't fix
|
||||
- CRITICAL (data loss risk, security, system stability):
|
||||
-> STOP immediately, escalate to @dev_central with full context
|
||||
-> STOP immediately, escalate to @devpulse with full context
|
||||
|
||||
SEED STANDARDS REMINDER:
|
||||
- Any code changes made during this investigation MUST follow Seed standards
|
||||
- After fixing, run: drone @seed checklist <modified_file>
|
||||
- Fixes scoring below 80% on Seed audit should NOT be shipped - clean up first
|
||||
|
||||
REPORT TO @dev_central:
|
||||
ai_mail send @dev_central "ERROR {error_hash} - [STATUS]" "Findings..."
|
||||
REPORT TO @devpulse:
|
||||
ai_mail send @devpulse "ERROR {error_hash} - [STATUS]" "Findings..."
|
||||
|
||||
Include: Error ID, severity (low/medium/high/critical), what you found, action taken or recommended.
|
||||
"""
|
||||
@@ -422,14 +422,14 @@ def handle_error_detected(
|
||||
# Convert branch name to email format (FLOW -> @flow)
|
||||
recipient = f"@{branch.lower()}"
|
||||
|
||||
# HARD RULE: DEV_CENTRAL is NEVER auto-triggered
|
||||
if recipient == '@dev_central':
|
||||
# devpulse is protected from auto-triggering
|
||||
if recipient == '@devpulse':
|
||||
return
|
||||
|
||||
# Validate target branch exists in registry before attempting delivery
|
||||
registered_emails = _get_registered_emails()
|
||||
if recipient not in registered_emails:
|
||||
# Unknown branch - log and skip (do NOT route to dev_central)
|
||||
# Unknown branch - log and skip (do NOT route to devpulse)
|
||||
try:
|
||||
suppressed_log = TRIGGER_ROOT / "logs" / "medic_suppressed.log"
|
||||
suppressed_log.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -15,7 +15,7 @@ backoff, and registry-based deduplication.
|
||||
|
||||
This handler remains for backward compatibility with code that fires error_logged
|
||||
events directly. It now includes full medic gating (medic_enabled, branch_muted,
|
||||
rate limiting, DEV_CENTRAL protection) to prevent bypass.
|
||||
rate limiting, devpulse protection) to prevent bypass.
|
||||
|
||||
Event data expected:
|
||||
- branch: Branch where error occurred (e.g., FLOW)
|
||||
@@ -198,14 +198,14 @@ INVESTIGATION STEPS:
|
||||
|
||||
DECISION TREE:
|
||||
- SIMPLE FIX (typo, missing import, config issue):
|
||||
-> Fix it yourself, then report what you did to @dev_central
|
||||
-> Fix it yourself, then report what you did to @devpulse
|
||||
- COMPLEX/UNCLEAR (needs research, affects multiple files):
|
||||
-> Report findings only to @dev_central, recommend action, don't fix
|
||||
-> Report findings only to @devpulse, recommend action, don't fix
|
||||
- CRITICAL (data loss risk, security, system stability):
|
||||
-> STOP immediately, escalate to @dev_central with full context
|
||||
-> STOP immediately, escalate to @devpulse with full context
|
||||
|
||||
REPORT TO @dev_central:
|
||||
ai_mail send @dev_central "ERROR {error_hash[:8]} - [STATUS]" "Findings..."
|
||||
REPORT TO @devpulse:
|
||||
ai_mail send @devpulse "ERROR {error_hash[:8]} - [STATUS]" "Findings..."
|
||||
"""
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ def handle_error_logged(
|
||||
Gating (matches error_detected.py):
|
||||
1. medic_enabled check (global toggle)
|
||||
2. branch_muted check (per-branch suppression)
|
||||
3. DEV_CENTRAL protection (HARD RULE: never auto-trigger)
|
||||
3. devpulse protection (never auto-trigger)
|
||||
4. Branch validation (unknown branches logged + skipped)
|
||||
5. Rate limiting (3 per 10 minutes per branch)
|
||||
|
||||
@@ -266,8 +266,8 @@ def handle_error_logged(
|
||||
# Gate 3: Convert branch name to email format
|
||||
recipient = f"@{branch.lower()}"
|
||||
|
||||
# HARD RULE: DEV_CENTRAL is NEVER auto-triggered
|
||||
if recipient == '@dev_central':
|
||||
# devpulse is protected from auto-triggering
|
||||
if recipient == '@devpulse':
|
||||
return
|
||||
|
||||
# Gate 4: Validate target branch exists in registry
|
||||
|
||||
Reference in New Issue
Block a user