Skip to content

Commit 90cb3b1

Browse files
authored
Merge branch 'main' into fix-windows-node-task-hidden
2 parents f927d51 + cb71ad5 commit 90cb3b1

99 files changed

Lines changed: 4635 additions & 1419 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Docs: https://docs.openclaw.ai
66

77
### Fixes
88

9+
- Agents/skills: apply the full effective tool policy pipeline to inline `command-dispatch: tool` skill dispatch before owner-only filtering, preserving configured allow, deny, sandbox, sender, group, and subagent restrictions. (#78525)
910
- Providers/GitHub Copilot: request identity-encoded Copilot API responses across token exchange, catalog, model calls, usage, and embeddings so compressed Business-account error payloads no longer reach JSON parsers as gzip bytes. Fixes #82871. Thanks @tonyfe01.
11+
- Telegram: preserve replied-to bot messages, captions, and media metadata in group reply chains so follow-up replies understand what the user is reacting to. (#82863)
1012
- Agents/diagnostics: split slow embedded-run `attempt-dispatch` startup summaries into workspace, prompt, runtime-plan, and final dispatch subspans so traces identify the delayed setup phase. Fixes #82782. (#82783) Thanks @galiniliev.
1113
- CLI/media: accept HTTP(S) URLs in `openclaw infer image describe --file`, fetching remote images through the guarded media path instead of treating URLs as local files. Fixes #82837. (#82854) Thanks @neeravmakwana.
1214
- Agents/subagents: keep session-backed parent runs active when the child wait call times out before the child session has actually settled, so late subagent completions are reconciled instead of being lost. Fixes #82787. Thanks @ramitrkar-hash.
@@ -19,11 +21,15 @@ Docs: https://docs.openclaw.ai
1921
- Agents/tools: keep the `message` tool available in embedded runs when it is explicitly allowed through `tools.alsoAllow` or runtime tool allowlists, so channel plugins with custom reply delivery can still use configured message sends. Fixes #82833. Thanks @cn1313113.
2022
- WhatsApp: honor forced document delivery for outbound image, GIF, and video media so `forceDocument`/`asDocument` sends preserve original media bytes instead of using compressed media payloads. (#79272) Thanks @itsuzef.
2123
- WhatsApp: name outbound document attachments from their MIME type when no filename is provided, so PDF and CSV sends arrive as `file.pdf` and `file.csv` instead of an extensionless `file`. Thanks @mcaxtr.
24+
- Process/diagnostics: report active lane blockers in lane wait warnings so `queueAhead=0` no longer hides commands waiting behind active work. Fixes #82791. (#82792) Thanks @galiniliev.
2225

2326
## 2026.5.17
2427

2528
### Changes
2629

30+
- Control UI: move settings-only destinations into the Settings workspace and add sidebar recent-session shortcuts plus a one-click new-session action.
31+
- Control UI: speed up scoped settings pages by loading required config before schema refreshes, caching burst schema responses, and opening Communications on lighter message settings first.
32+
- Control UI: simplify the Cron Jobs workspace with modal job creation, collapsed filters, and an empty state aimed at first-time setup.
2733
- Security/audit: add `security.audit.suppressions` for intentionally accepted audit findings, keeping suppressed matches out of the active summary while preserving them in JSON output with an active suppression notice. (#76949) Thanks @100menotu001.
2834
- Agents/subagents: label delegated task and subagent completion handoffs as ready for parent review, and tell requester agents to review/verify results before calling them done. (#78985) Thanks @100menotu001.
2935
- Providers/media: add fal and OpenRouter music-generation providers for the shared `music_generate` tool, including fal MiniMax/ACE/Stable Audio endpoints and OpenRouter Lyria audio output.
@@ -47,6 +53,8 @@ Docs: https://docs.openclaw.ai
4753

4854
### Fixes
4955

56+
- Gateway/Docker: fail closed for non-loopback gateway starts without explicit shared-secret or trusted-proxy auth, and stop the image default command from bypassing config validation. Fixes #82865. (#82866) Thanks @coygeek.
57+
- Agents/followups: route queued followup turns through CLI runtime backends instead of embedded harness lookup, preventing `claude-cli`/`google-gemini-cli` followups from failing before delivery. Fixes #82847. (#82857) Thanks @hclsys.
5058
- CLI/sessions: let `openclaw sessions cleanup --fix-missing` prune malformed rows with unresolvable transcript metadata instead of throwing. Fixes #80970. (#82745) Thanks @IWhatsskill.
5159
- Gateway/usage: refresh large session usage summaries in the background and reuse durable transcript metadata so `sessions.usage` no longer blocks Gateway requests on full transcript rescans. Fixes #82773. (#82778) Thanks @hclsys.
5260
- TUI: restore the submitted draft when chat is busy instead of clearing it or queueing another run. Fixes #45326. (#82774) Thanks @hyspacex.
@@ -126,6 +134,7 @@ Docs: https://docs.openclaw.ai
126134
- Providers/xAI: continue polling video generations while xAI reports in-flight jobs as `pending`, so Grok video requests no longer fail before the final `done` response. (#82610) Thanks @Manzojunior.
127135
- Logs: redact raw Basic auth and named security headers from `logs.tail` output before returning lines to read-scoped clients. Fixes #66832. Thanks @Magicray1217.
128136
- CLI/gateway: emit structured JSON for gateway transport close/timeout failures when `--json` is requested by health, gateway health, and devices list commands. Fixes #79108. Thanks @TurboTheTurtle.
137+
- Agents/Telegram: retry Bedrock non-visible terminal turns and mark non-deliverable attempts as trajectory errors instead of silent success. Fixes #82394. (#82905) Thanks @joshavant.
129138
- Telegram: normalize announce group targets via a new `resolveSessionTarget` channel hook so scheduled announcements resolve consistently against the same Telegram session conversation registry as inbound turns. Fixes #81229. Thanks @giodl73-repo.
130139
- QA/RTT: let `pnpm rtt` lease Convex-backed Telegram credentials while preserving RTT sample counts, sample timeouts, and result stats on the RTT harness path.
131140
- Discord: bind delayed gateway `identify` retries to the originating socket generation so retries triggered after a reconnect do not identify against a fresh socket. Fixes #82225. Thanks @giodl73-repo.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,4 @@ USER node
293293
HEALTHCHECK --interval=3m --timeout=10s --start-period=15s --retries=3 \
294294
CMD node -e "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
295295
ENTRYPOINT ["tini", "-s", "--"]
296-
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
296+
CMD ["node", "openclaw.mjs", "gateway"]

extensions/telegram/src/bot.test.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,6 +2237,129 @@ describe("createTelegramBot", () => {
22372237
expect(mediaFetch).toHaveBeenCalledTimes(1);
22382238
});
22392239

2240+
it("hydrates bot reply targets cached from earlier Telegram replies", async () => {
2241+
onSpy.mockClear();
2242+
replySpy.mockClear();
2243+
getFileSpy.mockClear();
2244+
2245+
const mediaFetch = vi.fn(
2246+
async () =>
2247+
new Response(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), {
2248+
status: 200,
2249+
headers: { "content-type": "image/png" },
2250+
}),
2251+
);
2252+
const ssrfMock = mockPinnedHostnameResolution();
2253+
2254+
try {
2255+
createTelegramBot({
2256+
token: "tok",
2257+
telegramTransport: {
2258+
fetch: mediaFetch as typeof fetch,
2259+
sourceFetch: mediaFetch as typeof fetch,
2260+
close: async () => {},
2261+
},
2262+
});
2263+
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
2264+
const baseCtx = {
2265+
me: { id: 999, username: "openclaw_bot" },
2266+
getFile: async () => ({ download: async () => new Uint8Array() }),
2267+
};
2268+
const chat = { id: 7, type: "group", title: "Ops" };
2269+
2270+
await handler({
2271+
...baseCtx,
2272+
message: {
2273+
chat,
2274+
message_id: 102,
2275+
text: "Why is there a 4th person?",
2276+
date: 1736380750,
2277+
from: { id: 2, is_bot: false, first_name: "UserB" },
2278+
reply_to_message: {
2279+
chat,
2280+
message_id: 101,
2281+
text: "Done, here is the image",
2282+
date: 1736380700,
2283+
from: { id: 999, is_bot: true, first_name: "OpenClaw" },
2284+
photo: [{ file_id: "generated-photo-1" }],
2285+
},
2286+
},
2287+
});
2288+
2289+
replySpy.mockClear();
2290+
getFileSpy.mockClear();
2291+
mediaFetch.mockClear();
2292+
2293+
await handler({
2294+
...baseCtx,
2295+
message: {
2296+
chat,
2297+
message_id: 103,
2298+
text: "@openclaw_bot explain what went wrong",
2299+
date: 1736380800,
2300+
from: { id: 1, is_bot: false, first_name: "UserA" },
2301+
reply_to_message: {
2302+
chat,
2303+
message_id: 102,
2304+
text: "Why is there a 4th person?",
2305+
date: 1736380750,
2306+
from: { id: 2, is_bot: false, first_name: "UserB" },
2307+
},
2308+
},
2309+
});
2310+
} finally {
2311+
ssrfMock.mockRestore();
2312+
}
2313+
2314+
expect(replySpy).toHaveBeenCalledTimes(1);
2315+
const payload = mockMsgContextArg(
2316+
replySpy as unknown as MockCallSource,
2317+
0,
2318+
0,
2319+
"replySpy call",
2320+
) as {
2321+
ReplyChain?: Array<{
2322+
messageId?: string;
2323+
sender?: string;
2324+
body?: string;
2325+
mediaRef?: string;
2326+
mediaPath?: string;
2327+
}>;
2328+
UntrustedStructuredContext?: unknown[];
2329+
};
2330+
expect(payload.ReplyChain?.map((entry) => entry.messageId)).toEqual(["102", "101"]);
2331+
expect(payload.ReplyChain?.[1]).toMatchObject({
2332+
sender: "OpenClaw",
2333+
body: "Done, here is the image",
2334+
mediaRef: "telegram:file/generated-photo-1",
2335+
});
2336+
expect(payload.ReplyChain?.[1]?.mediaPath).toBeTypeOf("string");
2337+
const [conversationContext] = requireArray(
2338+
payload.UntrustedStructuredContext,
2339+
"structured context",
2340+
);
2341+
const contextRecord = requireRecord(conversationContext, "conversation context");
2342+
const contextPayload = requireRecord(contextRecord.payload, "conversation context payload");
2343+
const messages = requireArray(contextPayload.messages, "conversation context messages").map(
2344+
(message, index) => requireRecord(message, `conversation context message ${index + 1}`),
2345+
);
2346+
const messagesById = new Map(messages.map((message) => [message.message_id, message]));
2347+
expect(messagesById.get("101")).toMatchObject({
2348+
sender: "OpenClaw",
2349+
body: "Done, here is the image",
2350+
media_ref: "telegram:file/generated-photo-1",
2351+
is_reply_target: true,
2352+
});
2353+
expect(messagesById.get("102")).toMatchObject({
2354+
sender: "UserB",
2355+
body: "Why is there a 4th person?",
2356+
reply_to_id: "101",
2357+
is_reply_target: true,
2358+
});
2359+
expect(getFileSpy).toHaveBeenCalledWith("generated-photo-1");
2360+
expect(mediaFetch).toHaveBeenCalledTimes(1);
2361+
});
2362+
22402363
it("does not fetch reply media for unauthorized DM replies", async () => {
22412364
onSpy.mockClear();
22422365
replySpy.mockClear();

extensions/telegram/src/message-cache.test.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,136 @@ describe("telegram message cache", () => {
142142
}
143143
});
144144

145+
it("records embedded reply targets as normal cached messages", async () => {
146+
const storePath = `/tmp/openclaw-telegram-message-cache-reply-target-${process.pid}-${Date.now()}.json`;
147+
const persistedPath = resolveTelegramMessageCachePath(storePath);
148+
const chat = { id: 7, type: "group", title: "Ops" } as const;
149+
await rm(persistedPath, { force: true });
150+
try {
151+
const firstCache = createTelegramMessageCache({ persistedPath });
152+
firstCache.record({
153+
accountId: "default",
154+
chatId: 7,
155+
msg: {
156+
chat,
157+
message_id: 102,
158+
date: 1736380750,
159+
text: "Why is there a 4th person?",
160+
from: { id: 2, is_bot: false, first_name: "UserB" },
161+
reply_to_message: {
162+
chat,
163+
message_id: 101,
164+
date: 1736380700,
165+
text: "Done, here is the image",
166+
from: { id: 999, is_bot: true, first_name: "Bot" },
167+
photo: [
168+
{
169+
file_id: "generated-photo-1",
170+
file_unique_id: "generated-photo-unique-1",
171+
width: 640,
172+
height: 480,
173+
},
174+
],
175+
} as Message["reply_to_message"],
176+
} as Message,
177+
});
178+
179+
resetTelegramMessageCacheBucketsForTest();
180+
const secondCache = createTelegramMessageCache({ persistedPath });
181+
const current = {
182+
chat,
183+
message_id: 103,
184+
date: 1736380800,
185+
text: "Explain what went wrong",
186+
from: { id: 1, is_bot: false, first_name: "UserA" },
187+
reply_to_message: {
188+
chat,
189+
message_id: 102,
190+
date: 1736380750,
191+
text: "Why is there a 4th person?",
192+
from: { id: 2, is_bot: false, first_name: "UserB" },
193+
} as Message["reply_to_message"],
194+
} as Message;
195+
const chain = buildTelegramReplyChain({
196+
cache: secondCache,
197+
accountId: "default",
198+
chatId: 7,
199+
msg: current,
200+
});
201+
const context = buildTelegramConversationContext({
202+
cache: secondCache,
203+
accountId: "default",
204+
chatId: 7,
205+
messageId: "103",
206+
replyChainNodes: chain,
207+
recentLimit: 10,
208+
replyTargetWindowSize: 2,
209+
});
210+
211+
expect(chain.map((entry) => entry.messageId)).toEqual(["102", "101"]);
212+
expect(chain[1]).toMatchObject({
213+
sender: "Bot",
214+
body: "Done, here is the image",
215+
mediaRef: "telegram:file/generated-photo-1",
216+
});
217+
expect(context.map((entry) => entry.node.messageId)).toEqual(["101", "102"]);
218+
expect(context.find((entry) => entry.node.messageId === "101")?.isReplyTarget).toBe(true);
219+
} finally {
220+
await rm(persistedPath, { force: true });
221+
}
222+
});
223+
224+
it("replaces authoritative edited message fields without stale caption carryover", () => {
225+
const cache = createTelegramMessageCache();
226+
const chat = { id: 7, type: "group", title: "Ops" } as const;
227+
cache.record({
228+
accountId: "default",
229+
chatId: 7,
230+
msg: {
231+
chat,
232+
message_id: 104,
233+
date: 1736380900,
234+
caption: "old caption",
235+
from: { id: 999, is_bot: true, first_name: "Bot" },
236+
photo: [
237+
{
238+
file_id: "generated-photo-2",
239+
file_unique_id: "generated-photo-unique-2",
240+
width: 640,
241+
height: 480,
242+
},
243+
],
244+
} as Message,
245+
});
246+
247+
const updated = cache.record({
248+
accountId: "default",
249+
chatId: 7,
250+
msg: {
251+
chat,
252+
message_id: 104,
253+
date: 1736380900,
254+
edit_date: 1736380910,
255+
from: { id: 999, is_bot: true, first_name: "Bot" },
256+
photo: [
257+
{
258+
file_id: "generated-photo-2",
259+
file_unique_id: "generated-photo-unique-2",
260+
width: 640,
261+
height: 480,
262+
},
263+
],
264+
} as Message,
265+
});
266+
267+
expect(updated).toMatchObject({
268+
messageId: "104",
269+
body: "<media:image>",
270+
mediaRef: "telegram:file/generated-photo-2",
271+
});
272+
expect(updated?.body).not.toBe("old caption");
273+
});
274+
145275
it("shares one persisted bucket across live cache instances", async () => {
146276
const storePath = `/tmp/openclaw-telegram-message-cache-shared-${process.pid}-${Date.now()}.json`;
147277
const persistedPath = resolveTelegramMessageCachePath(storePath);

0 commit comments

Comments
 (0)