Complete rewrite of the Dirigent plugin turn management system to work
correctly with OpenClaw's VM-context-per-session architecture:
- All turn state stored on globalThis (persists across VM context hot-reloads)
- Hooks registered unconditionally on every api instance; event-level dedup
(runId Set for agent_end, WeakSet for before_model_resolve) prevents
double-processing
- Gateway lifecycle events (gateway_start/stop) guarded once via globalThis flag
- Shared initializingChannels lock prevents concurrent channel init across VM
contexts in message_received and before_model_resolve
- New ChannelStore and IdentityRegistry replace old policy/session-state modules
- Added agent_end hook with tail-match polling for Discord delivery confirmation
- Added web control page, padded-cell auto-scan, discussion tool support
- Removed obsolete v1 modules: channel-resolver, channel-modes, discussion-service,
session-state, turn-bootstrap, policy/store, rules, decision-input
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the turn manager determines it's an agent's turn (checkTurn.allowed),
the rules engine's evaluateDecision() could still override the model to
no-reply with reason 'rule_match_no_end_symbol'. This happened because:
1. The sender of the triggering message (another agent) was not in the
humanList, so the rules fell through to the end-symbol check.
2. getLastChar() operates on the full prompt (including system content
like Runtime info), so it never found the end symbol even when the
actual message ended with one.
Fix: return early from before_model_resolve after the turn check passes,
skipping the rules-based no-reply override entirely. The turn manager is
the authoritative source for multi-agent turn coordination.
Tested: 3-agent counting chain ran successfully (3→11) with correct
NO_REPLY handling when count exceeded threshold.