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.
|
- Config: avoid stack traces for invalid configs and log the config path.
|
||||||
- Doctor: warn when gateway.mode is unset with configure/config guidance.
|
- 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)
|
- 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.
|
- UI: refresh debug panel on route-driven tab changes. (#1373) Thanks @yazinsai.
|
||||||
|
|
||||||
## 2026.1.21
|
## 2026.1.21
|
||||||
|
|||||||
@@ -129,6 +129,42 @@ describe("cron tool", () => {
|
|||||||
expect(text).toContain("User: Remind me about the thing at 2pm");
|
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 () => {
|
it("does not add context when contextMessages is 0 (default)", async () => {
|
||||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
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 CRON_WAKE_MODES = ["now", "next-heartbeat"] as const;
|
||||||
|
|
||||||
|
const REMINDER_CONTEXT_MESSAGES_MAX = 10;
|
||||||
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
|
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
|
||||||
const REMINDER_CONTEXT_TOTAL_MAX = 700;
|
const REMINDER_CONTEXT_TOTAL_MAX = 700;
|
||||||
const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
|
const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
|
||||||
@@ -33,7 +34,9 @@ const CronToolSchema = Type.Object({
|
|||||||
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||||
text: Type.Optional(Type.String()),
|
text: Type.Optional(Type.String()),
|
||||||
mode: optionalStringEnum(CRON_WAKE_MODES),
|
mode: optionalStringEnum(CRON_WAKE_MODES),
|
||||||
contextMessages: Type.Optional(Type.Number()),
|
contextMessages: Type.Optional(
|
||||||
|
Type.Number({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX }),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
type CronToolOptions = {
|
type CronToolOptions = {
|
||||||
@@ -88,7 +91,11 @@ async function buildReminderContextLines(params: {
|
|||||||
gatewayOpts: GatewayCallOptions;
|
gatewayOpts: GatewayCallOptions;
|
||||||
contextMessages: number;
|
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();
|
const sessionKey = params.agentSessionKey?.trim();
|
||||||
if (!sessionKey) return [];
|
if (!sessionKey) return [];
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
@@ -97,13 +104,13 @@ async function buildReminderContextLines(params: {
|
|||||||
try {
|
try {
|
||||||
const res = (await callGatewayTool("chat.history", params.gatewayOpts, {
|
const res = (await callGatewayTool("chat.history", params.gatewayOpts, {
|
||||||
sessionKey: resolvedKey,
|
sessionKey: resolvedKey,
|
||||||
limit: 12,
|
limit: maxMessages,
|
||||||
})) as { messages?: unknown[] };
|
})) as { messages?: unknown[] };
|
||||||
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
||||||
const parsed = messages
|
const parsed = messages
|
||||||
.map((msg) => extractMessageText(msg as ChatMessage))
|
.map((msg) => extractMessageText(msg as ChatMessage))
|
||||||
.filter((msg): msg is { role: string; text: string } => Boolean(msg));
|
.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 [];
|
if (recent.length === 0) return [];
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
let total = 0;
|
let total = 0;
|
||||||
@@ -126,7 +133,7 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool {
|
|||||||
label: "Cron",
|
label: "Cron",
|
||||||
name: "cron",
|
name: "cron",
|
||||||
description:
|
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,
|
parameters: CronToolSchema,
|
||||||
execute: async (_toolCallId, args) => {
|
execute: async (_toolCallId, args) => {
|
||||||
const params = args as Record<string, unknown>;
|
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 job = normalizeCronJobCreate(params.job) ?? params.job;
|
||||||
const contextMessages =
|
const contextMessages =
|
||||||
typeof params.contextMessages === "number" ? params.contextMessages : 0;
|
typeof params.contextMessages === "number" && Number.isFinite(params.contextMessages)
|
||||||
|
? params.contextMessages
|
||||||
|
: 0;
|
||||||
if (
|
if (
|
||||||
job &&
|
job &&
|
||||||
typeof job === "object" &&
|
typeof job === "object" &&
|
||||||
|
|||||||
Reference in New Issue
Block a user