mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:30:42 +00:00
fix(cron): preserve current delivery target context
This commit is contained in:
@@ -80,6 +80,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Models/LM Studio: preserve `@iq*` quant suffixes in model refs and provider
|
||||
matching so `/model lmstudio/...@iq3_xxs` keeps the exact LM Studio variant.
|
||||
Fixes #71474. (#71486) Thanks @Bartok9, @XinwuC, and @Sanjays2402.
|
||||
- Matrix/cron: preserve the live Matrix delivery target when creating implicit
|
||||
announce reminder jobs so mixed-case room IDs are not reconstructed from
|
||||
lowercased session keys. Fixes #71798.
|
||||
- Feishu: accept Schema 2.0 card action callbacks that report
|
||||
`context.open_chat_id` instead of legacy `context.chat_id`, so button
|
||||
callbacks no longer drop as malformed. Fixes #71670. Thanks @eddy1068.
|
||||
|
||||
@@ -241,6 +241,12 @@ export function createOpenClawTools(
|
||||
nodesTool,
|
||||
createCronTool({
|
||||
agentSessionKey: options?.agentSessionKey,
|
||||
currentDeliveryContext: {
|
||||
channel: options?.agentChannel,
|
||||
to: options?.currentChannelId ?? options?.agentTo,
|
||||
accountId: options?.agentAccountId,
|
||||
threadId: options?.currentThreadTs ?? options?.agentThreadId,
|
||||
},
|
||||
}),
|
||||
]),
|
||||
...(!embedded && messageTool ? [messageTool] : []),
|
||||
|
||||
@@ -15,6 +15,7 @@ const mocks = vi.hoisted(() => {
|
||||
|
||||
return {
|
||||
stubTool,
|
||||
createCronToolOptions: vi.fn(),
|
||||
textToSpeech: vi.fn(async () => ({
|
||||
success: true,
|
||||
audioPath: "/tmp/openclaw/tts-config-test.opus",
|
||||
@@ -41,7 +42,10 @@ vi.mock("./tools/canvas-tool.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./tools/cron-tool.js", () => ({
|
||||
createCronTool: () => mocks.stubTool("cron"),
|
||||
createCronTool: (options: unknown) => {
|
||||
mocks.createCronToolOptions(options);
|
||||
return mocks.stubTool("cron");
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("./tools/gateway-tool.js", () => ({
|
||||
@@ -119,6 +123,7 @@ vi.mock("../tts/tts.js", () => ({
|
||||
|
||||
describe("createOpenClawTools TTS config wiring", () => {
|
||||
beforeEach(() => {
|
||||
mocks.createCronToolOptions.mockClear();
|
||||
mocks.textToSpeech.mockClear();
|
||||
});
|
||||
|
||||
@@ -163,3 +168,35 @@ describe("createOpenClawTools TTS config wiring", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("createOpenClawTools cron context wiring", () => {
|
||||
beforeEach(() => {
|
||||
mocks.createCronToolOptions.mockClear();
|
||||
});
|
||||
|
||||
it("passes preserved channel delivery context into the cron tool", async () => {
|
||||
const { createOpenClawTools } = await import("./openclaw-tools.js");
|
||||
|
||||
createOpenClawTools({
|
||||
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||
agentChannel: "matrix",
|
||||
agentAccountId: "bot-a",
|
||||
agentTo: "room:!FallbackRoom:Example.Org",
|
||||
agentThreadId: "$FallbackThread:Example.Org",
|
||||
currentChannelId: "room:!AbCdEf1234567890:example.org",
|
||||
currentThreadTs: "$RootEvent:Example.Org",
|
||||
disableMessageTool: true,
|
||||
disablePluginTools: true,
|
||||
});
|
||||
|
||||
expect(mocks.createCronToolOptions).toHaveBeenCalledWith({
|
||||
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||
currentDeliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!AbCdEf1234567890:example.org",
|
||||
accountId: "bot-a",
|
||||
threadId: "$RootEvent:Example.Org",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,10 +60,16 @@ describe("cron tool", () => {
|
||||
|
||||
async function executeAddAndReadDelivery(params: {
|
||||
callId: string;
|
||||
agentSessionKey: string;
|
||||
agentSessionKey?: string;
|
||||
currentDeliveryContext?: NonNullable<
|
||||
Parameters<typeof createCronTool>[0]
|
||||
>["currentDeliveryContext"];
|
||||
delivery?: { mode?: string; channel?: string; to?: string } | null;
|
||||
}) {
|
||||
const tool = createTestCronTool({ agentSessionKey: params.agentSessionKey });
|
||||
const tool = createTestCronTool({
|
||||
agentSessionKey: params.agentSessionKey,
|
||||
currentDeliveryContext: params.currentDeliveryContext,
|
||||
});
|
||||
await tool.execute(params.callId, {
|
||||
action: "add",
|
||||
job: {
|
||||
@@ -415,6 +421,114 @@ describe("cron tool", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers current delivery context over lowercased session-key targets", async () => {
|
||||
expect(
|
||||
await executeAddAndReadDelivery({
|
||||
callId: "call-current-context",
|
||||
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||
currentDeliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!AbCdEf1234567890:example.org",
|
||||
accountId: "bot-a",
|
||||
threadId: "$RootEvent:Example.Org",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "announce",
|
||||
channel: "matrix",
|
||||
to: "room:!AbCdEf1234567890:example.org",
|
||||
accountId: "bot-a",
|
||||
threadId: "$RootEvent:Example.Org",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not let current delivery context override explicit delivery targets", async () => {
|
||||
expect(
|
||||
await executeAddAndReadDelivery({
|
||||
callId: "call-explicit-target-wins",
|
||||
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||
currentDeliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!AbCdEf1234567890:example.org",
|
||||
},
|
||||
delivery: {
|
||||
mode: "announce",
|
||||
channel: "telegram",
|
||||
to: "-100123",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "announce",
|
||||
channel: "telegram",
|
||||
to: "-100123",
|
||||
});
|
||||
});
|
||||
|
||||
it("infers delivery from current context even when no session key is available", async () => {
|
||||
expect(
|
||||
await executeAddAndReadDelivery({
|
||||
callId: "call-context-no-session",
|
||||
currentDeliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "!AbCdEf1234567890:example.org",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "announce",
|
||||
channel: "matrix",
|
||||
to: "!AbCdEf1234567890:example.org",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses current delivery context when delivery is null", async () => {
|
||||
expect(
|
||||
await executeAddAndReadDelivery({
|
||||
callId: "call-null-delivery-current-context",
|
||||
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||
currentDeliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "!AbCdEf1234567890:example.org",
|
||||
},
|
||||
delivery: null,
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "announce",
|
||||
channel: "matrix",
|
||||
to: "!AbCdEf1234567890:example.org",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to session-key inference when current context has no target", async () => {
|
||||
expect(
|
||||
await executeAddAndReadDelivery({
|
||||
callId: "call-empty-current-context",
|
||||
agentSessionKey: "agent:main:telegram:group:-1001234567890:topic:99",
|
||||
currentDeliveryContext: {
|
||||
channel: "matrix",
|
||||
to: " ",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "announce",
|
||||
channel: "telegram",
|
||||
to: "-1001234567890:topic:99",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not infer current delivery context when delivery mode is none", async () => {
|
||||
expect(
|
||||
await executeAddAndReadDelivery({
|
||||
callId: "call-current-context-mode-none",
|
||||
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||
currentDeliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "!AbCdEf1234567890:example.org",
|
||||
},
|
||||
delivery: { mode: "none" },
|
||||
}),
|
||||
).toEqual({ mode: "none" });
|
||||
});
|
||||
|
||||
it("infers delivery when delivery is null", async () => {
|
||||
expect(
|
||||
await executeAddAndReadDelivery({
|
||||
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "../../shared/string-coerce.js";
|
||||
import { isRecord, truncateUtf16Safe } from "../../utils.js";
|
||||
import {
|
||||
normalizeDeliveryContext,
|
||||
type DeliveryContext,
|
||||
} from "../../utils/delivery-context.shared.js";
|
||||
import { resolveSessionAgentId } from "../agent-scope.js";
|
||||
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||
import { CRON_TOOL_DISPLAY_SUMMARY } from "../tool-description-presets.js";
|
||||
@@ -287,6 +291,7 @@ export const CronToolSchema = Type.Object(
|
||||
|
||||
type CronToolOptions = {
|
||||
agentSessionKey?: string;
|
||||
currentDeliveryContext?: DeliveryContext;
|
||||
};
|
||||
|
||||
type GatewayToolCaller = typeof callGatewayTool;
|
||||
@@ -443,6 +448,27 @@ function inferDeliveryFromSessionKey(agentSessionKey?: string): CronDelivery | n
|
||||
return delivery;
|
||||
}
|
||||
|
||||
function inferDeliveryFromContext(context?: DeliveryContext): CronDelivery | null {
|
||||
const normalized = normalizeDeliveryContext(context);
|
||||
if (!normalized?.to) {
|
||||
return null;
|
||||
}
|
||||
const delivery: CronDelivery = {
|
||||
mode: "announce",
|
||||
to: normalized.to,
|
||||
};
|
||||
if (normalized.channel) {
|
||||
delivery.channel = normalized.channel as CronMessageChannel;
|
||||
}
|
||||
if (normalized.accountId) {
|
||||
delivery.accountId = normalized.accountId;
|
||||
}
|
||||
if (normalized.threadId != null) {
|
||||
delivery.threadId = normalized.threadId;
|
||||
}
|
||||
return delivery;
|
||||
}
|
||||
|
||||
export function createCronTool(opts?: CronToolOptions, deps?: CronToolDeps): AnyAgentTool {
|
||||
const callGateway = deps?.callGatewayTool ?? callGatewayTool;
|
||||
return {
|
||||
@@ -583,7 +609,7 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
||||
}
|
||||
|
||||
if (
|
||||
opts?.agentSessionKey &&
|
||||
(opts?.agentSessionKey || opts?.currentDeliveryContext) &&
|
||||
job &&
|
||||
typeof job === "object" &&
|
||||
"payload" in job &&
|
||||
@@ -613,7 +639,9 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
||||
(mode === "" || mode === "announce") &&
|
||||
!hasTarget;
|
||||
if (shouldInfer) {
|
||||
const inferred = inferDeliveryFromSessionKey(opts.agentSessionKey);
|
||||
const inferred =
|
||||
inferDeliveryFromContext(opts.currentDeliveryContext) ??
|
||||
inferDeliveryFromSessionKey(opts.agentSessionKey);
|
||||
if (inferred) {
|
||||
(job as { delivery?: unknown }).delivery = {
|
||||
...delivery,
|
||||
|
||||
Reference in New Issue
Block a user