test(slack): add regression test for thread_ts == ts case (#32353)#50
Open
newtontech wants to merge 1197 commits intomainfrom
Open
test(slack): add regression test for thread_ts == ts case (#32353)#50newtontech wants to merge 1197 commits intomainfrom
newtontech wants to merge 1197 commits intomainfrom
Conversation
) * fix(synology-chat): prevent restart loop in startAccount startAccount must return a Promise that stays pending while the channel is running. The gateway wraps the return value in Promise.resolve(), and when it resolves, the gateway thinks the channel crashed and auto-restarts with exponential backoff (5s → 10s → 20s..., up to 10 attempts). Replace the synchronous { stop } return with a Promise<void> that resolves only when ctx.abortSignal fires, keeping the channel alive until shutdown. Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(synology-chat): add type guards for startAccount return value startAccount returns `void | { stop: () => void }` — TypeScript requires a type guard before accessing .stop on the union type. Added proper checks in both integration and unit tests. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(synology-chat): use Readable stream in integration test for Windows compat Replace EventEmitter + process.nextTick with Readable stream for request body simulation. The process.nextTick approach caused the test to hang on Windows CI (120s timeout) because events were not reliably delivered to readBody() listeners. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: stabilize synology gateway account lifecycle (openclaw#23074) (thanks @druide67) --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
When enforceFinalTag is active (Google providers), stripBlockTags correctly returns empty for text without <final> tags. However, the handleMessageEnd fallback recovered raw text, bypassing this protection and leaking internal reasoning (e.g. "**Applying single-bot mention rule**NO_REPLY") to Discord. Guard the fallback with enforceFinalTag check: if the provider is supposed to use <final> tags and none were seen, the text is treated as leaked reasoning and suppressed. Also harden stripSilentToken regex to allow bold markdown (**) as separator before NO_REPLY, matching the pattern Gemini Flash Lite produces. Co-Authored-By: Claude Opus 4.6 <[email protected]>
The native streaming path (chatStream) and preview final edit path (chat.update) send raw Markdown text without converting to Slack mrkdwn format. This causes **bold** to appear as literal asterisks instead of rendered bold text. Apply markdownToSlackMrkdwn() in streaming.ts (start/append/stop) and in dispatch.ts (preview final edit via chat.update) to match the non-streaming delivery path behavior. Closes openclaw#31892
…t dispatch Replace the single per-account messageQueue Promise chain in DiscordMessageListener with per-channel queues. This restores parallel processing for channel-bound agents that regressed in 2026.3.1. Messages within the same channel remain serialized to preserve ordering, while messages to different channels now proceed independently. Completed queue entries are cleaned up to prevent memory accumulation. Closes openclaw#31530
…o/video uploads The downloadAndSaveTelegramFile inner function only used the server-side file path (e.g. "documents/file_42.pdf") or the Content-Disposition header (which Telegram doesn't send) to derive the saved filename. The original filename provided by Telegram via msg.document.file_name, msg.audio.file_name, msg.video.file_name, and msg.animation.file_name was never passed through, causing all inbound files to lose their user-provided names. Now downloadAndSaveTelegramFile accepts an optional telegramFileName parameter that takes priority over the fetched/server-side name. The resolveMedia call site extracts the original name from the message and passes it through. Closes openclaw#31768 Made-with: Cursor
…nitions xAI rejects minLength, maxLength, minItems, maxItems, minContains, and maxContains in tool schemas with a 502 error instead of ignoring them. This causes all requests to fail when any tool definition includes these validation-constraint keywords (e.g. sessions_spawn uses maxLength and maxItems on its attachment fields). Add stripXaiUnsupportedKeywords() in schema/clean-for-xai.ts, mirroring the existing cleanSchemaForGemini() pattern. Apply it in normalizeToolParameters() when the provider is xai directly, or openrouter with an x-ai/* model id. Fixes tool calls for x-ai/grok-* models both direct and via OpenRouter.
Addresses openclaw#31699 — config .bak files persist with sensitive data. Changes: - Explicitly chmod 0o600 on all .bak files after creation, instead of relying on copyFile to preserve source permissions (not guaranteed on all platforms, e.g. Windows, NFS mounts). - Clean up orphan .bak files that fall outside the managed 5-deep rotation ring (e.g. PID-stamped leftovers from interrupted writes, manual backups like .bak.before-marketing). - Add tests for permission hardening and orphan cleanup. The backup ring itself is preserved — it's a valuable recovery mechanism. This PR hardens the security surface by ensuring backup files are always owner-only and stale copies don't accumulate indefinitely.
Fixes openclaw#27189 When an inbound message is debounced, the Bot Framework turn context is revoked before the debouncer flushes and the reply is dispatched. Any attempt to use the revoked context proxy throws a TypeError, causing the reply to fail silently. This commit fixes the issue by adding a fallback to proactive messaging when the turn context is revoked: - `isRevokedProxyError()`: New error utility to reliably detect when a proxy has been revoked. - `reply-dispatcher.ts`: `sendTypingIndicator` now catches revoked proxy errors and falls back to sending the typing indicator via `adapter.continueConversation`. - `messenger.ts`: `sendMSTeamsMessages` now catches revoked proxy errors when `replyStyle` is `thread` and falls back to proactive messaging. This ensures that replies are delivered reliably even when the inbound message was debounced, resolving the core issue where the bot appeared to ignore messages.
…21208) * feat(tlon): sync with openclaw-tlon master - Add tlon CLI tool registration with binary lookup - Add approval, media, settings, foreigns, story, upload modules - Add http-api wrapper for Urbit connection patching - Update types for defaultAuthorizedShips support - Fix type compatibility with core plugin SDK - Stub uploadFile (API not yet available in @tloncorp/api-beta) - Remove incompatible test files (security, sse-client, upload) * chore(tlon): remove dead code Remove unused Urbit channel client files: - channel-client.ts - channel-ops.ts - context.ts These were not imported anywhere in the extension. * feat(tlon): add image upload support via @tloncorp/api - Import configureClient and uploadFile from @tloncorp/api - Implement uploadImageFromUrl using uploadFile - Configure API client before media uploads - Update dependency to github:tloncorp/api-beta#main * fix(tlon): restore SSRF protection with event ack tracking - Restore context.ts and channel-ops.ts for SSRF support - Restore sse-client.ts with urbitFetch for SSRF-protected requests - Add event ack tracking from openclaw-tlon (acks every 20 events) - Pass ssrfPolicy through authenticate() and UrbitSSEClient - Fixes security regression from sync with openclaw-tlon * fix(tlon): restore buildTlonAccountFields for allowPrivateNetwork The inlined payload building was missing allowPrivateNetwork field, which would prevent the setting from being persisted to config. * fix(tlon): restore SSRF protection in probeAccount - Restore channel-client.ts for UrbitChannelClient - Use UrbitChannelClient with ssrfPolicy in probeAccount - Ensures account probe respects allowPrivateNetwork setting * feat(tlon): add ownerShip to setup flow ownerShip should always be set as it controls who receives approval requests and can approve/deny actions. * chore(tlon): remove unused http-api.ts After restoring SSRF protection, probeAccount uses UrbitChannelClient instead of @urbit/http-api. The http-api.ts wrapper is no longer needed. * refactor(tlon): simplify probeAccount to direct /~/name request No channel needed - just authenticate and GET /~/name. Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor. * chore(tlon): add logging for event acks * chore(tlon): lower ack threshold to 5 for testing * fix(tlon): address security review issues - Fix SSRF in upload.ts: use urbitFetch with SSRF protection - Fix SSRF in media.ts: use urbitFetch with SSRF protection - Add command whitelist to tlon tool to prevent command injection - Add getDefaultSsrFPolicy() helper for uploads/downloads * fix(tlon): restore auth retry and add reauth on SSE reconnect - Add authenticateWithRetry() helper with exponential backoff (restores lost logic from #39) - Add onReconnect callback to re-authenticate when SSE stream reconnects - Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth * fix(tlon): add infinite reconnect with reset after max attempts Instead of giving up after maxReconnectAttempts, wait 10 seconds then reset the counter and keep trying. This ensures the monitor never permanently disconnects due to temporary network issues. * test(tlon): restore security, sse-client, and upload tests - security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization - sse-client.test.ts: subscription handling, cookie updates, reconnection params - upload.test.ts: image upload with SSRF protection, error handling * fix(tlon): restore DM partner ship extraction for proper routing - Add extractDmPartnerShip() to extract partner from 'whom' field - Use partner ship for routing (more reliable than essay.author) - Explicitly ignore bot's own outbound DM events - Log mismatch between author and partner for debugging * chore(tlon): restore ack threshold to 20 * chore(tlon): sync slash commands support from upstream - Add stripBotMention for proper CommandBody parsing - Add command authorization logic for owner-only slash commands - Add CommandAuthorized and CommandSource to context payload * fix(tlon): resolve TypeScript errors in tests and monitor - Store validated account url/code before closure to fix type narrowing - Fix test type annotations for mode rules - Add proper Response type cast in sse-client mock - Use optional chaining for init properties * docs(tlon): update docs for new config options and capabilities - Document ownerShip for approval system - Document autoAcceptDmInvites and autoAcceptGroupInvites - Update status to reflect rich text and image support - Add bundled skill section - Update notes with formatting and image details - Fix pnpm-lock.yaml conflict * docs(tlon): fix dmAllowlist description and improve allowPrivateNetwork docs - Correct dmAllowlist: empty means no DMs allowed (not allow all) - Promote allowPrivateNetwork to its own section with examples - Add warning about SSRF protection implications * docs(tlon): clarify ownerShip is auto-authorized everywhere - Add ownerShip to minimal config example (recommended) - Document that owner is automatically allowed for DMs and channels - No need to add owner to dmAllowlist or defaultAuthorizedShips * docs(tlon): add capabilities table, troubleshooting, and config reference Align with Matrix docs format: - Capabilities table for quick feature reference - Troubleshooting section with common failures - Configuration reference with all options * docs(tlon): fix reactions status and expand bundled skill section - Reactions ARE supported via bundled skill (not missing) - Add link to skill GitHub repo - List skill capabilities: contacts, channels, groups, DMs, reactions, settings * fix(tlon): use crypto.randomUUID instead of Math.random for channel ID Fixes security test failure - Math.random is flagged as weak randomness. * docs: fix markdown lint - add blank line before </Step> * fix: address PR review issues for tlon plugin - upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to preserve full URL path when fetching external images; add release() call - media.ts: Same fix - use fetchWithSsrFGuard for external media downloads; add release() call to clean up resources - channel.ts: Use urbitFetch for poke API to maintain consistent SSRF protection (DNS pinning + redirect handling) - upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch Addresses blocking issues from jalehman's review: 1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path) 2. Fixed missing release() calls that could leak resources 3. Restored guarded fetch semantics for poke operations * docs: add tlon changelog fragment * style: format tlon monitor * fix: align tlon lockfile and sse id generation * docs: fix onboarding markdown list spacing --------- Co-authored-by: Josh Lehman <[email protected]>
…bundle splitting Without this fix, the bundler can emit multiple copies of internal-hooks into separate chunks. registerInternalHook writes to one Map instance while triggerInternalHook reads from another — resulting in hooks that silently fire with zero handlers regardless of how many were registered. Reproduce: load a hook via hooks.external.entries (loader reads one chunk), then send a message:transcribed event (get-reply imports a different chunk). The handler list is empty; the hook never runs. Fix: use globalThis.__openclaw_internal_hook_handlers__ as a shared singleton. All module copies check for and reuse the same Map, ensuring registrations are always visible to triggers.
Addresses greptile review: collapses the if-guard + assignment into a single ??= expression so TypeScript can narrow the type without a non-null assertion.
When gateway.restart is triggered with a reason but no separate note, the payload sets both message and stats.reason to the same text. formatRestartSentinelMessage() then emits both the message line and a redundant 'Reason: <same text>' line, doubling the restart reason in the notification delivered to the agent session. Skip the 'Reason:' line when stats.reason matches the already-emitted message text. Add regression tests for both duplicate and distinct reason scenarios.
…32128) - Remove vi.hoisted() wrapper from exported mock in shared module (Vitest cannot export hoisted variables) - Inline vi.hoisted + vi.mock in startup test so Vitest's per-file hoisting registers mocks before production imports Co-authored-by: Claude Opus 4.6 <[email protected]>
) Fixes openclaw#32293: Discord voice message plays at ~0.5x speed with 24kHz TTS source When TTS providers (like mlx-audio Qwen3-TTS) output audioHz, Discord voice at 24k messages play at half speed because Discord expects 48kHz. This fix adds explicit sample rate conversion to 48kHz in the ensureOggOpus function, ensuring voice messages always play at correct speed regardless of the input audio's sample rate. Co-authored-by: Kevin Shenghui <[email protected]>
… runs (openclaw#32358) The `forceFlushTranscriptBytes` path (introduced in d729ab2) bypasses the `memoryFlushCompactionCount` guard that prevents repeated flushes within the same compaction cycle. Once the session transcript exceeds 2 MB, memory flush fires on every single message — even when token count is well under the compaction threshold. Extract `hasAlreadyFlushedForCurrentCompaction()` from the inline guard in `shouldRunMemoryFlush` and apply it to both the token-based and the transcript-size trigger paths. Fixes openclaw#32317 Signed-off-by: HCL <[email protected]>
) The sticker code path called ctx.getFile() directly without retry, unlike the non-sticker media path which uses resolveTelegramFileWithRetry (3 attempts with jitter). This made sticker downloads vulnerable to transient Telegram API failures, particularly in group topics where file availability can be delayed. Refs openclaw#32326 Co-authored-by: Claude Opus 4.6 <[email protected]>
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…2353) Adds explicit test coverage for the edge case where Slack auto-sets thread_ts == ts on top-level messages. This was fixed in openclaw#32320 but lacked a specific regression test for this exact scenario. The test verifies that messages with thread_ts == ts do NOT get a :thread: suffix in their session key, ensuring they stay on the per-channel session for continuity. Related: openclaw#32320, openclaw#32285
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds explicit regression test coverage for the edge case where Slack auto-sets
thread_ts == tson top-level messages. This specific scenario was fixed in openclaw#32320 but lacked a dedicated regression test.Changes
replyToModevalues:all,first, andoffthread_ts == tsdo NOT get a:thread:suffixTesting
prepare.thread-session-key.test.tspassthreading.test.ts)Related