fix(ai_mail,trigger): replace stale dev_central references with devpulse

Clean up 33 stale "dev_central" references across ai_mail (16 hits)
and trigger (17 hits) branches. All @dev_central → @devpulse in code,
comments, prompt templates, and config values. ALL CAPS prompt
directives rewritten to normal case. Category B rewrite in ai_mail.py
(dashboard comment corrected to reference prax). Also adds demo/
hello_aipass.py — animated terminal dashboard for demos.

Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
AIOSAI
2026-03-17 00:12:53 -07:00
co-authored by @devpulse
parent 79b09e8c1d
commit cef8ad029e
12 changed files with 274 additions and 38 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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):
+236
View File
@@ -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