



























@@ -198,14 +198,136 @@ describe("cron tool", () => {
198198expect(callGatewayMock).not.toHaveBeenCalled();
199199});
200200201-it("denies scoped isolated cron runs from using non-remove cron actions", async () => {
201+it("allows scoped isolated cron runs to read cron scheduler status", async () => {
202+callGatewayMock.mockResolvedValueOnce({
203+enabled: true,
204+storePath: "/home/user/.openclaw/cron/jobs.json",
205+jobs: 37,
206+nextWakeAtMs: 1_234,
207+});
202208const tool = createTestCronTool({ selfRemoveOnlyJobId: "job-current" });
203209204-await expect(
205-tool.execute("call-list", {
206-action: "list",
207-}),
208-).rejects.toThrow("Cron tool is restricted to removing the current cron job.");
210+const result = await tool.execute("call-status", {
211+action: "status",
212+timeoutMs: 10_000,
213+});
214+215+const params = expectSingleGatewayCallMethod("cron.status");
216+expect(params).toEqual({});
217+expect(result.details).toEqual({ enabled: true });
218+});
219+220+it("allows scoped isolated cron runs to list only the current job", async () => {
221+callGatewayMock.mockResolvedValueOnce({
222+jobs: [
223+{ id: "job-current", name: "current" },
224+{ id: "job-other", name: "other" },
225+],
226+total: 2,
227+offset: 0,
228+limit: 2,
229+hasMore: false,
230+nextOffset: null,
231+deliveryPreviews: {
232+"job-current": { label: "current", detail: "self" },
233+"job-other": { label: "other", detail: "hidden" },
234+},
235+});
236+const tool = createTestCronTool({
237+agentSessionKey: "agent:agent-123:cron:job-current:run:abc",
238+selfRemoveOnlyJobId: "job-current",
239+});
240+241+const result = await tool.execute("call-list", {
242+action: "list",
243+agentId: "other-agent",
244+includeDisabled: true,
245+});
246+247+const params = expectSingleGatewayCallMethod("cron.list");
248+expect(params).toEqual({ includeDisabled: true, agentId: "agent-123", limit: 200, offset: 0 });
249+expect(result.details).toEqual({
250+jobs: [{ id: "job-current", name: "current" }],
251+total: 1,
252+offset: 0,
253+limit: 1,
254+hasMore: false,
255+nextOffset: null,
256+deliveryPreviews: {
257+"job-current": { label: "current", detail: "self" },
258+},
259+});
260+});
261+262+it("pages scoped isolated cron list until it finds the current job", async () => {
263+callGatewayMock
264+.mockResolvedValueOnce({
265+jobs: Array.from({ length: 200 }, (_, index) => ({
266+id: `job-old-${index}`,
267+name: `old ${index}`,
268+})),
269+total: 201,
270+offset: 0,
271+limit: 200,
272+hasMore: true,
273+nextOffset: 200,
274+deliveryPreviews: {},
275+})
276+.mockResolvedValueOnce({
277+jobs: [{ id: "job-current", name: "current" }],
278+total: 201,
279+offset: 200,
280+limit: 200,
281+hasMore: false,
282+nextOffset: null,
283+deliveryPreviews: {
284+"job-current": { label: "current", detail: "self" },
285+},
286+});
287+const tool = createTestCronTool({
288+agentSessionKey: "agent:agent-123:cron:job-current:run:abc",
289+selfRemoveOnlyJobId: "job-current",
290+});
291+292+const result = await tool.execute("call-list-paged", {
293+action: "list",
294+includeDisabled: true,
295+});
296+297+expect(callGatewayMock).toHaveBeenCalledTimes(2);
298+expect(readGatewayCall(0)).toEqual({
299+method: "cron.list",
300+params: { includeDisabled: true, agentId: "agent-123", limit: 200, offset: 0 },
301+});
302+expect(readGatewayCall(1)).toEqual({
303+method: "cron.list",
304+params: { includeDisabled: true, agentId: "agent-123", limit: 200, offset: 200 },
305+});
306+expect(result.details).toEqual({
307+jobs: [{ id: "job-current", name: "current" }],
308+total: 1,
309+offset: 0,
310+limit: 1,
311+hasMore: false,
312+nextOffset: null,
313+deliveryPreviews: {
314+"job-current": { label: "current", detail: "self" },
315+},
316+});
317+});
318+319+it.each([
320+["add", { action: "add", job: buildReminderAgentTurnJob() }],
321+["update", { action: "update", jobId: "job-current", patch: { enabled: false } }],
322+["run", { action: "run", jobId: "job-current" }],
323+["runs", { action: "runs", jobId: "job-current" }],
324+["wake", { action: "wake", text: "wake up" }],
325+])("denies scoped isolated cron runs from using %s", async (_action, args) => {
326+const tool = createTestCronTool({ selfRemoveOnlyJobId: "job-current" });
327+328+await expect(tool.execute("call-denied", args)).rejects.toThrow(
329+"Cron tool is restricted to removing the current cron job.",
330+);
209331210332expect(callGatewayMock).not.toHaveBeenCalled();
211333});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。