msteams: add search message action#54832
Conversation
Wire up Graph API endpoints for message read, pin, unpin, and list-pins in the MS Teams extension, following the same patterns as edit/delete. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Handle 204 No Content in postGraphJson (Graph mutations may return empty body) - Strip conversation:/user: prefixes in resolveConversationPath to avoid Graph 404s - Remove dead variable in channel pin branch - Rename unpin param from messageId to pinnedMessageId for semantic clarity - Accept both pinnedMessageId and messageId in unpin action handler for compat Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Resolve user:<aadId> targets to actual conversation IDs via conversation store before Graph API calls (fixes 404 for DM-context actions) - Add User-Agent header to postGraphJson/deleteGraphRequest for consistency with fetchGraphJson after rebase onto main Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Prefer cached graphChatId over Bot Framework conversation IDs for user targets; throw descriptive error when no Graph-compatible ID is available - Add `id` field to list-pins rows so default formatters surface the pinned resource ID needed for the unpin flow Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Greptile SummaryThis PR adds a Issues found:
Confidence Score: 4/5PR is on the happy path to merge after fixing the double-quote injection in the $search query; all other new actions and the test suite look solid. 33 tests pass, the overall architecture is sound, and 9 of 10 new actions are clean. The one concrete bug (unescaped double quotes in the OData $search value) will cause real failures for any user who searches with a double quote in their query, and a one-line fix resolves it. The import-ordering issue in graph.ts is a quick style fix. extensions/msteams/src/graph-messages.ts (line 408 — double-quote escaping) and extensions/msteams/src/graph.ts (line 4 — import ordering)
|
| Filename | Overview |
|---|---|
| extensions/msteams/src/graph-messages.ts | New module implementing 8 Graph API operations (get, pin, unpin, list-pins, react, unreact, list-reactions, search). Logic is well-structured with proper user-target resolution; one bug — unescaped double quotes in the $search value. |
| extensions/msteams/src/graph.ts | Adds postGraphJson, postGraphBetaJson, and deleteGraphRequest helpers with correct error handling; const GRAPH_BETA is accidentally inserted between import statements. |
| extensions/msteams/src/channel.ts | Wires up 7 new action handlers (read, pin, unpin, list-pins, react, reactions, search) and the jsonActionResult helper; consistent with existing action patterns. |
| extensions/msteams/src/channel.runtime.ts | Registers all 8 new graph-messages functions into the runtime object; straightforward boilerplate with no issues. |
| extensions/msteams/src/graph-messages.test.ts | New test file with 33 cases covering all new operations including single-quote escaping in the from-filter; missing a test for double-quote injection in the $search query. |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/graph.ts
Line: 4-5
Comment:
**Constant declared between import statements**
`const GRAPH_BETA` lands between two `import` statements, which violates the `import/first` ESLint rule (all non-import statements must come after all imports). Move the constant below the import block.
```suggestion
import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js";
```
Then add the constant after all imports (below `import { buildUserAgent } from "./user-agent.js";`):
```ts
const GRAPH_BETA = "https://graph.microsoft.com/beta";
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: extensions/msteams/src/graph-messages.ts
Line: 408
Comment:
**Unescaped double quotes in `$search` value can break OData syntax**
The query is wrapped in double quotes (`"${params.query}"`) but double quotes inside `params.query` are not escaped. If a user searches for `say "hello"`, the resulting OData value becomes `"say "hello""`, which is malformed and will cause the Graph API request to fail.
OData `$search` strings should have internal double quotes escaped. Apply the same defensive pattern already used for single quotes in `escapeOData`:
```suggestion
qp.set("$search", `"${params.query.replace(/"/g, '\\"')}"`);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "msteams: add search message action via G..." | Re-trigger Greptile
| const GRAPH_BETA = "https://graph.microsoft.com/beta"; | ||
| import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js"; |
There was a problem hiding this comment.
Constant declared between import statements
const GRAPH_BETA lands between two import statements, which violates the import/first ESLint rule (all non-import statements must come after all imports). Move the constant below the import block.
| const GRAPH_BETA = "https://graph.microsoft.com/beta"; | |
| import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js"; | |
| import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js"; |
Then add the constant after all imports (below import { buildUserAgent } from "./user-agent.js";):
const GRAPH_BETA = "https://graph.microsoft.com/beta";Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/graph.ts
Line: 4-5
Comment:
**Constant declared between import statements**
`const GRAPH_BETA` lands between two `import` statements, which violates the `import/first` ESLint rule (all non-import statements must come after all imports). Move the constant below the import block.
```suggestion
import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js";
```
Then add the constant after all imports (below `import { buildUserAgent } from "./user-agent.js";`):
```ts
const GRAPH_BETA = "https://graph.microsoft.com/beta";
```
How can I resolve this? If you propose a fix, please make it concise.| ); | ||
|
|
||
| const qp = new URLSearchParams(); | ||
| qp.set("$search", `"${params.query}"`); |
There was a problem hiding this comment.
Unescaped double quotes in
$search value can break OData syntax
The query is wrapped in double quotes ("${params.query}") but double quotes inside params.query are not escaped. If a user searches for say "hello", the resulting OData value becomes "say "hello"", which is malformed and will cause the Graph API request to fail.
OData $search strings should have internal double quotes escaped. Apply the same defensive pattern already used for single quotes in escapeOData:
| qp.set("$search", `"${params.query}"`); | |
| qp.set("$search", `"${params.query.replace(/"/g, '\\"')}"`); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/graph-messages.ts
Line: 408
Comment:
**Unescaped double quotes in `$search` value can break OData syntax**
The query is wrapped in double quotes (`"${params.query}"`) but double quotes inside `params.query` are not escaped. If a user searches for `say "hello"`, the resulting OData value becomes `"say "hello""`, which is malformed and will cause the Graph API request to fail.
OData `$search` strings should have internal double quotes escaped. Apply the same defensive pattern already used for single quotes in `escapeOData`:
```suggestion
qp.set("$search", `"${params.query.replace(/"/g, '\\"')}"`);
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6a200b54e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| qp.set("$search", `"${params.query}"`); | ||
| qp.set("$top", String(top)); | ||
|
|
||
| if (params.from) { | ||
| qp.set("$filter", `from/user/displayName eq '${escapeOData(params.from)}'`); |
There was a problem hiding this comment.
Use supported Graph API for Teams message search
searchMessagesMSTeams builds a GET .../messages request with $search and an optional sender $filter, but the Graph list-messages APIs for chats/channels do not support those query options for full-text chat search; this either returns unfiltered recent messages or fails with a 4xx, so the new search action will not reliably return query matches in production.
Useful? React with 👍 / 👎.
| path: `${conv.basePath}/pinnedMessages`, | ||
| body: { message: { id: params.messageId } }, |
There was a problem hiding this comment.
Send Graph-compliant pin payload
The pin request body is sent as { message: { id } }, but Graph's pin-message contract expects a [email protected] reference to the chat message resource; with the current payload, pinMessageMSTeams will receive a request-validation error and fail to pin messages.
Useful? React with 👍 / 👎.
| if (conv.kind === "channel") { | ||
| // Graph v1.0 doesn't have channel pin — use the pinnedMessages pattern on chat | ||
| // For channels, attempt POST to pinnedMessages (same shape, may require beta) | ||
| await postGraphJson<unknown>({ | ||
| token, | ||
| path: `${conv.basePath}/pinnedMessages`, |
There was a problem hiding this comment.
Avoid channel pinnedMessages path for pin operations
For channel targets, the code posts to /teams/{teamId}/channels/{channelId}/pinnedMessages, but Graph pinned-message endpoints are chat-scoped (/chats/{chat-id}/pinnedMessages); this means channel pin requests will fail, and the same basePath pattern also impacts channel unpin/list paths.
Useful? React with 👍 / 👎.
… manual query string
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6160c71590
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const token = await resolveGraphToken(params.cfg); | ||
| const conversationId = await resolveGraphConversationId(params.to); | ||
| const { basePath } = resolveConversationPath(conversationId); | ||
| const path = `${basePath}/messages/${encodeURIComponent(params.messageId)}/setReaction`; | ||
| await postGraphBetaJson<unknown>({ token, path, body: { reactionType } }); |
There was a problem hiding this comment.
Switch reaction mutations off app-only Graph tokens
reactMessageMSTeams (and the adjacent unreactMessageMSTeams) use resolveGraphToken, which returns an app token, then call chatMessage:setReaction/unsetReaction. Those Graph endpoints are delegated-only (application permissions are not supported), so these calls will consistently fail with authorization errors in production for the current bot-auth model. This makes the newly added react and unreact actions non-functional unless the flow is changed to delegated user tokens or a different API is used.
Useful? React with 👍 / 👎.
BradGroux
left a comment
There was a problem hiding this comment.
Review: Search + 7 Graph Message Actions
Summary
Large PR adding searchMessagesMSTeams and seven additional Graph message actions (pin, unpin, react, unreact, getMessage, listPins, listReactions). Introduces postGraphBetaJson for beta endpoint support and a dedicated graph-messages.js module.
Analysis
Search sanitization strips double quotes via .replace(/"/g, "") and trims whitespace. Edge case: an input like '"" ""' passes the initial trim() check but sanitizes to an empty string, which would send $search="" to Graph. Worth adding a post-sanitization empty check.
postGraphBetaJson is a clean addition for beta endpoints. The pattern of extending the existing Graph helpers rather than duplicating auth logic is the right call.
Response handling correctly checks for content-length === "0" and 204 status before attempting JSON parse. No crash risk on empty responses.
GRAPH_BETA constant is declared between two import lines, which is a style inconsistency. Minor nit, no functional impact.
Note on overlap
This PR and #53432 both add reaction and pin functions. They will conflict on merge. Whichever lands second will need a rebase.
Verdict
Approve. The empty-after-sanitization edge case is worth a guard but is not a blocker. The Graph integration patterns are well-structured and consistent with the rest of the extension.
* msteams: add pin/unpin, list-pins, and read message actions Wire up Graph API endpoints for message read, pin, unpin, and list-pins in the MS Teams extension, following the same patterns as edit/delete. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: address PR review comments for pin/unpin/read actions - Handle 204 No Content in postGraphJson (Graph mutations may return empty body) - Strip conversation:/user: prefixes in resolveConversationPath to avoid Graph 404s - Remove dead variable in channel pin branch - Rename unpin param from messageId to pinnedMessageId for semantic clarity - Accept both pinnedMessageId and messageId in unpin action handler for compat Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve user targets + add User-Agent to Graph helpers - Resolve user:<aadId> targets to actual conversation IDs via conversation store before Graph API calls (fixes 404 for DM-context actions) - Add User-Agent header to postGraphJson/deleteGraphRequest for consistency with fetchGraphJson after rebase onto main Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve DM targets to Graph chat IDs + expose pin IDs - Prefer cached graphChatId over Bot Framework conversation IDs for user targets; throw descriptive error when no Graph-compatible ID is available - Add `id` field to list-pins rows so default formatters surface the pinned resource ID needed for the unpin flow Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: add react and reactions (list) message actions * msteams: add search message action via Graph API * msteams: fix search query injection, add ConsistencyLevel header, use manual query string --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
* msteams: add pin/unpin, list-pins, and read message actions Wire up Graph API endpoints for message read, pin, unpin, and list-pins in the MS Teams extension, following the same patterns as edit/delete. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: address PR review comments for pin/unpin/read actions - Handle 204 No Content in postGraphJson (Graph mutations may return empty body) - Strip conversation:/user: prefixes in resolveConversationPath to avoid Graph 404s - Remove dead variable in channel pin branch - Rename unpin param from messageId to pinnedMessageId for semantic clarity - Accept both pinnedMessageId and messageId in unpin action handler for compat Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve user targets + add User-Agent to Graph helpers - Resolve user:<aadId> targets to actual conversation IDs via conversation store before Graph API calls (fixes 404 for DM-context actions) - Add User-Agent header to postGraphJson/deleteGraphRequest for consistency with fetchGraphJson after rebase onto main Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve DM targets to Graph chat IDs + expose pin IDs - Prefer cached graphChatId over Bot Framework conversation IDs for user targets; throw descriptive error when no Graph-compatible ID is available - Add `id` field to list-pins rows so default formatters surface the pinned resource ID needed for the unpin flow Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: add react and reactions (list) message actions * msteams: add search message action via Graph API * msteams: fix search query injection, add ConsistencyLevel header, use manual query string --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
* test: collapse msteams helper suites * test: collapse msteams graph suites * test: collapse msteams state and monitor suites * fix: preserve Telegram forum topic last-route delivery (#53052) (thanks @VACInc) * fix(telegram): preserve forum topic thread in last-route delivery * style(telegram): format last-route update * test(telegram): cover General topic last-route thread * test(telegram): align topic route helper * fix(telegram): skip bound-topic last-route writes --------- Co-authored-by: VACInc <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> * fix(feishu): default requireMention to false for groupPolicy open Groups configured with groupPolicy: open are expected to respond to all messages. Previously, requireMention defaulted to true regardless of groupPolicy, causing image (and other non-text) messages to be silently dropped because they cannot carry @-mentions. Fix: when groupPolicy is 'open' and requireMention is not explicitly configured, resolve it to false instead of true. Users who want mention-required behaviour in open groups can still set requireMention: true explicitly. Adds three regression tests covering the new default, explicit override, and the unchanged allowlist-policy behaviour. Closes #52553 * test(feishu): update config-schema test for removed requireMention default * fix: finish feishu open-group docs and baselines (#54058) (thanks @byungsker) * test: refresh pairing reply assertions for fenced codes (#54058) (thanks @byungsker) * test: accept fenced discord pairing codes (#54058) (thanks @byungsker) * test: allow daemon start hints to grow on linux (#54058) (thanks @byungsker) * fix: preflight invalid telegram photos (#52545) (thanks @hnshah) * fix(telegram): validate photo dimensions before sendPhoto Prevents PHOTO_INVALID_DIMENSIONS errors by checking image dimensions against Telegram Bot API requirements before calling sendPhoto. If dimensions exceed limits (width + height > 10,000px), automatically falls back to sending as document instead of crashing with 400 error. Tested in production (openclaw 2026.3.13) where this error occurred: [telegram] tool reply failed: GrammyError: Call to 'sendPhoto' failed! (400: Bad Request: PHOTO_INVALID_DIMENSIONS) Uses existing sharp dependency to read image metadata. Gracefully degrades if sharp fails (lets Telegram handle validation, backward compatible behavior). Closes: #XXXXX (will reference OpenClaw issue if one exists) * fix(telegram): validate photo aspect ratio * refactor: use shared telegram image metadata * fix: fail closed on telegram image metadata * fix: preflight invalid telegram photos (#52545) (thanks @hnshah) --------- Co-authored-by: Bob Shah <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> * fix: clarify cron best-effort partial delivery status (#42535) (thanks @MoerAI) * fix(cron): track and log bestEffort delivery failures, mark not delivered on partial failure * fix(cron): cache successful results on partial failure to preserve replay idempotency When a best-effort send partially fails, we now still cache the successful delivery results via rememberCompletedDirectCronDelivery. This prevents duplicate sends on same-process replay while still correctly marking the job as not fully delivered. * fix(cron): preserve partial-failure state on replay (#27069) * fix(cron): restore test infrastructure and fix formatting * fix: clarify cron best-effort partial delivery status (#42535) (thanks @MoerAI) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix(cli): route telegram thread create to topic-create * fix: make telegram thread create use topic payload (#54336) (thanks @andyliu) * cron: queue isolated delivery awareness * fix(browser): add Edge LaunchServices bundle IDs for macOS default browser detection macOS registers Edge as 'com.microsoft.edgemac' in LaunchServices, which differs from the CFBundleIdentifier 'com.microsoft.Edge' in the app's own Info.plist. Without recognising the LaunchServices IDs, Edge users who set Edge as their default browser are not detected as having a Chromium browser. Add the four com.microsoft.edgemac* variants to CHROMIUM_BUNDLE_IDS and a corresponding test that mocks the LaunchServices → osascript resolution path for Edge. * fix: add changelog for macOS Edge default browser detection (#48561) (thanks @zoherghadyali) * fix: cover macOS Edge osascript fallback path (#48561) (thanks @zoherghadyali) * fix: fail loud when PTY cursor mode is unknown (#51490) (thanks @liuy) * fix(process): auto-detect PTY cursor key mode for send-keys When a PTY session sends smkx (\x1b[?1h) or rmkx (\x1b[?1l) to switch cursor key mode, send-keys now detects this and encodes cursor keys accordingly. - smkx/rmkx detection in handleStdout before sanitizeBinaryOutput - cursorKeyMode stored in ProcessSession - encodeKeySequence accepts cursorKeyMode parameter - DECCKM_SS3_KEYS for application mode (arrows + home/end) - CSI sequences for normal mode - Modified keys (including alt) always use xterm modifier scheme - Extract detectCursorKeyMode for unit testing - Use lastIndexOf to find last toggle in chunk (later one wins) Fixes #51488 * fix: fail loud when PTY cursor mode is unknown (#51490) (thanks @liuy) * style: format process send-keys guard (#51490) (thanks @liuy) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix: isolate session:patch hook payload (#53880) (thanks @graciegould) * gateway: make session:patch hook typed and non-blocking * gateway(test): add session:patch hook coverage * docs(gateway): clarify session:patch security note * fix: address review feedback on session:patch hook Remove unused createInternalHookEvent import and fix doc example to use inline event.type check matching existing hook examples. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: isolate hook payload to prevent mutation leaking into response Shallow-copy sessionEntry and patch in the session:patch hook event so fire-and-forget handlers cannot mutate objects used by the response path. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: isolate session:patch hook payload (#53880) (thanks @graciegould) --------- Co-authored-by: “graciegould” <“[email protected]”> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> * fix: unify log timestamp offsets (#38904) (thanks @sahilsatralkar) * fix: skip session:patch hook clone without listeners * fix: preserve before_dispatch delivery semantics (#50444) (thanks @gfzhx) * Plugins: add before_dispatch hook * Tests: fix before_dispatch hook mock typing * Rebase: adapt before_dispatch hook to routeReplyRuntime refactor * fix: preserve before_dispatch delivery semantics (#50444) (thanks @gfzhx) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix(whatsapp): avoid eager login tool runtime access * fix: normalize before_dispatch conversation id * test: harden parallels npm update runner * docs: sort changelog by user impact * test: collapse telegram helper suites * test: collapse telegram context and transport suites * docs: add missing changelog items * refactor: align pairing replies, daemon hints, and feishu mention policy * test: collapse telegram button and access suites * test: collapse telegram transport and status suites * fix(whatsapp): unwrap FutureProofMessage (botInvokeMessage) to restore reply-to-bot detection * fix(whatsapp): compare selfLid for reply-to-bot implicit mention in groups * fix(whatsapp): read selfLid from creds.json for reply-to-bot detection * fix(whatsapp): use async fs.promises.readFile for selfLid creds read * refactor(auth): separate profile ids from email metadata * refactor(openai): extract codex auth identity helper * test: isolate voice-call temp stores * build: prepare 2026.3.24-beta.1 * fix: copy openclaw bin before docker install * refactor: unify whatsapp identity handling * test: fix clobbered config snapshot expectation * fix(config): ignore same-base correction publish warnings * test: add Open WebUI docker smoke * fix(media): align outbound media access with fs policy * fix(update): preflight npm target node engine * fix(ci): restore e2e docker cache boundary * test(release): sync llama peer fixture * Discord: log rejected native command deploy failures (#54118) Merged via squash. Prepared head SHA: be250f96204ed6dc755c10d2b9640f7dd49bc70c Co-authored-by: huntharo <[email protected]> Co-authored-by: huntharo <[email protected]> Reviewed-by: @huntharo * fix(runtime): support Node 22.14 installs * test(media): make local roots fixture windows-safe * Tests: isolate security audit home skill resolution (#54473) Merged via squash. Prepared head SHA: 82181e15fbfa47244cf166f57f40646bc35629c6 Co-authored-by: huntharo <[email protected]> Co-authored-by: huntharo <[email protected]> Reviewed-by: @huntharo * docs: prepare 2026.3.24-beta.2 release * fix(sandbox): honor sandbox alsoAllow and explicit re-allows (#54492) * fix(sandbox): honor effective sandbox alsoAllow policy * fix(sandbox): prefer resolved sandbox context policy * fix: honor sandbox alsoAllow policy (#54492) (thanks @ngutman) * fix(feishu): close WebSocket connections on monitor stop (#52844) * fix(feishu): close WebSocket connections on monitor stop/abort Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test(feishu): add WebSocket cleanup tests Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(feishu): close WebSocket connections on monitor stop (#52844) (thanks @schumilin) --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> Co-authored-by: George Zhang <[email protected]> * fix(feishu): use message create_time for inbound timestamps (#52809) * fix(feishu): use message create_time instead of Date.now() for Timestamp field When a message is sent offline and later retried by the Feishu client upon reconnection, Date.now() captures the *delivery* time rather than the *authoring* time. This causes downstream consumers to see a timestamp that can be minutes or hours after the user actually composed the message, leading to incorrect temporal semantics — for example, a "delete this" command may target the wrong resource because the agent believes the instruction was issued much later than it actually was. Replace every Date.now() used for message timestamps with the original create_time from the Feishu event payload (millisecond-epoch string), falling back to Date.now() only when the field is absent. The definition is also hoisted to the top of handleFeishuMessage so that both the pending-history path and the main inbound-payload path share the same authoritative value. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test(feishu): verify Timestamp uses message create_time Add two test cases: 1. When create_time is present, Timestamp must equal the parsed value 2. When create_time is absent, Timestamp falls back to Date.now() Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: revert unrelated formatting change to lifecycle.test.ts This file was inadvertently formatted in a prior commit. Reverting to match main and keep the PR scoped to the Feishu timestamp fix only. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(feishu): use message create_time for inbound timestamps (#52809) (thanks @schumilin) --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> Co-authored-by: George Zhang <[email protected]> * Fix local copied package installs honoring staged project .npmrc (#54543) * fix(release): add plugin-sdk:check-exports to release:check (#54283) * fix(plugins): resolve sdk alias from import.meta.url for external plugins When a plugin is installed outside the openclaw package (e.g. ~/.openclaw/extensions/), resolveLoaderPluginSdkPackageRoot() fails to locate the openclaw root via cwd or argv1 hints, resulting in an empty alias map. Jiti then cannot resolve openclaw/plugin-sdk/* imports and the plugin fails to load with "Cannot find module". Since sdk-alias.ts is always compiled into the openclaw package itself, import.meta.url reliably points inside the installation directory. Add it as an unconditional fallback in resolveLoaderPluginSdkPackageRoot() so external plugins can always resolve the plugin SDK. Fixes: Error: Cannot find module 'openclaw/plugin-sdk/plugin-entry' * fix(plugins): pass loader moduleUrl to resolve sdk alias for external plugins The previous approach of adding import.meta.url as an unconditional fallback inside resolveLoaderPluginSdkPackageRoot() broke test isolation: tests that expected null from untrusted fixtures started finding the real openclaw root. Revert that and instead thread an optional moduleUrl through buildPluginLoaderAliasMap → resolvePluginSdkScopedAliasMap → listPluginSdkExportedSubpaths → resolveLoaderPluginSdkPackageRoot. loader.ts passes its own import.meta.url as the hint, which is always inside the openclaw installation. This guarantees the sdk alias map is built correctly even when argv1 does not resolve to the openclaw root (e.g. single-binary distributions, custom launchers, or Docker images where the binary wrapper is not a standard npm symlink). Tests that call sdk-alias helpers directly without moduleUrl are unaffected and continue to enforce the existing isolation semantics. A new test covers the moduleUrl resolution path explicitly. * fix(plugins): use existing fixture file for moduleUrl hint in test The previous test pointed loaderModuleUrl to dist/plugins/loader.js which is not created by createPluginSdkAliasFixture, causing resolution to fall back to the real openclaw root instead of the fixture root. Use fixture.root/openclaw.mjs (created by the bin+marker fixture) so the moduleUrl hint reliably resolves to the fixture package root. * fix(test): use fixture.root as cwd in external plugin alias test When process.cwd() is mocked to the external plugin dir, the findNearestPluginSdkPackageRoot(process.cwd()) fallback resolves to the real openclaw repo root in the CI test runner, making the test resolve the wrong aliases. Using fixture.root as cwd ensures all resolution paths consistently point to the fixture. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(release): add plugin-sdk:check-exports to release:check plugin-sdk subpath exports (e.g. openclaw/plugin-sdk/plugin-entry, openclaw/plugin-sdk/provider-auth) were missing from the published package.json, causing external plugins to fail at load time with 'Cannot find module openclaw/plugin-sdk/plugin-entry'. Root cause: sync-plugin-sdk-exports.mjs syncs plugin-sdk-entrypoints.json into package.json exports, but this sync was never validated in the release:check pipeline. As a result, any drift between plugin-sdk-entrypoints.json and the published package.json goes undetected until users hit the runtime error. Fix: add plugin-sdk:check-exports to release:check so the CI gate fails loudly if the exports are out of sync before publishing. * fix(test): isolate moduleUrl hint test from process.cwd() fallback Use externalPluginRoot as cwd instead of fixture.root, so only the moduleUrl hint can resolve the openclaw package root. Previously, withCwd(fixture.root) allowed the process.cwd() fallback to also resolve the fixture root, making the moduleUrl path untested. Spotted by greptile-apps review on #54283. * fix(test): use empty string to disable argv1 in moduleUrl hint test Passing undefined for argv1 in buildPluginLoaderAliasMap triggers the STARTUP_ARGV1 default (process.argv[1], the vitest runner binary inside the openclaw repo). resolveTrustedOpenClawRootFromArgvHint then resolves to the real openclaw root before the moduleUrl hint is checked, making the test resolve wrong aliases. Pass "" instead: falsy so the hint is skipped, but does not trigger the default parameter value. Only the moduleUrl can bridge the gap. Made-with: Cursor * fix(plugins): thread moduleUrl through SDK alias resolution for external plugins (#54283) Thanks @xieyongliang --------- Co-authored-by: bojsun <[email protected]> Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Jerry <[email protected]> Co-authored-by: yongliang.xie <[email protected]> Co-authored-by: George Zhang <[email protected]> * feat(minimax): add image generation provider and trim model catalog to M2.7 (#54487) * feat(minimax): add image generation and TTS providers, trim TUI model list Register MiniMax image-01 and speech-2.8 models as plugin providers for the image_generate and TTS tools. Both resolve CN/global base URLs from the configured model endpoint origin. - Image generation: base64 response, aspect-ratio support, image-to-image via subject_reference, registered for minimax and minimax-portal - TTS: speech-2.8-turbo (default) and speech-2.8-hd, hex-encoded audio, voice listing via get_voice API, telephony PCM support - Add MiniMax to TTS auto-detection cascade (after ElevenLabs, before Microsoft) and TTS config section - Remove MiniMax-VL-01, M2, M2.1, M2.5 and variants from TUI picker; keep M2.7 and M2.7-highspeed only (backend routing unchanged) * feat(minimax): trim legacy model catalog to M2.7 only Cherry-picked from temp/feat/minimax-trim-legacy-models (949ed28). Removes MiniMax-VL-01, M2, M2.1, M2.5 and variants from the model catalog, model order, modern model matchers, OAuth config, docs, and tests. Keeps only M2.7 and M2.7-highspeed. Conflicts resolved: - provider-catalog.ts: removed MINIMAX_TUI_MODELS filter (no longer needed since source array is now M2.7-only) - index.ts: kept image generation + speech provider registrations (added by this branch), moved media understanding registrations earlier (as intended by the cherry-picked commit) * fix(minimax): update discovery contract test to reflect M2.7-only catalog Cherry-picked from temp/feat/minimax-trim-legacy-models (2c750cb). * feat(minimax): add web search provider and register in plugin entry * fix(minimax): resolve OAuth credentials for TTS speech provider * MiniMax: remove web search and TTS providers * fix(minimax): throw on empty images array after generation failure * feat(minimax): add image generation provider and trim catalog to M2.7 (#54487) (thanks @liyuan97) --------- Co-authored-by: tars90percent <[email protected]> Co-authored-by: George Zhang <[email protected]> * build: prepare 2026.3.24 release * docs: format changelog for release * fix: reconcile session compaction count after late compaction success (#45493) Merged via squash. Prepared head SHA: d0715a5555791dd44a406d4732843454a3e9619e Co-authored-by: jackal092927 <[email protected]> Co-authored-by: jalehman <[email protected]> Reviewed-by: @jalehman * build: update appcast for 2026.3.24 * Telegram: ignore self-authored DM message updates (#54530) Merged via squash. Prepared head SHA: c1c8a851682944c49259afe1741d6d976016b08b Co-authored-by: huntharo <[email protected]> Co-authored-by: huntharo <[email protected]> Reviewed-by: @huntharo * test: keep vitest on forks only * fix: stop leaking reply tags in iMessage outbound text (#39512) (thanks @mvanhorn) * fix: stop leaking reply tags in iMessage outbound text (#39512) (thanks @mvanhorn) * fix: preserve iMessage outbound whitespace without directive tags (#39512) (thanks @mvanhorn) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix: mid-turn 429 rate limit silent no-reply and context engine registration failure (#50930) Merged via squash. Prepared head SHA: eea7800df31f30caabfec2aefe8f20008365a8e8 Co-authored-by: infichen <[email protected]> Co-authored-by: jalehman <[email protected]> Reviewed-by: @jalehman * plugin-runtime: expose runHeartbeatOnce in system API (#40299) * plugin-runtime: expose runHeartbeatOnce in system API Plugins that enqueue system events and need the agent to deliver responses to the originating channel currently have no way to override the default `heartbeat.target: "none"` behaviour. Expose `runHeartbeatOnce` in the plugin runtime `system` namespace so plugins can trigger a single heartbeat cycle with an explicit `heartbeat: { target: "last" }` override — the same pattern the cron service already uses (see #28508). Changes: - Add `RunHeartbeatOnceOptions` type and `runHeartbeatOnce` to `PluginRuntimeCore.system` (types-core.ts) - Wire the function through a thin wrapper in runtime-system.ts - Update the test-utils plugin-runtime mock Made-with: Cursor * feat(plugins): expose runHeartbeatOnce in system API (#40299) (thanks @loveyana) --------- Co-authored-by: George Zhang <[email protected]> * fix(compaction): surface safeguard cancel reasons and clarify /compact skips (#51072) Merged via squash. Prepared head SHA: f1dbef044384fbd79ca5ef3616ad37fb03b05fae Co-authored-by: afurm <[email protected]> Co-authored-by: jalehman <[email protected]> Reviewed-by: @jalehman * Fix/telegram writeback admin scope gate (#54561) * fix(telegram): require operator.admin for legacy target writeback persistence * Address claude feedback * Update extensions/telegram/src/target-writeback.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Remove stray brace * Add updated docs * Add missing test file, address codex concerns * Fix test formatting error * Address comments, fix tests --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test: fix windows tmp root assertions * fix: per-model cooldown scope, stepped backoff, and user-facing rate-limit message (#49834) Merged via squash. Prepared head SHA: 7c488c070c0cafb5a4b53c598d8ccd38c418b67c Co-authored-by: kiranvk-2011 <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * fix: reject path traversal and home-dir patterns in media parse layer (#54642) * fix: reject path traversal and home-dir patterns in media parse layer * Update parse tests * Block reset-profile on lower-privilege browser request surfaces (#54618) * Block reset-profile on lower-privilege browser request surfaces * add missing tests * Fix tests * Test fix * fix: trigger compaction on LLM timeout with high context usage (#46417) Merged via squash. Prepared head SHA: 619bc4c1fa1db3829ff3aa78ffcab8e4201379b5 Co-authored-by: joeykrug <[email protected]> Co-authored-by: jalehman <[email protected]> Reviewed-by: @jalehman * fix(talk-voice): enforce operator.admin scope on /voice set config writes (#54461) * fix(talk-voice): enforce operator.admin scope on /voice set config writes * fix(talk-voice): align scope guard with phone-control pattern Use optional chaining (?.) instead of Array.isArray so webchat callers with undefined scopes are rejected, matching the established pattern in phone-control. Add test for webchat-with-no-scopes case. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * fix: apply host-env blocklist to auth-profile env refs in daemon install (#54627) * fix: apply host-env blocklist to auth-profile env refs in daemon install Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * ci: retrigger checks --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * OpenShell: exclude hooks/ from mirror sync (#54657) * OpenShell: exclude hooks/ from mirror sync * OpenShell: make excludeDirs case-insensitive for cross-platform safety * Trigger preflight compaction from transcript estimates when usage is stale (#49479) Merged via squash. Prepared head SHA: 8d214b708b433702aba21b86ad19b6e2721c3ee0 Co-authored-by: jared596 <[email protected]> Co-authored-by: jalehman <[email protected]> Reviewed-by: @jalehman * refactor(sandbox): remove tool policy facade (#54684) * refactor(sandbox): remove tool policy facade * fix(sandbox): harden blocked-tool guidance * fix(sandbox): avoid control-char guidance leaks * fix: harden sandbox blocked-tool guidance (#54684) (thanks @ngutman) * fix(ci): restore main green * Filter untrusted CWD .env entries before OpenClaw startup (#54631) * Filter untrusted CWD .env entries before OpenClaw startup * Add missing test file * Fix missing and updated files * Address feedback * Feedback updates * Feedback update * Add test coverage * Unit test fix * fix: enforce localRoots sandbox on Feishu docx upload file reads (#54693) * fix: enforce localRoots sandbox on Feishu docx upload file reads * Formatting fixes * Update tests * Feedback updates * test: introduce planner-backed test runner, stabilize local builds (#54650) * test: stabilize ci and local vitest workers * test: introduce planner-backed test runner * test: address planner review follow-ups * test: derive planner budgets from host capabilities * test: restore planner filter helper import * test: align planner explain output with execution * test: keep low profile as serial alias * test: restrict explicit planner file targets * test: clean planner exits and pnpm launch * test: tighten wrapper flag validation * ci: gate heavy fanout on check * test: key shard assignments by unit identity * ci(bun): shard vitest lanes further * test: restore ci overlap and stabilize planner tests * test: relax planner output worker assertions * test: reset plugin runtime state in optional tools suite * ci: split macos node and swift jobs * test: honor no-isolate top-level concurrency budgets * ci: fix macos swift format lint * test: cap max-profile top-level concurrency * ci: shard macos node checks * ci: use four macos node shards * test: normalize explain targets before classification * feat(cli): add json schema to cli tool (#54523) Merged via squash. Prepared head SHA: 39c15ee70d04a1721ebafde327b3b95355653fac Co-authored-by: kvokka <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * fix(schema): tools.web.fetch.maxResponseBytes #53397 (#53401) Merged via squash. Prepared head SHA: 5d10a98bdb1f7fc3065adf438c4d572c3d61f8ce Co-authored-by: erhhung <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * fix: make buttons schema optional in message tool (#54418) Merged via squash. Prepared head SHA: 0805c095e930d669ba1b3aedc09baec80882ce45 Co-authored-by: adzendo <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * fix(gateway): block silent reconnect scope-upgrade escalation (#54694) * fix(gateway): block silent reconnect scope-upgrade escalation * formatting updateas * Resolve feedback * formatting fixes * Update src/gateway/server.silent-scope-upgrade-reconnect.poc.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Feedback updates * fix unit test * Feedback update * Review feedback update * More Greptile nit fixes --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: allow msteams feedback and welcome config keys (#54679) Merged via squash. Prepared head SHA: f56a15ddeaeb5369fd8feac09bb89d3d600d6ff4 Co-authored-by: gumclaw <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * fix(mattermost): thread resolved cfg through reply delivery send calls (#48347) Merged via squash. Prepared head SHA: 7ca468e365087c802be32675696080a8dd11fae2 Co-authored-by: mathiasnagler <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm * feat: add video generation core infrastructure and extend image generation parameters (#53681) * feat: add video generation core infrastructure and extend image generation parameters Add full video generation capability to OpenClaw core: - New `video_generate` agent tool with support for prompt, duration, aspect ratio, resolution, seed, watermark, I2V (first/last frame), camerafixed, and draft mode - New `VideoGenerationProvider` plugin SDK type and `registerVideoGenerationProvider` API - New `src/video-generation/` module (types, runtime with fallback, provider registry) - New `openclaw/plugin-sdk/video-generation` export for external plugins - 200MB max file size for generated videos (vs default 5MB for images) Extend image generation with additional parameters: - `seed`, `watermark`, `guidanceScale`, `optimizePrompt`, `providerOptions` - New `readBooleanParam()` helper in tool common utilities Update plugin registry, contracts, and all test mocks to include `videoGenerationProviders` and `videoGenerationProviderIds`. Made-with: Cursor * fix: validate aspect ratio against target provider when model override is set * cleanup: remove redundant ?? undefined from video/image generate tools * chore: regenerate plugin SDK API baseline after video generation additions --------- Co-authored-by: yongliang.xie <[email protected]> * fix: deliver verbose tool summaries in Telegram forum topics (#43236) (thanks @frankbuild) * fix(auto-reply): deliver verbose tool summaries in Telegram forum topics Forum topics have ChatType 'group' but are threaded conversations where verbose tool output should be delivered (same as DMs). The shouldSendToolSummaries gate now checks IsForum to allow tool summaries in forum topic sessions. Fixes #43206 * test: add sendToolResult count assertion per review feedback * fix: add changelog for forum topic verbose tool summaries (#43236) (thanks @frankbuild) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * docs: add WeChat channel via official Tencent iLink Bot plugin (#52131) (thanks @MonkeyLeeT) * docs: add WeChat channel via official Tencent iLink Bot plugin Add WeChat to the README channel lists and setup section. Uses the official Tencent-published plugin @tencent-weixin/openclaw-weixin which connects via the iLink Bot API (QR code login, long-poll). Requires WeChat 8.0.70+ with the ClawBot plugin enabled; the plugin is being rolled out gradually by Tencent. Covers: setup steps, capabilities (DM-only, media up to 100 MB, multi-account, pairing authorization, typing indicators, config path), and the context token restart caveat. * docs: update WeChat plugin install for v2.0 compatibility - Add version compatibility note (v2.x requires OpenClaw >= 2026.3.22, @legacy tag for older hosts) - Add plugins.allow step (required since plugins.allow was introduced) * docs: drop manual plugins.allow/enable steps (handled by plugins install) * docs: fix multi-account instruction to require explicit --account id * docs: trim WeChat section to match neighboring channels, fix pairing link * docs: sync WeChat channel docs --------- Co-authored-by: Ayaan Zaidi <[email protected]> * ci: collapse preflight manifest routing (#54773) * ci: collapse preflight manifest routing * ci: fix preflight workflow outputs * ci: restore compat workflow tasks * ci: match macos shards to windows * ci: collapse macos swift jobs * ci: skip empty submodule setup * ci: drop submodule setup from node env * fix(whatsapp): clarify allowFrom policy error (#54850) * fix: resolve telegram token fallback for binding-created accounts (#54362) (thanks @openperf) * fix(telegram): resolve channel-level token fallthrough for binding-created accountIds Fixes #53876 * fix(telegram): align isConfigured with resolveTelegramToken multi-bot guard * fix(telegram): use normalized account lookup and require available token * fix: tighten systemd duplicate gateway detection (#45328) (thanks @gregretkowski) * daemon: tighten systemd duplicate gateway detection (#15849) * fix three issues from PR review * fix windows unit tests due to posix/windows path differences * ensure line continuations are handled in systemd units * fix misleading test name * attempt fix windows test due to fs path separator * fix system_dir separator, fix platform side-effect * change approach for mocking systemd filesystem test * normalize systemd paths to linux style * revert to vers that didnt impact win32 tests * back out all systemd inspect tests * change test approach to avoid other tests issues * fix: tighten systemd duplicate gateway detection (#45328) (thanks @gregretkowski) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix: use provider-aware context window lookup (#54796) (thanks @neeravmakwana) * fix(status): use provider-aware context window lookup * test(status): cover provider-aware context lookup * fix: use provider-aware context window lookup (#54796) (thanks @neeravmakwana) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix: restore CLI message transcript mirroring (#54187) (thanks @KevInTheCloud5617) * fix: pass agentId in CLI message command to enable session transcript writes The CLI `openclaw message send` command was not passing `agentId` to `runMessageAction()`, causing the outbound session route resolution to be skipped (it's gated on `agentId && !dryRun`). Without a route, the `mirror` object is never constructed, and `appendAssistantMessageToSessionTranscript()` is never called. This fix resolves the agent ID from the config (defaulting to "main") and passes it through, enabling transcript mirroring for all channels when using the CLI. Closes #54186 * fix: format message.ts with oxfmt * fix: use resolveDefaultAgentId instead of cfg.agent * fix: restore CLI message transcript mirroring (#54187) (thanks @KevInTheCloud5617) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix: restore Kimi Code under Moonshot setup (#54619) (thanks @sparkyrider) * Onboarding: restore Kimi Code under Moonshot setup * Update extensions/kimi-coding/index.ts Fix naming convention in metadata Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: auto-enable configured channel plugins in routed CLI commands (#54809) (thanks @neeravmakwana) * CLI: auto-enable configured channel plugins in routed commands * fix: auto-enable configured channel plugins in routed CLI commands (#54809) (thanks @neeravmakwana) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix: restore image-tool generic provider fallback (#54858) (thanks @MonkeyLeeT) * Image tool: restore generic provider fallback * Image tool: cover multi-image generic fallback * test: tighten minimax-portal image fallback coverage * fix: restore image-tool generic provider fallback (#54858) (thanks @MonkeyLeeT) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix: support OpenAI Codex media understanding (#54829) (thanks @neeravmakwana) * OpenAI: register Codex media understanding provider * fix: route codex image prompts through system instructions * fix: add changelog for codex image tool fix (#54829) (thanks @neeravmakwana) * fix: remove any from provider registration tests (#54829) (thanks @neeravmakwana) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix(whatsapp): drop fromMe echoes in self-chat DMs using outbound ID tracking (#54570) Merged via squash. Prepared head SHA: dad53caf3974e2e90a358d0561a0b0ee9bd924f2 Co-authored-by: joelnishanth <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr * Revert "feat: add video generation core infrastructure and extend image generation parameters (#53681)" (#54943) This reverts commit 4cb8dde894e3a85ce81b770869d7bd86e6b6e7a1. * msteams: fetch thread history via Graph API for channel replies (#51643) * msteams: fetch thread history via Graph API for channel replies * msteams: address PR #51643 review feedback - Wrap resolveTeamGroupId Graph call in try/catch, fall back to raw conversationTeamId when Team.ReadBasic.All permission is missing - Remove dead fetchChatMessages function (exported but never called) - Add JSDoc documenting oldest-50-replies Graph API limitation Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: address thread history PR review comments * msteams: only cache team group IDs on successful Graph lookup Avoid caching raw conversationTeamId as a Graph team GUID when the /teams/{id} lookup fails — the raw ID may be a Bot Framework conversation key, not a valid GUID, causing silent thread-history failures for the entire cache TTL. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * msteams: add search message action (#54832) * msteams: add pin/unpin, list-pins, and read message actions Wire up Graph API endpoints for message read, pin, unpin, and list-pins in the MS Teams extension, following the same patterns as edit/delete. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: address PR review comments for pin/unpin/read actions - Handle 204 No Content in postGraphJson (Graph mutations may return empty body) - Strip conversation:/user: prefixes in resolveConversationPath to avoid Graph 404s - Remove dead variable in channel pin branch - Rename unpin param from messageId to pinnedMessageId for semantic clarity - Accept both pinnedMessageId and messageId in unpin action handler for compat Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve user targets + add User-Agent to Graph helpers - Resolve user:<aadId> targets to actual conversation IDs via conversation store before Graph API calls (fixes 404 for DM-context actions) - Add User-Agent header to postGraphJson/deleteGraphRequest for consistency with fetchGraphJson after rebase onto main Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve DM targets to Graph chat IDs + expose pin IDs - Prefer cached graphChatId over Bot Framework conversation IDs for user targets; throw descriptive error when no Graph-compatible ID is available - Add `id` field to list-pins rows so default formatters surface the pinned resource ID needed for the unpin flow Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: add react and reactions (list) message actions * msteams: add search message action via Graph API * msteams: fix search query injection, add ConsistencyLevel header, use manual query string --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * fix(plugins): skip allowlist warning for config paths * feat: Add Microsoft Foundry provider with Entra ID authentication (#51973) * Microsoft Foundry: add native provider * Microsoft Foundry: tighten review fixes * Microsoft Foundry: enable by default * Microsoft Foundry: stabilize API routing * fix: restore inbound image embedding for CLI routed BlueBubbles turns (#51373) * fix(cli): hydrate prompt image refs for inbound media * Agents: harden CLI prompt image hydration (#51373) * test: fix CLI prompt image hydration helper mocks * fix: route codex responses over websocket and preserve tool warnings (#53702) (thanks @Nanako0129) * fix: route codex responses over websocket and suppress gated core tool warnings * fix: rebase codex websocket patch onto main * fix: preserve explicit alsoAllow warnings (#53702) (thanks @Nanako0129) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints (#54407) * Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints * refine docs * Docs: fix broken link in providers/index.md after modelstudio rename * Docs: add redirect from /providers/modelstudio to /providers/qwen_modelstudio * Docs: adjust the order in index.md * docs: rename modelstudio to qwen_modelstudio, add Standard API endpoints (#54407) (thanks @wenmengzhou) --------- Co-authored-by: George Zhang <[email protected]> * fix: wire microsoft foundry into contract registry * docs: refresh config baseline for microsoft foundry * fix: add slack upload-file action (#54987) (thanks @kevinlin-openai) * feat(slack): add upload-file action Co-authored-by: Codex <[email protected]> * fix(slack): guard upload-file routing Co-authored-by: Codex <[email protected]> * fix(slack): tighten upload-file validation --------- Co-authored-by: kevinlin-openai <[email protected]> Co-authored-by: Codex <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> * build: update plugin sdk api baseline * Remove Qwen OAuth integration (qwen-portal-auth) (#52709) * Remove Qwen OAuth integration (qwen-portal-auth) Qwen OAuth via portal.qwen.ai is being deprecated by the Qwen team due to traffic impact on their primary Qwen Code user base. Users should migrate to the officially supported Model Studio (Alibaba Cloud Coding Plan) provider instead. Ref: https://github.com/openclaw/openclaw/issues/49557 - Delete extensions/qwen-portal-auth/ plugin entirely - Remove qwen-portal from onboarding auth choices, provider aliases, auto-enable list, bundled plugin defaults, and pricing cache - Remove Qwen CLI credential sync (external-cli-sync, cli-credentials) - Remove QWEN_OAUTH_MARKER from model auth markers - Update docs/providers/qwen.md to redirect to Model Studio - Update model-providers docs (EN + zh-CN) to remove Qwen OAuth section - Regenerate config and plugin-sdk baselines - Update all affected tests Co-authored-by: Qwen-Coder <[email protected]> * Clean up residual qwen-portal references after OAuth removal * Add migration hint for deprecated qwen-portal OAuth provider * fix: finish qwen oauth removal follow-up --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Frank Yang <[email protected]> * fix(extensions): route fetch calls through fetchWithSsrFGuard (#53929) * fix(extensions): route fetch calls through fetchWithSsrFGuard Replace raw fetch() with fetchWithSsrFGuard in BlueBubbles, Mattermost, Nextcloud Talk, and Thread Ownership extensions so outbound requests go through the shared DNS-pinning and network-policy layer. BlueBubbles: thread allowPrivateNetwork from account config through all fetch call sites (send, chat, reactions, history, probe, attachments, multipart). Add _setFetchGuardForTesting hook for test overrides. Mattermost: add guardedFetchImpl wrapper in createMattermostClient that buffers the response body before releasing the dispatcher. Handle null-body status codes (204/304). Nextcloud Talk: wrap both sendMessage and sendReaction with fetchWithSsrFGuard and try/finally release. Thread Ownership: add fetchWithSsrFGuard and ssrfPolicyFromAllowPrivateNetwork to the plugin SDK surface; use allowPrivateNetwork:true for the Docker-internal forwarder. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(extensions): improve null-body handling and test harness cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(bluebubbles): default to strict SSRF policy when allowPrivateNetwork is unset Callers that omit allowPrivateNetwork previously got undefined policy, which caused blueBubblesFetchWithTimeout to fall through to raw fetch and bypass the SSRF guard entirely. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(bluebubbles): thread allowPrivateNetwork through action and monitor call sites Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(mattermost,nextcloud-talk): add allowPrivateNetwork config for self-hosted/LAN deployments * fix: regenerate config docs baseline for new allowPrivateNetwork fields --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * BlueBubbles: enrich group participants with local Contacts names (#54984) * BlueBubbles: enrich group participants with Contacts names * BlueBubbles: gate contact enrichment behind opt in config * chore: add lockfile entry for `extensions/microsoft-foundry` * fix(discord): force fresh gateway reconnects (#54697) * fix(discord): force fresh gateway reconnects * fix(discord): harden forced reconnect teardown * fix(discord): retry after socket drain timeouts * fix(discord): guard forced socket teardown * fix(discord): stop cleanly during reconnect drain * Feishu: validate webhook signatures before parsing (#55083) * Feishu: validate webhook signatures before parsing * Scripts: allow Feishu raw body guard callsite * fix(agents): enforce session_status guard after sessionId resolution (#55105) * fix(agents): enforce visibility guard after sessionId resolution in session_status When a sessionId (rather than an explicit agent key) is passed to the session_status tool, the sessionId resolution block rewrites requestedKeyRaw to an explicit "agent:..." key. The subsequent visibility guard check at line 375 tested `!requestedKeyRaw.startsWith("agent:")`, which was now always false after resolution — skipping the visibility check entirely. This meant a sandboxed agent could bypass visibility restrictions by providing a sessionId instead of an explicit session key. Fix: use the original `isExplicitAgentKey` flag (captured before resolution) instead of re-checking the dynamic requestedKeyRaw. This ensures the visibility guard runs for sessionId inputs while still skipping the redundant check for inputs that were already validated at the earlier explicit-key check (lines 281-286). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test: cover session status sessionId guard * test: align parent sessionId guard coverage --------- Co-authored-by: Kevin Sheng <[email protected]> Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * Telegram: enforce DM auth for callbacks (#55112) * fix(msteams): align feedback invoke authorization (#55108) * msteams: align feedback invoke authorization * msteams: fix feedback allowlist regressions * msteams: tighten feedback group authorization * fix(bluebubbles): auto-allow private network for local serverUrl and add allowPrivateNetwork to channel schema * Matrix: gate verification notices on DM access (#55122) * fix: surface provider-specific rate limit error message (#54433) (#54512) Merged via squash. Prepared head SHA: 755cff833c1f6aca06ab2c3e1f802f35e7c4d553 Co-authored-by: bugkill3r <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * test: fix bluebubbles attachment ssrf expectations * docs: add beta release testing guidance * fix(bluebubbles): enable group participant enrichment by default, add fallback fetch and handle field aliases * fix: prefer freshest duplicate row promotion * fix: replace stale canonical duplicate rows * docs: sync config baseline * build: prepare 2026.3.25 unreleased * ACP: sanitize terminal tool titles (#55137) * ACP: sanitize terminal tool titles Co-authored-by: nexrin <[email protected]> * Config: refresh config baseline and stabilize restart pid test --------- Co-authored-by: nexrin <[email protected]> * Google Chat: require stable group ids (#55131) * Google Chat: require stable group ids * Google Chat: fail closed on deprecated room keys * Feishu: reject legacy raw card command payloads (#55130) * Feishu: reject legacy raw card callbacks * Feishu: cover legacy text card payloads * Docs: refresh config baseline * CI: refresh PR checks * Feishu: limit legacy card guard scope * fix(bluebubbles): refactor sendMessageBlueBubbles to use resolveBlueBubblesServerAccount and enhance private network handling in tests * test(memory): initialize providers in lazy manager tests * test: fix portable stderr capture and env leakage (#55184) * docs: add beta blocker contributor guidance (#55199) * docs: add beta blocker contributor guidance * fix: tighten beta blocker labeling and flaky config test * docs: update PR template review guidance * refactor: dedupe msteams graph actions * fix(bluebubbles): throttle webhook auth guesses (#55133) * fix(bluebubbles): throttle webhook auth guesses * test(bluebubbles): isolate attachment ssrf config * test(bluebubbles): hoist attachment mocks * docs: refresh bluebubbles config baseline * fix(bluebubbles): trust proxied webhook client IPs * fix(bluebubbles): honor trusted proxy webhook IPs * fix(bluebubbles): honor real-ip fallback for webhooks * refactor: share web media loader * fix: avoid duplicate ACP Telegram finals (#55173) * fix: avoid duplicate final ACP text on telegram * fix: keep ACP final fallback for non-telegram blocks * fix: count telegram ACP block replies as success * fix: recover ACP final fallback after block failures * fix: settle telegram ACP block delivery before fallback * test: isolate ACP dispatch mocks under shared workers * fix: prefer telegram provider for ACP visibility * refactor: share matrix and telegram dedupe helpers * fix: prefer freshest duplicate store matches * feat: pluginize cli inference backends * test: dedupe foundry auth fixtures * fix(whatsapp): unwrap quoted wrapper messages * test: share pi compaction fixtures * test: share redact and approval fixtures * fix: preserve metadata on voice session touches * test: dedupe web search provider fixtures * synology-chat: throttle webhook token guesses (#55141) * synology-chat: throttle webhook token guesses * synology-chat: keep valid webhook traffic within configured limits * docs: refresh generated config baseline * synology-chat: enforce lockout after repeated token failures * fix: preserve reset ownership metadata * test: improve test runner help text (#55227) * test: improve test runner help text * test: print extension help to stdout * test: leave extension help passthrough alone * test: parse timing update flags in one pass * perf: speed up channel test runs * test: share msteams monitor and pi runner fixtures * fix: preserve reset spawn depth * test: dedupe config compatibility fixtures * ci: make docker release tag-driven * fix: preserve reset elevated level * fix: preserve reset spawn context * test: share subagent and policy test fixtures * perf: enable local channel planner parallelism on node 25 * test: dedupe remaining agent test seams * telegram: throttle repeated webhook auth guesses (#55142) * telegram: throttle repeated webhook auth guesses * telegram: use per-listener webhook rate limits * config: stabilize doc baseline ordering * test: share matrix migration fixtures * test: share gateway authz and watchdog fixtures * test: share ui reconnect and storage helpers * perf: overlap isolated channel runs with shared lane * fix: preserve reset session behavior config * fix: preserve reset channel identity * fix: preserve reset acp session metadata * fix: preserve reset cli session linkage * refactor: share auto-reply reply helpers * test: add docker cli-backend smoke * docs: note guest openclaw shim in parallels skill * test: share plugin auth and ui storage fixtures * test: dedupe matrix setup seams * test: dedupe ui chat seams * style: normalize ui slash executor formatting * fix(ci): refresh plugin sdk api baseline * fix: keep plugin HTTP runtime scopes least-privileged (#55284) * Gateway: require caller scope for subagent session deletion (#55281) * gateway: require pairing for backend scope upgrades (#55286) * test: dedupe secrets and guardrail fixtures * bluebubbles: honor reaction mention gating (#55283) * Gateway: align HTTP session history scopes (#55285) * Gateway: require scopes for HTTP session history * Gateway: cover missing HTTP history scope header * refactor: dedupe gateway and binding helpers * test: dedupe extension channel fixtures * Gateway: require requester ownership for HTTP session kills (#55308) * test: share cli and channel setup fixtures * test: share auto-reply typing helpers * refactor: share plugin setup helpers * ci: optimize windows test shard fanout (#55261) * ci: reduce windows test shard fanout * ci: tighten windows shard target * ci: back off windows shard target * ci: restore windows shard cap * fix: include dashboard children in owner filters * fix: expose parent session keys in sessions list * fix: expose spawned session owners in sessions list * fix: keep spawned session owners in live events * refactor: share browser and sandbox helpers * refactor: share discord outbound session routing * refactor: dedupe gateway session resolve visibility * refactor: share slack and telegram action helpers * fix: auto-load bundled plugin capabilities from config refs * test: share cli and doctor test helpers * fix(ci): repair discord regression tests * test(gateway): strip MiniMax live scaffolding * test: share cli command and discord test helpers * test: dedupe extension channel fixtures * fix(ci): repair discord message handler tests * test: reduce remaining clone seams * perf: speed up test parallelism * telegram: rebuild transport after stalled polling cycles * test: share discord monitor fixtures * fix: preserve cli sessions across model changes * test: split cli agent command coverage * test: eliminate remaining clone seams * refactor: dedupe cli runner session reuse * fix(ci): repair discord harness regressions * fix(ci): clean up discord harness types * fix(agents): classify "Failed to extract accountId from token" as auth error for failover (#27055) (#55206) Co-authored-by: Lyle Hopkins <[email protected]> * test: dedupe discord provider proxy overrides * fix(ci): restore discord provider test seams * fix(ci): format discord provider follow-up * refactor: split telegram polling and sdk surfaces * fix: export shared channel action enum helpers * fix: backfill claude cli chat history * fix: skip cli backends in models auth warnings * test: dedupe telegram polling session harness * fix(ci): repair discord and telegram follow-ups * refactor: clean plugin capability boundaries * refactor: share speech normalization helpers * perf: speed up shared extension test batches * refactor: simplify bundled plugin contracts * refactor: move memory flush ownership into memory plugin * refactor: move memory tooling into memory-core extension * refactor: add memory-core extension sources * fix: unify claude cli imported tool messages * test: share planner and sandbox test helpers * refactor: split memory-core plugin helpers * fix: regenerate pnpm-lock.yaml after upstream merge (workspace dep added) * fix: resolve post-absorb CI failures After absorbing 140 upstream commits, several botster-specific files needed updating to match upstream SDK changes: 1. extensions/zulip/index.ts — import order reordered by oxfmt (types import after value import, as required by formatter) 2. scripts/lib/plugin-sdk-entrypoints.json + package.json — './plugin-sdk/zulip' subpath export missing from upstream's entrypoints list (our addition). Added 'zulip' to the canonical entrypoints JSON so sync-exports keeps it. 3. src/seks/spine-exec-intercept.test.ts — - TextContent|ImageContent union: .text access now requires type narrowing (cast to { type: 'text'; text: string }) - AgentTool now requires execute: test case for missing-execute path casts to any (intentionally testing fallback behavior) 4. docs/.generated/* — regenerated config-baseline and plugin-sdk-api-baseline after upstream config schema + plugin SDK surface changes. * fix: lint and docs issues from post-absorb CI - src/seks/spine-exec-intercept.test.ts: replace 'as any' with 'as Parameters<typeof createSpineExecTool>[0]' to satisfy oxlint no-explicit-any rule while still testing the no-execute fallback path - docs/providers/anthropic.md: rename duplicate '### Config snippet' heading (added by upstream in ab4de18) to '### Config snippet (claude-cli)' to satisfy markdownlint MD024 --------- Co-authored-by: Peter Steinberger <[email protected]> Co-authored-by: VACInc <[email protected]> Co-authored-by: VACInc <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> Co-authored-by: lbo728 <[email protected]> Co-authored-by: hnshah <[email protected]> Co-authored-by: Bob Shah <[email protected]> Co-authored-by: ToToKr <[email protected]> Co-authored-by: Andy <[email protected]> Co-authored-by: Sparkyrider <[email protected]> Co-authored-by: Zoher Ghadyali <[email protected]> Co-authored-by: Liu Yuan <[email protected]> Co-authored-by: Gracie Gould <[email protected]> Co-authored-by: “graciegould” <“[email protected]”> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: ZhangXuan <[email protected]> Co-authored-by: khhjoe <[email protected]> Co-authored-by: Harold Hunt <[email protected]> Co-authored-by: huntharo <[email protected]> Co-authored-by: Nimrod Gutman <[email protected]> Co-authored-by: Lin Z <[email protected]> Co-authored-by: George Zhang <[email protected]> Co-authored-by: Devin Robison <[email protected]> Co-authored-by: xieyongliang <[email protected]> Co-authored-by: bojsun <[email protected]> Co-authored-by: Jerry <[email protected]> Co-authored-by: yongliang.xie <[email protected]> Co-authored-by: liyuan97 <[email protected]> Co-authored-by: tars90percent <[email protected]> Co-authored-by: Jackal Xin <[email protected]> Co-authored-by: jackal092927 <[email protected]> Co-authored-by: jalehman <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> Co-authored-by: Matt Van Horn <[email protected]> Co-authored-by: chenxingzhen <[email protected]> Co-authored-by: infichen <[email protected]> Co-authored-by: M1a0 <[email protected]> Co-authored-by: Andrii Furmanets <[email protected]> Co-authored-by: afurm <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: kiranvk2011 <[email protected]> Co-authored-by: altaywtf <[email protected]> Co-authored-by: Joseph Krug <[email protected]> Co-authored-by: joeykrug <[email protected]> Co-authored-by: Jacob Tomlinson <[email protected]> Co-authored-by: Jared <[email protected]> Co-authored-by: Mikhail Beliakov <[email protected]> Co-authored-by: kvokka <[email protected]> Co-authored-by: Erhhung Yuan <[email protected]> Co-authored-by: erhhung <[email protected]> Co-authored-by: adzendo <[email protected]> Co-authored-by: adzendo <[email protected]> Co-authored-by: gumclaw <[email protected]> Co-authored-by: gumclaw <[email protected]> Co-authored-by: Mathias Nagler <[email protected]> Co-authored-by: mathiasnagler <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Co-authored-by: Frank the Builder <[email protected]> Co-authored-by: Ted Li <[email protected]> Co-authored-by: Marcus Castro <[email protected]> Co-authored-by: wangchunyue <[email protected]> Co-authored-by: Greg Retkowski <[email protected]> Co-authored-by: Neerav Makwana <[email protected]> Co-authored-by: Kevin Boyle <[email protected]> Co-authored-by: OfflynAI <[email protected]> Co-authored-by: sudie-codes <[email protected]> Co-authored-by: MetaX e|acc <[email protected]> Co-authored-by: Tyler Yust <[email protected]> Co-authored-by: Nyanako <[email protected]> Co-authored-by: wenmeng zhou <[email protected]> Co-authored-by: kevinlin-openai <[email protected]> Co-authored-by: kevinlin-openai <[email protected]> Co-authored-by: Codex <[email protected]> Co-authored-by: pomelo <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Frank Yang <[email protected]> Co-authored-by: Altay <[email protected]> Co-authored-by: Kevin Sheng <[email protected]> Co-authored-by: Tyler Yust <[email protected]> Co-authored-by: Saurabh Mishra <[email protected]> Co-authored-by: bugkill3r <[email protected]> Co-authored-by: Shakker <[email protected]> Co-authored-by: nexrin <[email protected]> Co-authored-by: pkuGeo <[email protected]> Co-authored-by: Lyle Hopkins <[email protected]> Co-authored-by: Lyle Hopkins <[email protected]>
* msteams: add pin/unpin, list-pins, and read message actions Wire up Graph API endpoints for message read, pin, unpin, and list-pins in the MS Teams extension, following the same patterns as edit/delete. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: address PR review comments for pin/unpin/read actions - Handle 204 No Content in postGraphJson (Graph mutations may return empty body) - Strip conversation:/user: prefixes in resolveConversationPath to avoid Graph 404s - Remove dead variable in channel pin branch - Rename unpin param from messageId to pinnedMessageId for semantic clarity - Accept both pinnedMessageId and messageId in unpin action handler for compat Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve user targets + add User-Agent to Graph helpers - Resolve user:<aadId> targets to actual conversation IDs via conversation store before Graph API calls (fixes 404 for DM-context actions) - Add User-Agent header to postGraphJson/deleteGraphRequest for consistency with fetchGraphJson after rebase onto main Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: resolve DM targets to Graph chat IDs + expose pin IDs - Prefer cached graphChatId over Bot Framework conversation IDs for user targets; throw descriptive error when no Graph-compatible ID is available - Add `id` field to list-pins rows so default formatters surface the pinned resource ID needed for the unpin flow Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * msteams: add react and reactions (list) message actions * msteams: add search message action via Graph API * msteams: fix search query injection, add ConsistencyLevel header, use manual query string --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Summary
fromparam) and result limiting (limitparam, 1–50, default 25)readfor consistency across actionsMS Teams now supports 10 message actions: poll, edit, delete, pin, unpin, list-pins, read, react, reactions, search.
Test plan
pnpm test -- extensions/msteams/src/graph-messages.test.ts— 33/33 pass (9 new search tests)pnpm format— clean🤖 Generated with Claude Code