Commit Graph

22 Commits

Author SHA1 Message Date
9e61af4a16 fix: concluded discussions suppress turns and send single auto-reply
Two bugs in concluded discussion channel handling:

1. before_model_resolve did not check rec.discussion.concluded, so it
   still initialized the speaker list and ran turn management. Fixed by
   returning NO_REPLY early for concluded discussions (same as report mode).

2. message_received fired for all agent VM contexts, causing multiple
   "This discussion is closed" auto-replies per incoming message. Fixed
   with process-level dedup keyed on channelId:messageId (same pattern
   as agent_end runId dedup). Also fixed message_id extraction to look
   in metadata.conversation_info.message_id first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 09:02:10 +01:00
27c968fa69 fix: prevent idle reminder from re-waking dormant discussion channel
The moderator bot's own idle reminder message triggered message_received,
which saw senderId != currentSpeaker and called wakeFromDormant, immediately
undoing the dormant state just entered.

Fix: derive the moderator bot's Discord user ID from the token and skip
wake-from-dormant when the sender is the moderator bot itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 08:17:55 +01:00
74e6d61d4d fix: correct channelId extraction and dormant check in hooks
message-received.ts:
- message_received ctx has channelId/accountId/conversationId (not
  sessionKey). Add extraction from ctx.channelId and metadata.to
  ("channel:ID" format) before the conversation_info fallback.

agent-end.ts:
- When tail-match is interrupted, only call wakeFromDormant() if the
  channel is actually dormant. For non-dormant interrupts (e.g. the
  moderator bot's own trigger messages firing message_received on
  other agents), fall through to normal advanceSpeaker() so the turn
  cycle continues correctly instead of re-triggering the same speaker.
- Import isDormant from turn-manager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 06:24:05 +01:00
b5196e972c feat: rewrite plugin as v2 with globalThis-based turn management
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>
2026-04-08 22:41:25 +01:00
zhi
895cfe3bab fix: align discussion workspace and tool schemas 2026-04-02 11:52:20 +00:00
zhi
7bccb660df fix: wake origin workflow after discussion callback 2026-04-02 08:20:23 +00:00
zhi
7670d41785 Complete A6 and A7 tasks: Implement closed discussion channel protections and update tasklist
- Complete tasks A6.1, A6.2, A6.3: Turn manager discussion mode handling
- Complete tasks A7.1, A7.2, A7.3, A7.5: Hook-level discussion channel protections
- Add closed discussion channel check to message-sent hook to prevent handoffs
- Update TASKLIST.md to mark completed tasks with [x]
2026-04-02 05:04:47 +00:00
zhi
d44204fabf feat: wire channel mode runtime config and docs 2026-04-02 04:48:20 +00:00
zhi
bfbe40b3c6 feat: implement multi-message mode and shuffle mode features
- Add multi-message mode with start/end/prompt markers
- Implement turn order shuffling with /turn-shuffling command
- Add channel mode state management
- Update hooks to handle multi-message mode behavior
- Update plugin config with new markers
- Update TASKLIST.md with completed tasks
2026-04-02 04:36:36 +00:00
zhi
684f8f9ee7 Refine discussion moderator messaging flow 2026-04-02 04:18:45 +00:00
zhi
d9bb5c2e21 Fix discussion channel closure handling 2026-04-02 03:49:03 +00:00
zhi
62cd2f20cf feat(csm): bootstrap discussion callback flow 2026-04-02 02:35:08 +00:00
0a99abc7e3 Use pluginConfig directly for Dirigent runtime config 2026-04-01 20:01:57 +00:00
nav
4905f37c1a fix: limit no-reply keywords and log 2026-03-10 13:53:32 +00:00
nav
e6b20e9d52 feat: treat NO/empty as gateway no-reply 2026-03-10 13:42:50 +00:00
zhi
08f42bfd92 fix: skip rules-based no-reply override when turn manager allows agent
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.
2026-03-09 21:16:53 +00:00
zhi
1f846fa7aa fix: stop treating empty content as NO_REPLY, detect tool calls
Problems fixed:
1. before_message_write treated empty content (isEmpty) as NO_REPLY.
   Tool-call-only assistant messages (thinking + toolCall, no text)
   had empty extracted text, causing false NO_REPLY detection.
   In single-agent channels this immediately set turn to dormant,
   blocking all subsequent responses for the entire model run.

2. Added explicit toolCall/tool_call/tool_use detection in
   before_message_write — skip turn processing for intermediate
   tool-call messages entirely.

3. no-reply-api/server.mjs: default model name changed from
   'dirigent-no-reply-v1' to 'no-reply' to match the configured
   model id in openclaw.json, fixing model list discovery.

Changes:
- plugin/hooks/before-message-write.ts: toolCall detection + remove isEmpty
- plugin/hooks/message-sent.ts: remove isEmpty from wasNoReply
- no-reply-api/server.mjs: fix default model name
- dist/dirigent/index.ts: same fixes applied to monolithic build
- dist/no-reply-api/server.mjs: same model name fix
2026-03-09 12:00:36 +00:00
12b0f4c6cc feat(turn-init): persist channel bot membership cache to local file 2026-03-08 06:13:38 +00:00
307235e207 feat(turn-init): use internal Discord channel member resolver for bootstrap (no public tool) 2026-03-08 06:08:51 +00:00
b63c1dfe94 refactor(plugin): extract message_received hook and slim index imports 2026-03-07 22:24:48 +00:00
5c4340d5a9 refactor(plugin): extract before_prompt_build and before_message_write hooks 2026-03-07 22:21:16 +00:00
c15ea0d471 refactor(plugin): extract before_model_resolve and message_sent hooks 2026-03-07 22:18:09 +00:00