mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-04-30 22:12:32 +08:00
fix(cron): accept threaded delivery in gateway schema
This commit is contained in:
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Providers/Cloudflare AI Gateway: strip assistant prefill turns from Anthropic Messages payloads when thinking is enabled, so Claude requests through Cloudflare AI Gateway no longer fail Anthropic conversation-ending validation. Fixes #72905; carries forward #73005. Thanks @AaronFaby and @sahilsatralkar.
|
||||
- Channels/sessions: prevent guarded inbound session recording from creating route-only phantom sessions while still allowing last-route updates for sessions that already exist. Carries forward #73009. Thanks @jzakirov.
|
||||
- Cron: accept `delivery.threadId` in Gateway cron add/update schemas so scheduled announce delivery can target Telegram forum topics and other threaded channel destinations through the documented delivery path. Fixes #73017. Thanks @coachsootz.
|
||||
- Plugins/runtime deps: stage bundled plugin dependencies imported by mirrored root dist chunks, so packaged memory and status commands do not miss `chokidar` or similar root-chunk dependencies after update. Fixes #72882 and #72970; carries forward #72992. Thanks @shrimpy8, @colin-chang, and @Schnup03.
|
||||
- Agents/runtime context: deliver hidden runtime context through prompt-local system context while keeping the transcript-only custom entry out of provider user turns, and strip stale copied runtime-context prefaces from user-facing replies. Fixes #72386; carries forward #72969. Thanks @jhsmith409.
|
||||
- Channels/Telegram: skip the optional webhook-info API call during polling-mode status checks and startup bot-label probes so long-polling setups avoid an unnecessary Telegram round trip. Carries forward #72990. Thanks @danielgruneberg.
|
||||
|
||||
@@ -150,7 +150,7 @@ If an isolated run hits a live model-switch handoff, cron retries with the switc
|
||||
| `webhook` | POST finished event payload to a URL |
|
||||
| `none` | No runner fallback delivery |
|
||||
|
||||
Use `--announce --channel telegram --to "-1001234567890"` for channel delivery. For Telegram forum topics, use `-1001234567890:topic:123`. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`). Matrix room IDs are case-sensitive; use the exact room ID or `room:!room:server` form from Matrix.
|
||||
Use `--announce --channel telegram --to "-1001234567890"` for channel delivery. For Telegram forum topics, use `-1001234567890:topic:123`; direct RPC/config callers may also pass `delivery.threadId` as a string or number. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`). Matrix room IDs are case-sensitive; use the exact room ID or `room:!room:server` form from Matrix.
|
||||
|
||||
For isolated jobs, chat delivery is shared. If a chat route is available, the agent can use the `message` tool even when the job uses `--no-deliver`. If the agent sends to the configured/current target, OpenClaw skips the fallback announce. Otherwise `announce`, `webhook`, and `none` only control what the runner does with the final reply after the agent turn.
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ vi.mock("../agent-scope.js", async () => {
|
||||
import { createCronTool } from "./cron-tool.js";
|
||||
|
||||
describe("cron tool", () => {
|
||||
type SchemaLike = {
|
||||
anyOf?: Array<{ type?: string }>;
|
||||
description?: string;
|
||||
properties?: Record<string, SchemaLike>;
|
||||
};
|
||||
|
||||
type TestDelivery = {
|
||||
mode?: string;
|
||||
channel?: string;
|
||||
@@ -145,6 +151,18 @@ describe("cron tool", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("advertises delivery threadId in the tool schema", () => {
|
||||
const tool = createTestCronTool();
|
||||
const parameters = tool.parameters as SchemaLike;
|
||||
const jobThreadId = parameters.properties?.job?.properties?.delivery?.properties?.threadId;
|
||||
const patchThreadId = parameters.properties?.patch?.properties?.delivery?.properties?.threadId;
|
||||
|
||||
for (const threadId of [jobThreadId, patchThreadId]) {
|
||||
expect(threadId?.description).toContain("Thread/topic id");
|
||||
expect(threadId?.anyOf?.map((entry) => entry.type)).toEqual(["string", "number"]);
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
"update",
|
||||
|
||||
@@ -183,6 +183,11 @@ const CronDeliverySchema = Type.Optional(
|
||||
mode: optionalStringEnum(CRON_DELIVERY_MODES, { description: "Delivery mode" }),
|
||||
channel: Type.Optional(Type.String({ description: "Delivery channel" })),
|
||||
to: Type.Optional(Type.String({ description: "Delivery target" })),
|
||||
threadId: Type.Optional(
|
||||
Type.Union([Type.String(), Type.Number()], {
|
||||
description: "Thread/topic id for channels that support threaded delivery",
|
||||
}),
|
||||
),
|
||||
bestEffort: Type.Optional(Type.Boolean()),
|
||||
accountId: Type.Optional(Type.String({ description: "Account target for delivery" })),
|
||||
failureDestination: Type.Optional(
|
||||
@@ -576,9 +581,10 @@ PAYLOAD TYPES (payload.kind):
|
||||
{ "kind": "agentTurn", "message": "<prompt>", "model": "<optional>", "thinking": "<optional>", "timeoutSeconds": <optional, 0 means no timeout> }
|
||||
|
||||
DELIVERY (top-level):
|
||||
{ "mode": "none|announce|webhook", "channel": "<optional>", "to": "<optional>", "bestEffort": <optional-bool> }
|
||||
{ "mode": "none|announce|webhook", "channel": "<optional>", "to": "<optional>", "threadId": "<optional>", "bestEffort": <optional-bool> }
|
||||
- Default for isolated agentTurn jobs (when delivery omitted): "announce"
|
||||
- announce: send to chat channel (optional channel/to target)
|
||||
- threadId: chat thread/topic id for channels that support threaded delivery
|
||||
- webhook: send finished-run event as HTTP POST to delivery.to (URL required)
|
||||
- If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ export const CronFailureDestinationSchema = Type.Object(
|
||||
|
||||
const CronDeliverySharedProperties = {
|
||||
channel: Type.Optional(Type.Union([Type.Literal("last"), NonEmptyString])),
|
||||
threadId: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
||||
accountId: Type.Optional(NonEmptyString),
|
||||
bestEffort: Type.Optional(Type.Boolean()),
|
||||
failureDestination: Type.Optional(CronFailureDestinationSchema),
|
||||
|
||||
@@ -82,6 +82,93 @@ describe("cron method validation", () => {
|
||||
getRuntimeConfig.mockReset().mockReturnValue({} as OpenClawConfig);
|
||||
});
|
||||
|
||||
it("accepts threadId on announce delivery add params", async () => {
|
||||
getRuntimeConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "telegram-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
const { context, respond } = await invokeCronAdd({
|
||||
name: "topic announce add",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
delivery: {
|
||||
mode: "announce",
|
||||
channel: "telegram",
|
||||
to: "-1001234567890",
|
||||
threadId: 123,
|
||||
},
|
||||
});
|
||||
|
||||
expect(context.cron.add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
delivery: expect.objectContaining({
|
||||
mode: "announce",
|
||||
channel: "telegram",
|
||||
to: "-1001234567890",
|
||||
threadId: 123,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(respond).toHaveBeenCalledWith(true, { id: "cron-1" }, undefined);
|
||||
});
|
||||
|
||||
it("accepts threadId on announce delivery update params", async () => {
|
||||
getRuntimeConfig.mockReturnValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "telegram-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
const { context, respond } = await invokeCronUpdate(
|
||||
{
|
||||
id: "cron-1",
|
||||
patch: {
|
||||
delivery: {
|
||||
mode: "announce",
|
||||
channel: "telegram",
|
||||
to: "-1001234567890",
|
||||
threadId: "456",
|
||||
},
|
||||
},
|
||||
},
|
||||
createCronJob({
|
||||
delivery: { mode: "announce", channel: "telegram", to: "-1001234567890" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(context.cron.update).toHaveBeenCalledWith(
|
||||
"cron-1",
|
||||
expect.objectContaining({
|
||||
delivery: expect.objectContaining({
|
||||
mode: "announce",
|
||||
channel: "telegram",
|
||||
to: "-1001234567890",
|
||||
threadId: "456",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(respond).toHaveBeenCalledWith(true, { id: "cron-1" }, undefined);
|
||||
});
|
||||
|
||||
it("rejects ambiguous announce delivery on add when multiple channels are configured", async () => {
|
||||
getRuntimeConfig.mockReturnValue({
|
||||
session: {
|
||||
|
||||
Reference in New Issue
Block a user