Skip to content

feat: channel adapters, 48 native tools, presets, commands, and integrations#38

Merged
Telli merged 10 commits intomainfrom
feat/channel-expansion-tools-features
Apr 1, 2026
Merged

feat: channel adapters, 48 native tools, presets, commands, and integrations#38
Telli merged 10 commits intomainfrom
feat/channel-expansion-tools-features

Conversation

@Telli
Copy link
Copy Markdown
Contributor

@Telli Telli commented Apr 1, 2026

Summary

  • 3 new channel adapters: Slack (Events API + slash commands), Discord (Gateway WebSocket + interaction webhook), Signal (signald/signal-cli bridge)
  • 14 new native tools (34 → 48): edit_file, apply_patch, message, x_search, memory_get, sessions_history, sessions_send, sessions_spawn, session_status, sessions_yield, agents_list, cron, gateway, profile_write
  • 4 new built-in tool presets: full, coding, messaging, minimal (9 total)
  • 7 built-in tool groups: group:runtime, group:fs, group:sessions, group:memory, group:web, group:automation, group:messaging
  • 3 new chat commands: /think off|low|medium|high, /compact, /verbose on|off
  • Multi-agent routing: per-channel/sender routing with workspace isolation, model override, system prompt, and tool preset config
  • Tailscale Serve/Funnel: zero-config remote gateway access
  • Gmail Pub/Sub: webhook bridge for email event triggers
  • mDNS/Bonjour: local network service discovery

Test plan

  • Full solution builds with zero errors/warnings
  • All 624 tests pass
  • Verify Slack adapter with a test workspace (Events API + slash commands)
  • Verify Discord adapter with a test bot (Gateway WebSocket + interactions)
  • Verify Signal adapter with signald daemon
  • Test new tool presets resolve correctly (/preset coding, etc.)
  • Test /think, /compact, /verbose commands in a chat session
  • Test multi-agent routing with channel-specific route config
  • Test Gmail Pub/Sub webhook endpoint with sample payload

🤖 Generated with Claude Code

…ntegrations

Expand the platform with three new channel adapters (Slack, Discord, Signal),
14 new native tools (48 total), built-in tool presets and groups, new chat
commands (/think, /compact, /verbose), multi-agent routing, and integration
services for Tailscale, Gmail Pub/Sub, and mDNS discovery.

Channels:
- Slack: Events API webhooks, slash commands, HMAC-SHA256 signature validation,
  thread-to-session mapping, Markdown-to-mrkdwn conversion
- Discord: Gateway WebSocket (heartbeat, identify, resume, reconnect), REST API,
  slash command registration, Ed25519 interaction webhook verification
- Signal: signald Unix socket and signal-cli subprocess drivers, DM-only with
  optional no-content logging privacy mode

Tools (34 → 48):
- edit_file, apply_patch, message, x_search, memory_get, sessions_history,
  sessions_send, sessions_spawn, session_status, sessions_yield, agents_list,
  cron, gateway, profile_write

Presets & Groups:
- 4 new built-in presets: full, coding, messaging, minimal (9 total)
- 7 built-in tool groups: group:runtime, group:fs, group:sessions, group:memory,
  group:web, group:automation, group:messaging

Commands:
- /think off|low|medium|high — set reasoning effort level
- /compact — trigger history compaction
- /verbose on|off — toggle verbose output

Integrations:
- Multi-agent routing with per-route workspace, model, prompt, and preset config
- Tailscale Serve/Funnel for zero-config remote access
- Gmail Pub/Sub webhook bridge for email event triggers
- mDNS/Bonjour service discovery for local network advertisement

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 1, 2026 04:19
@augmentcode
Copy link
Copy Markdown

augmentcode bot commented Apr 1, 2026

🤖 Augment PR Summary

Summary: This PR significantly expands OpenClaw.NET’s runtime surfaces (channels, tools, presets, and integrations) to support richer operator workflows and multi-channel messaging.

Changes:

  • Added new channel adapters for Slack, Discord (Gateway WS + interactions webhook), and Signal (signald/signal-cli bridge).
  • Introduced multiple new native tools (e.g., edit_file, apply_patch, message, x_search, memory_get, session management tools, and gateway/cron/profile tooling).
  • Added built-in tool presets (full, coding, messaging, minimal) and built-in tool groups (group:*) with resolver support.
  • Extended chat commands with /think, /compact, and /verbose, persisting session state where appropriate.
  • Added multi-agent routing configuration models plus an AgentRouteResolver integration component.
  • Implemented Tailscale Serve/Funnel helper and an mDNS discovery service for easier gateway access/discovery.
  • Added a Gmail Pub/Sub webhook bridge that injects “triage” prompts into the runtime pipeline.
  • Added Ed25519 verification via BouncyCastle for Discord interaction signature validation.
  • Updated README/roadmap docs and added security-focused tests for new channel/webhook paths.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

runtime.Pipeline,
app.Services.GetRequiredService<Microsoft.Extensions.Logging.ILogger<GmailPubSubBridge>>());

app.MapPost(startup.Config.GmailPubSub.WebhookPath, async (HttpContext ctx) =>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/OpenClaw.Gateway/Endpoints/WebhookEndpoints.cs:680: The Gmail Pub/Sub webhook is exposed as a plain POST that immediately injects an IsSystem=true InboundMessage into the runtime, but there’s no verification that the request actually came from Google Pub/Sub. Consider adding an authenticity check (e.g., verified push auth/JWT or a shared secret) to prevent arbitrary external callers from triggering agent runs.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

/// The signed message is: timestamp + body.
/// Uses a minimal TweetNaCl-compatible Ed25519 verification.
/// </summary>
private bool ValidateSignature(string body, string? signature, string? timestamp)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/OpenClaw.Gateway/DiscordWebhookHandler.cs:152: ValidateSignature() verifies Ed25519 over timestamp + body, but it doesn’t enforce any maximum clock skew / freshness on timestamp, so a captured valid request could be replayed later and still verify. Consider rejecting timestamps outside a short window to reduce replay risk (similar to the Slack handler’s 5-minute check).

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

await ProcessSignalCliMessageAsync(line, ct);
}

await process.WaitForExitAsync(ct);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/OpenClaw.Channels/SignalChannel.cs:229: In RunSignalCliLoopAsync, cancellation exits the read loop, but the signal-cli daemon --json child process may keep running in the background if it isn’t explicitly terminated, which can leak orphan daemons over restarts/shutdown. Consider ensuring the subprocess is stopped on cancellation (or that closing stdin reliably terminates it) before breaking out.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

return $"Error: Hunk at line {hunk.OriginalStart} is out of range (file has {result.Count} lines).";

// Remove old lines
var removeCount = Math.Min(hunk.RemoveLines.Count, result.Count - startLine);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/OpenClaw.Agent/Tools/ApplyPatchTool.cs:60: apply_patch removes RemoveLines.Count lines without verifying that the file content matches the hunk (and silently truncates removal with Math.Min(...) if the file is shorter), which can apply a patch incorrectly without error. Consider validating the expected removed lines / bounds so mismatched patches fail fast instead of corrupting the file.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR significantly expands OpenClaw.NET’s gateway/channel surface area by adding new channel adapters (Slack/Discord/Signal), a large set of new native tools and tool presets/groups, plus several new “integrations” (routing, Gmail Pub/Sub, Tailscale, mDNS) and chat commands.

Changes:

  • Adds Slack/Discord/Signal channel adapters and webhook handlers, including allowlist/signature verification plumbing.
  • Introduces multiple new native tools (file editing/patching, sessions utilities, messaging, cron/gateway status, profile write, X search) and expands tool preset resolution with new presets and built-in tool groups.
  • Extends configuration and docs to cover new capabilities (channels, routing, integrations, commands).

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 29 comments.

Show a summary per file
File Description
src/OpenClaw.Tests/ChannelAdapterSecurityTests.cs Adds security-focused tests for signature verification and allowlist behavior.
src/OpenClaw.Gateway/Tools/SessionsYieldTool.cs New tool to yield execution to another session and wait for a response.
src/OpenClaw.Gateway/Tools/SessionStatusTool.cs New tool to display compact session status/usage.
src/OpenClaw.Gateway/Tools/SessionsSpawnTool.cs New tool to spawn a new session with an initial prompt.
src/OpenClaw.Gateway/Tools/SessionsSendTool.cs New tool to send a system message to another session.
src/OpenClaw.Gateway/Tools/SessionsHistoryTool.cs New tool to retrieve a session transcript/history.
src/OpenClaw.Gateway/Tools/ProfileWriteTool.cs New tool to create/update user profile fields.
src/OpenClaw.Gateway/Tools/GatewayTool.cs New tool to show gateway status/config summary.
src/OpenClaw.Gateway/Tools/CronTool.cs New tool to list/get/run cron jobs via the inbound pipeline.
src/OpenClaw.Gateway/Tools/AgentsListTool.cs New tool to list configured delegation agent profiles.
src/OpenClaw.Gateway/ToolPresetResolver.cs Adds new presets (full/coding/messaging/minimal) and built-in tool groups.
src/OpenClaw.Gateway/SlackWebhookHandler.cs Implements Slack Events API + slash command webhook handling with signature validation and allowlists.
src/OpenClaw.Gateway/OpenClaw.Gateway.csproj Adds BouncyCastle dependency for Ed25519 verification.
src/OpenClaw.Gateway/Integrations/TailscaleService.cs Adds a Tailscale Serve/Funnel helper service (not yet wired).
src/OpenClaw.Gateway/Integrations/MdnsDiscoveryService.cs Adds an mDNS/DNS-SD discovery service (not yet wired).
src/OpenClaw.Gateway/Integrations/GmailPubSubBridge.cs Adds Gmail Pub/Sub webhook bridge into the inbound pipeline.
src/OpenClaw.Gateway/Integrations/AgentRouteResolver.cs Adds routing resolver for per-channel/sender routing (not yet wired).
src/OpenClaw.Gateway/Endpoints/WebhookEndpoints.cs Maps new Slack/Discord webhook endpoints and Gmail Pub/Sub endpoint.
src/OpenClaw.Gateway/Ed25519Verify.cs Adds Ed25519 verification helper backed by BouncyCastle.
src/OpenClaw.Gateway/DiscordWebhookHandler.cs Implements Discord interactions webhook handling with signature validation and allowlists.
src/OpenClaw.Gateway/Composition/RuntimeInitializationExtensions.cs Registers new channels and new built-in tools in runtime composition.
src/OpenClaw.Gateway/Composition/ChannelServicesExtensions.cs Adds DI wiring for Slack/Discord/Signal channels + Slack/Discord handlers.
src/OpenClaw.Core/Pipeline/ChatCommandProcessor.cs Adds /think, /compact, /verbose commands and compaction callback hook.
src/OpenClaw.Core/Models/Session.cs Adds session fields for reasoning effort and verbose mode; extends JSON context types.
src/OpenClaw.Core/Models/GatewayConfig.cs Adds configs for Slack/Discord/Signal + routing/Tailscale/GmailPubSub/mDNS.
src/OpenClaw.Channels/SlackChannel.cs Implements Slack outbound messaging via Slack Web API.
src/OpenClaw.Channels/SignalChannel.cs Implements Signal adapter via signald or signal-cli.
src/OpenClaw.Channels/DiscordChannel.cs Implements Discord adapter via Gateway WebSocket + REST send, plus slash command registration.
src/OpenClaw.Agent/Tools/XSearchTool.cs Adds X (Twitter) search tool.
src/OpenClaw.Agent/Tools/MessageTool.cs Adds cross-channel outbound message enqueue tool.
src/OpenClaw.Agent/Tools/MemoryGetTool.cs Adds direct memory note retrieval tool.
src/OpenClaw.Agent/Tools/EditFileTool.cs Adds targeted search/replace file edit tool.
src/OpenClaw.Agent/Tools/ApplyPatchTool.cs Adds unified-diff patch apply tool.
README.md Updates documentation for new features, channels, and live session surface.
docs/ROADMAP.md Adds roadmap items (currently inconsistent with implemented adapters).

Comment on lines +91 to +99
// Workspace allowlist
if (_config.AllowedWorkspaceIds.Length > 0 && !string.IsNullOrWhiteSpace(wrapper.TeamId))
{
if (!Array.Exists(_config.AllowedWorkspaceIds, id => string.Equals(id, wrapper.TeamId, StringComparison.Ordinal)))
{
_logger.LogWarning("Rejected Slack message from disallowed workspace {TeamId}.", wrapper.TeamId);
return new WebhookResponse(403);
}
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Workspace allowlist can be bypassed when AllowedWorkspaceIds is set but the incoming payload has a missing/blank team_id (wrapper.TeamId). Right now the check only runs when TeamId is non-empty, so a malformed payload would skip allowlisting entirely. If AllowedWorkspaceIds is configured, reject requests with missing team_id (e.g., 400/403) before continuing.

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +184
if (_config.AllowedWorkspaceIds.Length > 0 && !string.IsNullOrWhiteSpace(teamId))
{
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Same allowlist bypass exists for slash commands: if AllowedWorkspaceIds is configured but team_id is missing/blank, the request is not rejected. Consider rejecting when team_id is absent while an allowlist is configured, to avoid accepting malformed/forged requests.

Suggested change
if (_config.AllowedWorkspaceIds.Length > 0 && !string.IsNullOrWhiteSpace(teamId))
{
if (_config.AllowedWorkspaceIds.Length > 0)
{
if (string.IsNullOrWhiteSpace(teamId))
return new WebhookResponse(400, "Missing team_id.");

Copilot uses AI. Check for mistakes.
Comment on lines +246 to +256
// Prevent replay attacks: reject requests older than 5 minutes
if (long.TryParse(timestamp, out var ts))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(now - ts) > 300)
{
_logger.LogWarning("Rejected Slack webhook with stale timestamp ({Timestamp}).", timestamp);
return false;
}
}

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

ValidateSlackSignature skips the replay-window check when the timestamp header is not a valid integer (TryParse fails). With signature validation enabled, it’s safer to treat an invalid timestamp as an invalid request and return false, rather than accepting a non-parseable timestamp.

Suggested change
// Prevent replay attacks: reject requests older than 5 minutes
if (long.TryParse(timestamp, out var ts))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(now - ts) > 300)
{
_logger.LogWarning("Rejected Slack webhook with stale timestamp ({Timestamp}).", timestamp);
return false;
}
}
// Prevent replay attacks: reject requests older than 5 minutes or with invalid timestamp
if (!long.TryParse(timestamp, out var ts))
{
_logger.LogWarning("Rejected Slack webhook with invalid timestamp format ({Timestamp}).", timestamp);
return false;
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(now - ts) > 300)
{
_logger.LogWarning("Rejected Slack webhook with stale timestamp ({Timestamp}).", timestamp);
return false;
}

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +67
response.EnsureSuccessStatusCode();

_logger.LogInformation("Sent Slack message to {Channel}", outbound.RecipientId);
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

SlackChannel.SendAsync only checks HTTP status via EnsureSuccessStatusCode(), but Slack Web API often returns 200 with an error payload (e.g., {"ok": false, "error": ...}). This will log success even when the message was rejected (invalid_auth, channel_not_found, rate_limited, etc.). Parse the JSON response and enforce ok==true (and handle Slack’s rate_limit errors / Retry-After).

Suggested change
response.EnsureSuccessStatusCode();
_logger.LogInformation("Sent Slack message to {Channel}", outbound.RecipientId);
var responseBody = await response.Content.ReadAsStringAsync();
// Handle Slack rate limiting (HTTP 429 with Retry-After).
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
var retryAfterHeader = response.Headers.RetryAfter;
TimeSpan? retryAfter = retryAfterHeader?.Delta;
if (!retryAfter.HasValue && retryAfterHeader?.Date.HasValue == true)
{
retryAfter = retryAfterHeader.Date.Value - DateTimeOffset.UtcNow;
}
_logger.LogWarning(
"Slack rate_limited when sending message to {Channel}. Retry-After: {RetryAfter}. Response: {ResponseBody}",
outbound.RecipientId,
retryAfter,
responseBody);
return;
}
// Handle non-success HTTP status codes (other than 429 handled above).
if (!response.IsSuccessStatusCode)
{
_logger.LogError(
"Slack API request failed with status code {StatusCode} when sending message to {Channel}. Response: {ResponseBody}",
(int)response.StatusCode,
outbound.RecipientId,
responseBody);
return;
}
// For HTTP 2xx, Slack may still return application-level errors via { \"ok\": false, \"error\": \"...\" }.
try
{
using var document = JsonDocument.Parse(responseBody);
var root = document.RootElement;
if (root.TryGetProperty("ok", out var okProperty) && okProperty.ValueKind == JsonValueKind.True)
{
_logger.LogInformation("Sent Slack message to {Channel}", outbound.RecipientId);
return;
}
string? errorCode = null;
if (root.TryGetProperty("error", out var errorProperty) && errorProperty.ValueKind == JsonValueKind.String)
{
errorCode = errorProperty.GetString();
}
_logger.LogError(
"Slack API responded with ok=false when sending message to {Channel}. Error: {Error}. Response: {ResponseBody}",
outbound.RecipientId,
errorCode,
responseBody);
}
catch (JsonException)
{
_logger.LogError(
"Failed to parse Slack API response when sending message to {Channel}. Raw response: {ResponseBody}",
outbound.RecipientId,
responseBody);
}

Copilot uses AI. Check for mistakes.
Comment on lines +462 to +470
var deliveryKey = ctx.Request.Headers["X-Slack-Request-Timestamp"].ToString() + ":" +
(bodyText.Length > 64 ? bodyText[..64] : bodyText);
var hashedKey = WebhookDeliveryStore.HashDeliveryKey(deliveryKey);
if (!deliveries.TryBegin("slack", hashedKey, TimeSpan.FromHours(6)))
{
ctx.Response.StatusCode = StatusCodes.Status200OK;
await ctx.Response.WriteAsync("Duplicate ignored.", ctx.RequestAborted);
return;
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Slack webhook delivery deduplication runs before signature verification. This enables an unauthenticated sender to pre-fill the delivery-key cache and cause valid Slack events to be treated as duplicates (availability/DoS). Consider validating the Slack signature first (or at least including the validated signature/timestamp in the key) before calling deliveries.TryBegin().

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +35
using OpenClaw.Core.Abstractions;
using OpenClaw.Core.Models;

namespace OpenClaw.Agent.Tools;

/// <summary>
/// Apply a unified diff patch to a file. Supports multi-hunk patches.
/// </summary>
public sealed class ApplyPatchTool : ITool
{
private readonly ToolingConfig _config;

public ApplyPatchTool(ToolingConfig config) => _config = config;

public string Name => "apply_patch";
public string Description => "Apply a unified diff patch to a file. Supports multi-hunk patches for complex edits.";
public string ParameterSchema => """{"type":"object","properties":{"path":{"type":"string","description":"File path to patch"},"patch":{"type":"string","description":"Unified diff patch content (lines starting with +/- and @@ hunk headers)"}},"required":["path","patch"]}""";

public async ValueTask<string> ExecuteAsync(string argumentsJson, CancellationToken ct)
{
if (_config.ReadOnlyMode)
return "Error: apply_patch is disabled because Tooling.ReadOnlyMode is enabled.";

using var args = System.Text.Json.JsonDocument.Parse(
string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson);
var root = args.RootElement;

var path = GetString(root, "path");
if (string.IsNullOrWhiteSpace(path))
return "Error: 'path' is required.";

var patch = GetString(root, "patch");
if (string.IsNullOrWhiteSpace(patch))
return "Error: 'patch' is required.";

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

New file-manipulation tools (edit_file, apply_patch) are introduced without any unit tests, while the repo has extensive tool-level tests (e.g., FileReadToolTests, ToolPathPolicyTests). Add coverage for success paths and failure cases (path allow/deny, old_text uniqueness, multi-hunk patches, mismatch handling) to prevent accidental file corruption regressions.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +25 to +53
public string Name => "sessions_yield";
public string Description => "Yield execution to another session. Sends a message and waits for the target to respond, then returns the response.";
public string ParameterSchema => """{"type":"object","properties":{"session_id":{"type":"string","description":"Target session ID to yield to"},"message":{"type":"string","description":"Message to send to the target session"},"timeout_seconds":{"type":"integer","description":"Max seconds to wait for response (default 60, max 300)"}},"required":["session_id","message"]}""";

public ValueTask<string> ExecuteAsync(string argumentsJson, CancellationToken ct)
=> ValueTask.FromResult("Error: sessions_yield requires execution context.");

public async ValueTask<string> ExecuteAsync(string argumentsJson, ToolExecutionContext context, CancellationToken ct)
{
using var args = JsonDocument.Parse(
string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson);
var root = args.RootElement;

var sessionId = GetString(root, "session_id");
if (string.IsNullOrWhiteSpace(sessionId))
return "Error: 'session_id' is required.";

var message = GetString(root, "message");
if (string.IsNullOrWhiteSpace(message))
return "Error: 'message' is required.";

var timeoutSeconds = 60;
if (root.TryGetProperty("timeout_seconds", out var ts) && ts.ValueKind == JsonValueKind.Number)
timeoutSeconds = Math.Clamp(ts.GetInt32(), 5, 300);

// Verify target session exists
var target = _sessions.TryGetActiveById(sessionId);
if (target is null)
return $"Error: Session '{sessionId}' not found or not active.";
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

New session-management tools (sessions_history/sessions_send/sessions_spawn/sessions_yield/session_status) don’t appear to have test coverage (no related tests found), despite existing coverage for other tools. Add unit tests for argument validation, session lookup behavior, and success/failure responses to keep these operator-facing tools stable.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +44 to +46
var target = _sessions.TryGetActiveById(sessionId);
if (target is null)
return $"Error: Session '{sessionId}' not found or not active.";
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

SessionsSendTool uses SessionManager.TryGetActiveById(), which is an O(n) scan of active sessions. Prefer SessionManager.LoadAsync(sessionId, ct) (or a direct dictionary lookup API) to avoid linear scans as active session counts grow.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +48
// Try active session first, then load from store
var session = _sessions.TryGetActiveById(sessionId);
if (session is null)
session = await _store.GetSessionAsync(sessionId, ct);

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

SessionsHistoryTool uses SessionManager.TryGetActiveById(), which is an O(n) scan. Since SessionManager already has LoadAsync(sessionId, ct) that does an O(1) active lookup and then store fallback, prefer that over TryGetActiveById + GetSessionAsync to avoid linear scans and duplicate logic.

Copilot uses AI. Check for mistakes.
Comment on lines +285 to +290
Arguments = $"-u {_accountNumber} send -m \"{EscapeJson(text)}\" {recipient}",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

SendViaSignalCliAsync builds ProcessStartInfo.Arguments by manually quoting and using EscapeJson(), but EscapeJson is JSON-oriented and does not correctly escape/quote for command-line parsing (e.g., tabs, backslashes, embedded quotes). This can corrupt message content and can break for certain inputs. Prefer ProcessStartInfo.ArgumentList (or a proper CLI-escaping routine) and pass message text as a separate argument.

Suggested change
Arguments = $"-u {_accountNumber} send -m \"{EscapeJson(text)}\" {recipient}",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
psi.ArgumentList.Add("-u");
psi.ArgumentList.Add(_accountNumber);
psi.ArgumentList.Add("send");
psi.ArgumentList.Add("-m");
psi.ArgumentList.Add(text);
psi.ArgumentList.Add(recipient);

Copilot uses AI. Check for mistakes.
Telli and others added 3 commits March 31, 2026 21:37
…list/search)

Adds a native plugin management command that fetches packages from npm
(which also hosts ClawHub packages) and installs them into the extensions
directory for the plugin bridge to discover.

Commands:
- openclaw plugins install <package|path|tarball> — fetch from npm or local
- openclaw plugins remove <plugin-name> — uninstall a plugin
- openclaw plugins list — show installed plugins with manifest info
- openclaw plugins search <query> — search npm for OpenClaw plugins

Supports scoped npm packages (@scope/plugin), local directories, and .tgz
archives. Installs dependencies automatically via npm install --production.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security fixes:
- Gmail Pub/Sub: add shared secret verification (WebhookSecret/WebhookSecretRef)
  to prevent unauthenticated callers from triggering agent runs
- Discord: add 5-minute timestamp replay protection to Ed25519 signature
  validation, matching the Slack handler pattern
- Slack: reject requests with missing team_id when workspace allowlist is
  configured (prevents allowlist bypass via malformed payloads)
- Slack: reject invalid timestamp formats instead of skipping replay check
- Slack: use event_id for dedup key instead of body prefix to avoid
  false-positive deduplication of distinct events

Reliability fixes:
- Slack SendAsync: handle ok=false responses and 429 rate limiting instead
  of treating all 200s as success
- Signal CLI: kill daemon process on cancellation to prevent orphan leaks
- Signal CLI: use ProcessStartInfo.ArgumentList instead of string
  interpolation for safe argument passing
- ApplyPatchTool: validate removed lines match file content before applying;
  fail fast on mismatches instead of silently corrupting files
- EditFileTool: reject empty old_text to prevent unexpected replacements
- SessionsYieldTool: guard against self-yield deadlock, use exponential
  backoff polling, avoid repeated store reads
- SessionStatusTool: use TotalHours for accurate duration display

Integration wiring:
- Wire TailscaleService and MdnsDiscoveryService into gateway startup
- Update Discord webhook handler Ed25519 comment to reflect BouncyCastle
- Remove unused GatewayConfig parameter from XSearchTool constructor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses remaining PR review comments that required deeper integration:

Multi-agent routing:
- Wire AgentRouteResolver into GatewayWorkers inbound processing
- Apply route model overrides to sessions after creation

Reasoning effort (/think):
- Plumb session.ReasoningEffort into ChatOptions.AdditionalProperties
  for both RunAsync and RunStreamingAsync LLM call paths

History compaction (/compact):
- Make CompactHistoryAsync public on AgentRuntime
- Wire SetCompactCallback from gateway runtime initialization so
  /compact triggers LLM-powered summarization instead of simple trim

Verbose mode (/verbose):
- Append tool call count and token delta footer to responses when
  session.VerboseMode is enabled

DM policy enforcement:
- Extend DmPolicy resolution in GatewayWorkers to cover Slack, Discord,
  and Signal channels (was only SMS/Telegram/WhatsApp/Teams)
- Add DmPolicy validation for new channels in ConfigValidator

Documentation:
- Update ROADMAP.md to move completed channel/tool/preset/command work
  from future plans to "Recently Completed"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Telli and others added 6 commits March 31, 2026 22:35
…tegrations

Complete README overhaul covering the current feature set:
- 48 native tools organized by category with full table
- 9 channel adapters with transport details and feature highlights
- Tool presets (full/coding/messaging/minimal) and groups (group:*)
- Chat commands (/think, /compact, /verbose)
- Plugin installer (openclaw plugins install)
- Multi-agent routing, Tailscale, Gmail Pub/Sub, mDNS
- Updated architecture diagram showing message pipeline, session
  manager, route resolution, and tool backends
- Simplified runtime flow diagram
- Badges for tool count and channel count

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move AgentRouteResolver construction outside worker loop (was per-message)
- Route model override now always applies (was ??= which skipped existing)
- Fix verbose footer tool call count: scan all turns since last user turn
  instead of only checking the final assistant turn
- Add verbose footer to streaming branch (was non-streaming only)
- Fix /compact callback: return actual remaining turn count, remove unused var
- Wire reasoning_effort into MafAgentRuntime.CreateChatOptions for MAF parity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: wire deferred review items end-to-end
The DeliveryFailureDoesNotMarkDelivered test asserts that the outbound
worker attempted to send (adapter.SendAttempts > 0), but the heartbeat
status is written by the inbound worker before the outbound worker
processes the queued message. Add a brief delay after heartbeat status
is confirmed to allow the outbound worker to attempt (and fail) delivery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Telli Telli merged commit 94d1ef8 into main Apr 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants