Skip to content

feat(discord): add send_discord_message proactive-post tool#1026

Merged
Aaronontheweb merged 5 commits into
netclaw-dev:devfrom
Aaronontheweb:claude-wt-discord-direct-dm-send
May 18, 2026
Merged

feat(discord): add send_discord_message proactive-post tool#1026
Aaronontheweb merged 5 commits into
netclaw-dev:devfrom
Aaronontheweb:claude-wt-discord-direct-dm-send

Conversation

@Aaronontheweb
Copy link
Copy Markdown
Collaborator

@Aaronontheweb Aaronontheweb commented May 16, 2026

Summary

Adds a send_discord_message proactive-post tool so the agent can start a new Discord conversation for DeliveryKind.Channel reminder delivery and cross-channel outreach, with Discord thread/session wiring at parity with send_slack_message. Closes #953.

  • New send_discord_message builtin tool: posts a message to an allowed Discord channel and creates a thread off it, so the posted message becomes the thread root.
  • New StartProactiveThread / ProactiveThreadAck protocol routed gateway -> conversation -> session-binding actor, so user replies in the created thread route back to a live session.
  • Conforms to the thread-history-backfill contract: the bot's posted message is recovered as adopted context on the first authorized reply via the existing deferred-hydration path.
  • Hardens Discord reminder delivery to explicit channel targets only: channel:<id> or <#id>. DM, user, and bare-snowflake targets are rejected up front.
  • Reports partial success when the root message posts but Discord thread creation fails, so operators do not retry into duplicate orphan posts.
  • Marks proactively created threads as already threaded so later SessionTitleOutput renames still apply.
  • Channel-only scope remains intentional. DM (user_id) proactive posting is still deferred and tracked in Add DM (user_id) proactive-post support to send_discord_message #1025.
  • The known unrelated Discord approval-button regression remains tracked separately in Discord approval buttons fail with "This interaction failed" — defer misses the 3s gateway ack window (regression) #1030.
  • Ships the add-discord-proactive-post OpenSpec change and updates the netclaw-operations system skill.

Test plan

  • dotnet test src/Netclaw.Actors.Tests/Netclaw.Actors.Tests.csproj --filter "FullyQualifiedName~SendDiscordMessageToolTests|FullyQualifiedName~DiscordReminderTargetResolverTests|FullyQualifiedName~SetReminderToolTests|FullyQualifiedName~DiscordSessionBindingContractTests"
  • Focused Discord channel, reminder, and session-binding tests pass locally
  • dotnet slopwatch analyze
  • pwsh "./scripts/Add-FileHeaders.ps1" -Verify
  • ./evals/run-evals.sh not run locally; requires a model endpoint
  • Manual live Discord bot validation: post to channel -> thread created -> allowed user replies in-thread -> reply routes to a live session with adopted context

Adds a channel-only send_discord_message tool so the agent can start a
new Discord conversation — for DeliveryKind.Channel reminder delivery and
cross-channel outreach — at parity with send_slack_message. The tool
posts a message, creates a thread off it, and wires the thread into the
actor hierarchy via StartProactiveThread so user replies route back to a
live session. The bot's posted message is recovered as adopted context
on the first authorized reply through the existing deferred-hydration
path, satisfying the thread-history-backfill conformance contract.

DM (user_id) targets are deliberately deferred: Discord DMs are flat
with no thread root, so the thread-history-backfill amnesia fix cannot
apply. Recorded in the add-discord-proactive-post OpenSpec change and
tracked in netclaw-dev#1025.

Closes netclaw-dev#953
PostNewThreadAsync now forwards the CancellationToken to the underlying
Discord.Net SendMessageAsync/CreateThreadAsync calls. The duplicated
FindExistingThreadAsync helper is extracted into DiscordThreadHelpers
and shared by the outbound and reply clients. SendDiscordMessageTool
resolves the default channel id once instead of twice.
Discord reminders should not guess whether a snowflake is a user or a channel, and posted root messages should not look retry-safe when thread creation fails. Mark proactive threads as already created so later title outputs still rename the thread.
@Aaronontheweb Aaronontheweb added .NET Pull requests that update .NET code channels Discord, Slack, and other channels. reliability Retries, resilience, graceful degradation reminders Reminder scheduling, execution, and history labels May 18, 2026
@Aaronontheweb Aaronontheweb marked this pull request as ready for review May 18, 2026 00:31
Copy link
Copy Markdown
Collaborator Author

@Aaronontheweb Aaronontheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

// Channel-specific LLM tool: registered as an INetclawTool singleton.
// The gateway actor ref is resolved lazily via DiscordChannel since it
// is not available until StartAsync completes.
services.AddSingleton<SendDiscordMessageTool>(sp =>
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic tool registration - only register the discord tool when a discord channel is active.

_rootMessageId = null;

_log.Info("Initializing proactive thread pipeline for session {0}", message.SessionId.Value);
await EnsureInitializedAsync();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensures that the full messaging pipeline gets initialized

@Aaronontheweb Aaronontheweb merged commit 9a2a18c into netclaw-dev:dev May 18, 2026
12 checks passed
@Aaronontheweb Aaronontheweb deleted the claude-wt-discord-direct-dm-send branch May 18, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channels Discord, Slack, and other channels. .NET Pull requests that update .NET code reliability Retries, resilience, graceful degradation reminders Reminder scheduling, execution, and history

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add send_discord_message proactive-post tool

1 participant