feat(gateway): pluggable NEXUS gateway with notification bridge and Discord rewrite#83
Open
Christiantyemele wants to merge 9 commits into
Open
feat(gateway): pluggable NEXUS gateway with notification bridge and Discord rewrite#83Christiantyemele wants to merge 9 commits into
Christiantyemele wants to merge 9 commits into
Conversation
added 7 commits
May 13, 2026 06:38
- Add DiscordClient with embed-based rich messages via Discord Bot API - Add WhatsAppClient using WhatsApp Cloud API for business messaging - Update ChatConfig with multi-channel fields and active_channels() method - Refactor HumanChannel to aggregate multiple ChatClient implementations - Update chat_loop to poll all configured channels every 2 seconds - Add ChannelType enum for type-safe channel identification - Update .env.example with Discord and WhatsApp environment variables - Update NEXUS persona and skill documentation for multi-channel support
…ion/prefix filtering and command parsing fixes - Add Discord Gateway WebSocket client (discord_gateway.rs) with heartbeat, automatic reconnection, and opcode 1 (Heartbeat Request) handling. - Filter messages: respond only when @mentioned or addressed by bot username. - Strip mentions and username prefix before passing content to interpreter. - Add background command processor in agentflow.rs so human commands are processed even while the flow is at forge_pair or vessel nodes. - Fix chat_loop shutdown: use tokio::sync::watch to keep Gateway alive instead of dropping the mpsc sender immediately. - Fix command prefix matching to accept common typos (assigne, assigning). - Add extract_two_workers() and read_worker() helpers to parse space-separated worker IDs like "forge 1" as "forge-1". - Add helpful reply message when a command cannot be interpreted. - Improve LLM interpreter prompt with strict flat-string rules and examples. - Fix ack_message to compare deterministic fields (user_id, channel_id, text) instead of timestamp which drifts through JSON round-trips.
…ifecycles Two root causes prevented forge-2 from spawning alongside forge-1: 1. Git worktree creation race condition: BatchNode::run_batch() runs exec_one for all workers concurrently via join_all. Both pairs called WorktreeManager::create_worktree() simultaneously on the same .git directory, causing index.lock contention that silently killed forge-2. 2. Async runtime starvation: ForgeSentinelPair::run() is a long-lived event loop. When two pairs ran via join_all on the same tokio runtime, the first pair's loop starved the second. Fixes: - Add process-wide std::sync::Mutex (GIT_WORKTREE_MUTEX) around synchronous git operations in create_worktree, released before async configure_git_identity to avoid Send issues across .await points. - Add retry_git_operation() with exponential backoff for transient lock contention from external git processes. - Add is_git_lock_error() to detect git lock patterns in stderr. - Split create_worktree into sync (create_worktree_sync) and async phases. - Run ForgePairNode::exec_one inside tokio::task::spawn_blocking with a dedicated current_thread runtime per pair. - Move 'already exists' fallback inside retry closure to eliminate dead code. - Add notification bridge events for forge lifecycle state changes. - Add 7 unit tests for lock detection and retry logic. Verified: both forge-1 and forge-2 now create worktrees and make progress when running concurrently.
…R events and ticket assignments - Add NotificationBridge that polls SharedStore events and routes them as OutboundMessages through the gateway to Discord/Slack/WhatsApp - Bridge maps forge work_completed → PrOpened, vessel events → CiFailed/ MergeBlocked/ConflictsDetected, nexus worker_assigned → AgentAssigned - Rewrite Discord plugin with twilight-gateway for reliable WebSocket handling - Add comprehensive OutboundMessageType variants for all stakeholder events - Discord plugin renders rich embeds for PR opened, CI failures, merge blocked - Fix clippy: remove unused GatewayConfig import, allow dead_code on ReActDecision::Act - Clean up redundant comments across nexus-gateway, agent-forge, agent-vessel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the monolithic
nexus-chatcrate with a pluggablenexus-gatewaycrate that decouples domain logic (forge, vessel, pair-harness) from notification delivery, and introduces a notification bridge that routes all stakeholder events to Discord (and other channels).Core Changes
nexus-gatewaycrate withChannelPlugintrait,Gatewaystruct, and channel-agnosticInboundMessage/OutboundMessagetypes — adding a new channel requires only implementing the trait, no gateway modificationsOutboundMessages, and broadcasts them through the gateway to all registered channel plugins. Decouples forge/vessel/nexus from notification delivery entirelytwilight-gateway, which handles heartbeat, reconnection, resume, and session invalidation automatically. Rich embeds for PR opened, CI failures, merge blocked, conflicts detected, worker suspended, human intervention, etc.OutboundMessageTypevariants —PrOpened,PrMerged,CiFailed,CiTimeout,CiMissing,MergeBlocked,ConflictsDetected,TicketFailed,TicketExhausted,FuelExhausted,WorkerSuspended,HumanInterventionKnowledgeStoretrait with stub implementation for future vector DB integrationAgentRunnerreuse across iterations to avoid repeated MCP server spawningEvent → Notification Mapping
work_completed(pr_opened)PrOpenedwork_completed(other)AgentCompletedwork_suspendedWorkerSuspendedhuman_interventionHumanInterventionwork_failed/ticket_exhausted/fuel_exhaustedTicketFailed/TicketExhausted/FuelExhaustedticket_mergedPrMergedci_failed/ci_timeout/ci_missingCiFailed/CiTimeout/CiMissingmerge_blocked/conflicts_detectedMergeBlocked/ConflictsDetectedwork_assigned/worker_assignedWorkflowStarted/AgentAssignedSupporting Changes
store.emit("forge", "work_completed", ...)on PR outcome,store.emit("forge", "work_suspended", ...)on blocked work, etc.VesselNotifieremits structured events for CI failures, merge outcomes, conflict detectionwork_assignedandworker_assignedevents via bridge; background gateway processor with heartbeatwork_completednotification events on pair lifecycle completionGatewayConfig::from_env()NEXUS_GATEWAY_*env vars for Slack, Discord, WhatsApp configurationTesting
cargo test)cargo clippy --all-targets -- -D warningscleancargo fmt -- --checkclean