fix: cap cron context messages (#1103) (thanks @mkbehr)
This commit is contained in:
@@ -16,6 +16,7 @@ Docs: https://docs.clawd.bot
|
||||
- Config: avoid stack traces for invalid configs and log the config path.
|
||||
- Doctor: warn when gateway.mode is unset with configure/config guidance.
|
||||
- macOS: include Textual syntax highlighting resources in packaged app to prevent chat crashes. (#1362)
|
||||
- Cron: cap reminder context history to 10 messages and honor `contextMessages`. (#1103) Thanks @mkbehr.
|
||||
- UI: refresh debug panel on route-driven tab changes. (#1373) Thanks @yazinsai.
|
||||
|
||||
## 2026.1.21
|
||||
|
||||
@@ -129,6 +129,42 @@ describe("cron tool", () => {
|
||||
expect(text).toContain("User: Remind me about the thing at 2pm");
|
||||
});
|
||||
|
||||
it("caps contextMessages at 10", async () => {
|
||||
const messages = Array.from({ length: 12 }, (_, idx) => ({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: `Message ${idx + 1}` }],
|
||||
}));
|
||||
callGatewayMock.mockResolvedValueOnce({ messages }).mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const tool = createCronTool({ agentSessionKey: "main" });
|
||||
await tool.execute("call5", {
|
||||
action: "add",
|
||||
contextMessages: 20,
|
||||
job: {
|
||||
name: "reminder",
|
||||
schedule: { atMs: 123 },
|
||||
payload: { kind: "systemEvent", text: "Reminder: the thing." },
|
||||
},
|
||||
});
|
||||
|
||||
expect(callGatewayMock).toHaveBeenCalledTimes(2);
|
||||
const historyCall = callGatewayMock.mock.calls[0]?.[0] as {
|
||||
method?: string;
|
||||
params?: { limit?: number };
|
||||
};
|
||||
expect(historyCall.method).toBe("chat.history");
|
||||
expect(historyCall.params?.limit).toBe(10);
|
||||
|
||||
const cronCall = callGatewayMock.mock.calls[1]?.[0] as {
|
||||
params?: { payload?: { text?: string } };
|
||||
};
|
||||
const text = cronCall.params?.payload?.text ?? "";
|
||||
expect(text).not.toMatch(/Message 1\\b/);
|
||||
expect(text).not.toMatch(/Message 2\\b/);
|
||||
expect(text).toContain("Message 3");
|
||||
expect(text).toContain("Message 12");
|
||||
});
|
||||
|
||||
it("does not add context when contextMessages is 0 (default)", async () => {
|
||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const CRON_ACTIONS = ["status", "list", "add", "update", "remove", "run", "runs"
|
||||
|
||||
const CRON_WAKE_MODES = ["now", "next-heartbeat"] as const;
|
||||
|
||||
const REMINDER_CONTEXT_MESSAGES_MAX = 10;
|
||||
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
|
||||
const REMINDER_CONTEXT_TOTAL_MAX = 700;
|
||||
const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
|
||||
@@ -33,7 +34,9 @@ const CronToolSchema = Type.Object({
|
||||
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||
text: Type.Optional(Type.String()),
|
||||
mode: optionalStringEnum(CRON_WAKE_MODES),
|
||||
contextMessages: Type.Optional(Type.Number()),
|
||||
contextMessages: Type.Optional(
|
||||
Type.Number({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX }),
|
||||
),
|
||||
});
|
||||
|
||||
type CronToolOptions = {
|
||||
@@ -88,7 +91,11 @@ async function buildReminderContextLines(params: {
|
||||
gatewayOpts: GatewayCallOptions;
|
||||
contextMessages: number;
|
||||
}) {
|
||||
if (params.contextMessages <= 0) return [];
|
||||
const maxMessages = Math.min(
|
||||
REMINDER_CONTEXT_MESSAGES_MAX,
|
||||
Math.max(0, Math.floor(params.contextMessages)),
|
||||
);
|
||||
if (maxMessages <= 0) return [];
|
||||
const sessionKey = params.agentSessionKey?.trim();
|
||||
if (!sessionKey) return [];
|
||||
const cfg = loadConfig();
|
||||
@@ -97,13 +104,13 @@ async function buildReminderContextLines(params: {
|
||||
try {
|
||||
const res = (await callGatewayTool("chat.history", params.gatewayOpts, {
|
||||
sessionKey: resolvedKey,
|
||||
limit: 12,
|
||||
limit: maxMessages,
|
||||
})) as { messages?: unknown[] };
|
||||
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
||||
const parsed = messages
|
||||
.map((msg) => extractMessageText(msg as ChatMessage))
|
||||
.filter((msg): msg is { role: string; text: string } => Boolean(msg));
|
||||
const recent = parsed.slice(-params.contextMessages);
|
||||
const recent = parsed.slice(-maxMessages);
|
||||
if (recent.length === 0) return [];
|
||||
const lines: string[] = [];
|
||||
let total = 0;
|
||||
@@ -126,7 +133,7 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool {
|
||||
label: "Cron",
|
||||
name: "cron",
|
||||
description:
|
||||
"Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use `jobId` as the canonical identifier; `id` is accepted for compatibility. Use `contextMessages` to add previous messages as context to the job text.",
|
||||
"Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use `jobId` as the canonical identifier; `id` is accepted for compatibility. Use `contextMessages` (0-10) to add previous messages as context to the job text.",
|
||||
parameters: CronToolSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const params = args as Record<string, unknown>;
|
||||
@@ -152,7 +159,9 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool {
|
||||
}
|
||||
const job = normalizeCronJobCreate(params.job) ?? params.job;
|
||||
const contextMessages =
|
||||
typeof params.contextMessages === "number" ? params.contextMessages : 0;
|
||||
typeof params.contextMessages === "number" && Number.isFinite(params.contextMessages)
|
||||
? params.contextMessages
|
||||
: 0;
|
||||
if (
|
||||
job &&
|
||||
typeof job === "object" &&
|
||||
|
||||
Reference in New Issue
Block a user