feat: config-driven architecture, install wizard, live runtime switching, usage tracking, auto-failover

Major changes:
- Config-driven adapters: all channels (Slack, Discord, Telegram, WebChat, Webhooks) controlled via config.json with enabled flags and token auto-detection, no CLI flags required
- Runtime engine field: runtime.engine selects opencode/claude from config
- Interactive install script: 8-phase setup wizard with AI runtime detection/installation, token setup, identity file personalization (personality presets), aetheel CLI command, background service (launchd/systemd)
- Live runtime switching: /engine, /model, /provider commands hot-swap the AI runtime from chat without restart, changes persisted to config.json
- Usage tracking: per-request cost extraction from Claude Code JSON output, cumulative stats via /usage command
- Auto-failover: rate limit detection on both runtimes, automatic switch to other engine on quota errors with user notification
- Chat commands work without / prefix (Slack intercepts / in channels), commands: engine, model, provider, config, usage, reload, cron, subagents, status, help
- /config set for editing config.json from chat with dotted key notation
- Security audit saved to docs/security-audit.md
- Full command reference in docs/commands.md
- Future changes doc with NanoClaw agent teams analysis
- Logo added to README and WebChat UI
- README fully rewritten with all features documented
This commit is contained in:
2026-02-18 01:07:12 -05:00
parent 41b2f9a593
commit 6d73f74e0b
41 changed files with 11363 additions and 437 deletions

View File

@@ -59,6 +59,39 @@ SendFunction = Callable[[str, str, str | None, str], None]
# send_fn(channel_id, text, thread_id, channel_type)
# ---------------------------------------------------------------------------
# Subagent Bus (pub/sub for inter-subagent communication)
# ---------------------------------------------------------------------------
class SubagentBus:
"""Simple pub/sub message bus for inter-subagent communication."""
def __init__(self):
self._channels: dict[str, list[Callable]] = {}
self._lock = threading.Lock()
def subscribe(self, channel: str, callback: Callable[[str, str], None]) -> None:
"""Register a callback for messages on a channel."""
with self._lock:
self._channels.setdefault(channel, []).append(callback)
def publish(self, channel: str, message: str, sender: str) -> None:
"""Publish a message to all subscribers of a channel."""
with self._lock:
callbacks = list(self._channels.get(channel, []))
for cb in callbacks:
try:
cb(message, sender)
except Exception as e:
logger.error(f"SubagentBus callback error: {e}")
def unsubscribe_all(self, channel: str) -> None:
"""Remove all subscribers from a channel."""
with self._lock:
self._channels.pop(channel, None)
# ---------------------------------------------------------------------------
# Subagent Manager
# ---------------------------------------------------------------------------
@@ -84,6 +117,12 @@ class SubagentManager:
self._max_concurrent = max_concurrent
self._tasks: dict[str, SubagentTask] = {}
self._lock = threading.Lock()
self._bus = SubagentBus()
@property
def bus(self) -> SubagentBus:
"""The pub/sub message bus for inter-subagent communication."""
return self._bus
def spawn(
self,