mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
test(cron): cover delivery context edge cases
This commit is contained in:
@@ -140,7 +140,7 @@ forever.
|
|||||||
| `webhook` | POST finished event payload to a URL |
|
| `webhook` | POST finished event payload to a URL |
|
||||||
| `none` | No runner fallback delivery |
|
| `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>`).
|
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.
|
||||||
|
|
||||||
For isolated jobs, chat delivery is shared. If a chat route is available, the
|
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 can use the `message` tool even when the job uses `--no-deliver`. If the
|
||||||
@@ -148,6 +148,11 @@ agent sends to the configured/current target, OpenClaw skips the fallback
|
|||||||
announce. Otherwise `announce`, `webhook`, and `none` only control what the
|
announce. Otherwise `announce`, `webhook`, and `none` only control what the
|
||||||
runner does with the final reply after the agent turn.
|
runner does with the final reply after the agent turn.
|
||||||
|
|
||||||
|
When an agent creates an isolated reminder from an active chat, OpenClaw stores
|
||||||
|
the preserved live delivery target for the fallback announce route. Internal
|
||||||
|
session keys may be lowercase; provider delivery targets are not reconstructed
|
||||||
|
from those keys when current chat context is available.
|
||||||
|
|
||||||
Failure notifications follow a separate destination path:
|
Failure notifications follow a separate destination path:
|
||||||
|
|
||||||
- `cron.failureDestination` sets a global default for failure notifications.
|
- `cron.failureDestination` sets a global default for failure notifications.
|
||||||
@@ -418,6 +423,9 @@ openclaw doctor
|
|||||||
- Delivery mode `none` means no runner fallback send is expected. The agent can
|
- Delivery mode `none` means no runner fallback send is expected. The agent can
|
||||||
still send directly with the `message` tool when a chat route is available.
|
still send directly with the `message` tool when a chat route is available.
|
||||||
- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped.
|
- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped.
|
||||||
|
- For Matrix, copied or legacy jobs with lowercased `delivery.to` room IDs can
|
||||||
|
fail because Matrix room IDs are case-sensitive. Edit the job to the exact
|
||||||
|
`!room:server` or `room:!room:server` value from Matrix.
|
||||||
- Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials.
|
- Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials.
|
||||||
- If the isolated run returns only the silent token (`NO_REPLY` / `no_reply`),
|
- If the isolated run returns only the silent token (`NO_REPLY` / `no_reply`),
|
||||||
OpenClaw suppresses direct outbound delivery and also suppresses the fallback
|
OpenClaw suppresses direct outbound delivery and also suppresses the fallback
|
||||||
|
|||||||
@@ -883,6 +883,11 @@ Matrix accepts these target forms anywhere OpenClaw asks you for a room or user
|
|||||||
- Rooms: `!room:server`, `room:!room:server`, or `matrix:room:!room:server`
|
- Rooms: `!room:server`, `room:!room:server`, or `matrix:room:!room:server`
|
||||||
- Aliases: `#alias:server`, `channel:#alias:server`, or `matrix:channel:#alias:server`
|
- Aliases: `#alias:server`, `channel:#alias:server`, or `matrix:channel:#alias:server`
|
||||||
|
|
||||||
|
Matrix room IDs are case-sensitive. Use the exact room ID casing from Matrix
|
||||||
|
when configuring explicit delivery targets, cron jobs, bindings, or allowlists.
|
||||||
|
OpenClaw keeps internal session keys canonical for storage, so those lowercase
|
||||||
|
keys are not a reliable source for Matrix delivery IDs.
|
||||||
|
|
||||||
Live directory lookup uses the logged-in Matrix account:
|
Live directory lookup uses the logged-in Matrix account:
|
||||||
|
|
||||||
- User lookups query the Matrix user directory on that homeserver.
|
- User lookups query the Matrix user directory on that homeserver.
|
||||||
|
|||||||
@@ -138,6 +138,10 @@ Delivery ownership note:
|
|||||||
- `announce` fallback-delivers the final reply only when the agent did not send
|
- `announce` fallback-delivers the final reply only when the agent did not send
|
||||||
directly to the resolved target. `webhook` posts the finished payload to a URL.
|
directly to the resolved target. `webhook` posts the finished payload to a URL.
|
||||||
`none` disables runner fallback delivery.
|
`none` disables runner fallback delivery.
|
||||||
|
- Reminders created from an active chat preserve the live chat delivery target
|
||||||
|
for fallback announce delivery. Internal session keys may be lowercase; do not
|
||||||
|
use them as a source of truth for case-sensitive provider IDs such as Matrix
|
||||||
|
room IDs.
|
||||||
|
|
||||||
## Common admin commands
|
## Common admin commands
|
||||||
|
|
||||||
|
|||||||
@@ -199,4 +199,28 @@ describe("createOpenClawTools cron context wiring", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses agent route context when auto-threading context is unavailable", 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",
|
||||||
|
disableMessageTool: true,
|
||||||
|
disablePluginTools: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mocks.createCronToolOptions).toHaveBeenCalledWith({
|
||||||
|
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||||
|
currentDeliveryContext: {
|
||||||
|
channel: "matrix",
|
||||||
|
to: "room:!FallbackRoom:Example.Org",
|
||||||
|
accountId: "bot-a",
|
||||||
|
threadId: "$FallbackThread:Example.Org",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ vi.mock("../agent-scope.js", async () => {
|
|||||||
import { createCronTool } from "./cron-tool.js";
|
import { createCronTool } from "./cron-tool.js";
|
||||||
|
|
||||||
describe("cron tool", () => {
|
describe("cron tool", () => {
|
||||||
|
type TestDelivery = {
|
||||||
|
mode?: string;
|
||||||
|
channel?: string;
|
||||||
|
to?: string;
|
||||||
|
accountId?: string;
|
||||||
|
threadId?: string | number;
|
||||||
|
};
|
||||||
|
|
||||||
function createTestCronTool(
|
function createTestCronTool(
|
||||||
opts?: Parameters<typeof createCronTool>[0],
|
opts?: Parameters<typeof createCronTool>[0],
|
||||||
): ReturnType<typeof createCronTool> {
|
): ReturnType<typeof createCronTool> {
|
||||||
@@ -64,7 +72,7 @@ describe("cron tool", () => {
|
|||||||
currentDeliveryContext?: NonNullable<
|
currentDeliveryContext?: NonNullable<
|
||||||
Parameters<typeof createCronTool>[0]
|
Parameters<typeof createCronTool>[0]
|
||||||
>["currentDeliveryContext"];
|
>["currentDeliveryContext"];
|
||||||
delivery?: { mode?: string; channel?: string; to?: string } | null;
|
delivery?: TestDelivery | null;
|
||||||
}) {
|
}) {
|
||||||
const tool = createTestCronTool({
|
const tool = createTestCronTool({
|
||||||
agentSessionKey: params.agentSessionKey,
|
agentSessionKey: params.agentSessionKey,
|
||||||
@@ -79,7 +87,7 @@ describe("cron tool", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
||||||
params?: { delivery?: { mode?: string; channel?: string; to?: string } };
|
params?: { delivery?: TestDelivery };
|
||||||
};
|
};
|
||||||
return call?.params?.delivery;
|
return call?.params?.delivery;
|
||||||
}
|
}
|
||||||
@@ -464,6 +472,53 @@ describe("cron tool", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps explicit delivery account and thread while filling target from context", async () => {
|
||||||
|
expect(
|
||||||
|
await executeAddAndReadDelivery({
|
||||||
|
callId: "call-explicit-delivery-fields-win",
|
||||||
|
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||||
|
currentDeliveryContext: {
|
||||||
|
channel: "matrix",
|
||||||
|
to: "!AbCdEf1234567890:example.org",
|
||||||
|
accountId: "context-bot",
|
||||||
|
threadId: "$ContextThread:Example.Org",
|
||||||
|
},
|
||||||
|
delivery: {
|
||||||
|
mode: "announce",
|
||||||
|
accountId: "explicit-bot",
|
||||||
|
threadId: "$ExplicitThread:Example.Org",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
mode: "announce",
|
||||||
|
channel: "matrix",
|
||||||
|
to: "!AbCdEf1234567890:example.org",
|
||||||
|
accountId: "explicit-bot",
|
||||||
|
threadId: "$ExplicitThread:Example.Org",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("trims current context fields without changing provider target casing", async () => {
|
||||||
|
expect(
|
||||||
|
await executeAddAndReadDelivery({
|
||||||
|
callId: "call-trim-current-context",
|
||||||
|
agentSessionKey: "agent:main:matrix:channel:!abcdef1234567890:example.org",
|
||||||
|
currentDeliveryContext: {
|
||||||
|
channel: " Matrix ",
|
||||||
|
to: " !AbCdEf1234567890:Example.Org ",
|
||||||
|
accountId: " Bot-A ",
|
||||||
|
threadId: " $RootEvent:Example.Org ",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
mode: "announce",
|
||||||
|
channel: "matrix",
|
||||||
|
to: "!AbCdEf1234567890:Example.Org",
|
||||||
|
accountId: "bot-a",
|
||||||
|
threadId: "$RootEvent:Example.Org",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("infers delivery from current context even when no session key is available", async () => {
|
it("infers delivery from current context even when no session key is available", async () => {
|
||||||
expect(
|
expect(
|
||||||
await executeAddAndReadDelivery({
|
await executeAddAndReadDelivery({
|
||||||
|
|||||||
@@ -644,8 +644,8 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|||||||
inferDeliveryFromSessionKey(opts.agentSessionKey);
|
inferDeliveryFromSessionKey(opts.agentSessionKey);
|
||||||
if (inferred) {
|
if (inferred) {
|
||||||
(job as { delivery?: unknown }).delivery = {
|
(job as { delivery?: unknown }).delivery = {
|
||||||
...delivery,
|
|
||||||
...inferred,
|
...inferred,
|
||||||
|
...delivery,
|
||||||
} satisfies CronDelivery;
|
} satisfies CronDelivery;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user