feat: channel adapters, 48 native tools, presets, commands, and integrations#38
feat: channel adapters, 48 native tools, presets, commands, and integrations#38
Conversation
…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>
🤖 Augment PR SummarySummary: This PR significantly expands OpenClaw.NET’s runtime surfaces (channels, tools, presets, and integrations) to support richer operator workflows and multi-channel messaging. Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
| runtime.Pipeline, | ||
| app.Services.GetRequiredService<Microsoft.Extensions.Logging.ILogger<GmailPubSubBridge>>()); | ||
|
|
||
| app.MapPost(startup.Config.GmailPubSub.WebhookPath, async (HttpContext ctx) => |
There was a problem hiding this comment.
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
🤖 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) |
There was a problem hiding this comment.
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
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| await ProcessSignalCliMessageAsync(line, ct); | ||
| } | ||
|
|
||
| await process.WaitForExitAsync(ct); |
There was a problem hiding this comment.
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
🤖 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); |
There was a problem hiding this comment.
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
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
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). |
| // 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| if (_config.AllowedWorkspaceIds.Length > 0 && !string.IsNullOrWhiteSpace(teamId)) | ||
| { |
There was a problem hiding this comment.
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.
| if (_config.AllowedWorkspaceIds.Length > 0 && !string.IsNullOrWhiteSpace(teamId)) | |
| { | |
| if (_config.AllowedWorkspaceIds.Length > 0) | |
| { | |
| if (string.IsNullOrWhiteSpace(teamId)) | |
| return new WebhookResponse(400, "Missing team_id."); |
| // 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; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| // 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; | |
| } |
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| _logger.LogInformation("Sent Slack message to {Channel}", outbound.RecipientId); |
There was a problem hiding this comment.
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).
| 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); | |
| } |
| 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; | ||
| } |
There was a problem hiding this comment.
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().
| 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."; | ||
|
|
There was a problem hiding this comment.
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.
| 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."; |
There was a problem hiding this comment.
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.
| var target = _sessions.TryGetActiveById(sessionId); | ||
| if (target is null) | ||
| return $"Error: Session '{sessionId}' not found or not active."; |
There was a problem hiding this comment.
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.
| // Try active session first, then load from store | ||
| var session = _sessions.TryGetActiveById(sessionId); | ||
| if (session is null) | ||
| session = await _store.GetSessionAsync(sessionId, ct); | ||
|
|
There was a problem hiding this comment.
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.
| Arguments = $"-u {_accountNumber} send -m \"{EscapeJson(text)}\" {recipient}", | ||
| RedirectStandardOutput = true, | ||
| UseShellExecute = false, | ||
| CreateNoWindow = true | ||
| }; | ||
|
|
There was a problem hiding this comment.
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.
| 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); |
…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>
…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>
Summary
full,coding,messaging,minimal(9 total)group:runtime,group:fs,group:sessions,group:memory,group:web,group:automation,group:messaging/think off|low|medium|high,/compact,/verbose on|offTest plan
/preset coding, etc.)/think,/compact,/verbosecommands in a chat session🤖 Generated with Claude Code