Add bot schedules (routines), open Activity, add Help and a Desktop computer view
Bots create, list, update and delete schedules (cron_jobs, Bun.cron in UTC); a per-minute cron queues due runs as tasks. Routines, Activity (super admins) and Help get their own rail aside. Help covers familiar problems, starting with signing in to Google through Cookie-Editor over the new Desktop (noVNC) view. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { z } from "zod";
|
||||
import type { BUN_SQLITE_COBBLE_COMPUTERS } from "@/db/types/db";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import createCronJob from "@/src/functions/backend/db/cron-jobs/create-cron-job";
|
||||
import describeCronJob from "@/src/utils/describe-cron-job";
|
||||
import type { HarnessTool } from "../../types";
|
||||
|
||||
type Params = {
|
||||
user_id: number | string;
|
||||
bot_id: number | string;
|
||||
/**
|
||||
* Where the run is: the schedule runs there too.
|
||||
*/
|
||||
workspace_id?: number | string | null;
|
||||
/**
|
||||
* The bot's computers where it runs, the current one first.
|
||||
*/
|
||||
computers: BUN_SQLITE_COBBLE_COMPUTERS[];
|
||||
};
|
||||
|
||||
type Args = {
|
||||
title: string;
|
||||
instructions: string;
|
||||
schedule: string;
|
||||
computer_id?: number;
|
||||
max_runs?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lets a bot schedule work for itself (a routine, to the user): its brief
|
||||
* is queued as a task at every run, here, and the result posted in this
|
||||
* conversation.
|
||||
*/
|
||||
export default function createScheduleTool({ user_id, bot_id, workspace_id, computers }: Params): HarnessTool<Args> {
|
||||
const listed = computers.map((computer) => `${computer.id} (${computer.name || computer.kind})`).join(", ");
|
||||
|
||||
return {
|
||||
name: "create_schedule",
|
||||
description: `Schedule work to run on its own, again and again or once (the user sees schedules as routines). At every run your instructions are queued as a task for you, ${
|
||||
workspace_id ? "in this workspace" : "in your own chat"
|
||||
}, and the result is posted here. The schedule is a cron expression in UTC: 5 fields (minute hour day-of-month month day-of-week, e.g. "0 9 * * MON-FRI" for 09:00 UTC on weekdays, "*/30 * * * *" every 30 minutes) or a nickname (@hourly, @daily, @weekly, @monthly). It's ${new Date().toISOString()} now. Convert the user's local time to UTC; if you don't know their time zone, ask, and remember it. At least ${Math.round(
|
||||
AppData.CronJobMinIntervalMs / 60000,
|
||||
)} minutes between runs. For a one-off, pin the date (e.g. "30 14 21 10 *") and set max_runs to 1. ${
|
||||
computers.length
|
||||
? `Runs use the computer you're on when they start unless you pick one: ${listed}.`
|
||||
: `You have no computer here, so runs can't use one.`
|
||||
} Write the instructions as a full brief for a future you with no memory of this chat.`,
|
||||
schema: z.object({
|
||||
title: z.string().min(1).max(80).describe(`A short label, e.g. "Morning inbox digest"`),
|
||||
instructions: z.string().min(1).max(8000),
|
||||
schedule: z.string().min(1).max(100),
|
||||
computer_id: z.number().int().optional(),
|
||||
max_runs: z.number().int().positive().optional().describe("Stop after this many runs; leave out to run until stopped"),
|
||||
}),
|
||||
run: async ({ args }) => {
|
||||
const cron_job = await createCronJob({
|
||||
user_id,
|
||||
bot_id,
|
||||
workspace_id,
|
||||
title: args.title,
|
||||
instructions: args.instructions,
|
||||
schedule: args.schedule,
|
||||
computer_id: args.computer_id,
|
||||
max_runs: args.max_runs,
|
||||
});
|
||||
|
||||
return `Schedule saved: ${describeCronJob({ cron_job })}. Tell the user when it runs, in their time, and that they can pause or change it under Routines.`;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { z } from "zod";
|
||||
import deleteCronJob from "@/src/functions/backend/db/cron-jobs/delete-cron-job";
|
||||
import type { HarnessTool } from "../../types";
|
||||
|
||||
type Params = {
|
||||
user_id: number | string;
|
||||
bot_id: number | string;
|
||||
};
|
||||
|
||||
type Args = {
|
||||
schedule_id: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lets a bot delete one of its own schedules.
|
||||
*/
|
||||
export default function deleteScheduleTool({ user_id, bot_id }: Params): HarnessTool<Args> {
|
||||
return {
|
||||
name: "delete_schedule",
|
||||
description: `Delete one of your schedules for good (ids from list_schedules). To stop it for a while, pause it with update_schedule instead.`,
|
||||
schema: z.object({
|
||||
schedule_id: z.number().int(),
|
||||
}),
|
||||
run: async ({ args }) => {
|
||||
const cron_job = await deleteCronJob({ user_id, bot_id, cron_job_id: args.schedule_id });
|
||||
|
||||
return `Schedule #${cron_job.id} "${cron_job.title}" deleted.`;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { BUN_SQLITE_COBBLE_COMPUTERS } from "@/db/types/db";
|
||||
import type { HarnessTool } from "../../types";
|
||||
import createScheduleTool from "./create-schedule-tool";
|
||||
import deleteScheduleTool from "./delete-schedule-tool";
|
||||
import listSchedulesTool from "./list-schedules-tool";
|
||||
import setWorkspaceComputerTool from "./set-workspace-computer-tool";
|
||||
import updateScheduleTool from "./update-schedule-tool";
|
||||
import updateInstructionsTool from "./update-instructions-tool";
|
||||
import updateMemoryTool from "./update-memory-tool";
|
||||
|
||||
@@ -20,7 +24,8 @@ type Params = {
|
||||
|
||||
/**
|
||||
* The tools a bot works with on every run, bound to that bot and where it
|
||||
* works.
|
||||
* works: its memory and instructions, its schedules, and in a workspace
|
||||
* with several computers switching the default one.
|
||||
*/
|
||||
export default function grabBotTools({
|
||||
user_id,
|
||||
@@ -31,6 +36,10 @@ export default function grabBotTools({
|
||||
return [
|
||||
updateMemoryTool({ user_id, bot_id, workspace_id }),
|
||||
updateInstructionsTool({ user_id, bot_id, workspace_id }),
|
||||
createScheduleTool({ user_id, bot_id, workspace_id, computers }),
|
||||
listSchedulesTool({ user_id, bot_id }),
|
||||
updateScheduleTool({ user_id, bot_id }),
|
||||
deleteScheduleTool({ user_id, bot_id }),
|
||||
...(workspace_id && computers.length > 1
|
||||
? [setWorkspaceComputerTool({ user_id, workspace_id, computers })]
|
||||
: []),
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { z } from "zod";
|
||||
import grabCronJobs from "@/src/functions/backend/db/cron-jobs/grab-cron-jobs";
|
||||
import describeCronJob from "@/src/utils/describe-cron-job";
|
||||
import type { HarnessTool } from "../../types";
|
||||
|
||||
type Params = {
|
||||
user_id: number | string;
|
||||
bot_id: number | string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lets a bot see its own schedules, everywhere it works.
|
||||
*/
|
||||
export default function listSchedulesTool({ user_id, bot_id }: Params): HarnessTool<Record<string, never>> {
|
||||
return {
|
||||
name: "list_schedules",
|
||||
description: `List your schedules (routines): id, cron expression (UTC), where each runs, whether it's paused, its next run and its last error.`,
|
||||
schema: z.object({}),
|
||||
run: async () => {
|
||||
const cron_jobs = await grabCronJobs({ user_id, bot_id });
|
||||
|
||||
if (!cron_jobs.length) return `You have no schedules.`;
|
||||
|
||||
return cron_jobs.map((cron_job) => `- ${describeCronJob({ cron_job })}`).join("\n");
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { z } from "zod";
|
||||
import updateCronJob from "@/src/functions/backend/db/cron-jobs/update-cron-job";
|
||||
import describeCronJob from "@/src/utils/describe-cron-job";
|
||||
import type { HarnessTool } from "../../types";
|
||||
|
||||
type Params = {
|
||||
user_id: number | string;
|
||||
bot_id: number | string;
|
||||
};
|
||||
|
||||
type Args = {
|
||||
schedule_id: number;
|
||||
title?: string;
|
||||
instructions?: string;
|
||||
schedule?: string;
|
||||
enabled?: boolean;
|
||||
max_runs?: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lets a bot change, pause or resume one of its own schedules.
|
||||
*/
|
||||
export default function updateScheduleTool({ user_id, bot_id }: Params): HarnessTool<Args> {
|
||||
return {
|
||||
name: "update_schedule",
|
||||
description: `Change one of your schedules (ids from list_schedules): its title, instructions, cron expression (UTC), or pause it (enabled false) and resume it (enabled true). Only the fields you give change; max_runs null runs it until stopped.`,
|
||||
schema: z.object({
|
||||
schedule_id: z.number().int(),
|
||||
title: z.string().min(1).max(80).optional(),
|
||||
instructions: z.string().min(1).max(8000).optional(),
|
||||
schedule: z.string().min(1).max(100).optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
max_runs: z.number().int().positive().nullable().optional(),
|
||||
}),
|
||||
run: async ({ args }) => {
|
||||
const { schedule_id, ...data } = args;
|
||||
const cron_job = await updateCronJob({ user_id, bot_id, cron_job_id: schedule_id, data });
|
||||
|
||||
return `Schedule updated: ${describeCronJob({ cron_job })}.`;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import type { BUN_SQLITE_COBBLE_COMPUTERS } from "@/db/types/db";
|
||||
import { ComputerViewModes, type ComputerViewMode } from "@/src/dict/computer-dict";
|
||||
import { HelpPath, HelpSignInTopicId } from "@/src/dict/help-dict";
|
||||
import takeOverRef from "@/src/utils/take-over-ref";
|
||||
import type { HarnessTool } from "../../types";
|
||||
|
||||
@@ -40,7 +41,7 @@ export default function askToTakeOverTool({ computers, in_task, onAsk }: Params)
|
||||
|
||||
return {
|
||||
name: "ask_user_to_take_over",
|
||||
description: `Ask the user to take over one of your computers when only they can do something on it: log in, solve a captcha, enter a code, approve a prompt, or show you something. They get a button that opens it in the mode you pick: browser (the web browser), terminal (a shell) or code (the code editor). Computers: ${listed}. Defaults to ${computers[0]?.id}.`,
|
||||
description: `Ask the user to take over one of your computers when only they can do something on it: log in, solve a captcha, enter a code, approve a prompt, or show you something. They get a button that opens it in the mode you pick: browser (the web browser), terminal (a shell), code (the code editor) or desktop (the whole screen, with the browser's toolbar and extensions). When a site refuses to sign in on the computer (Google often does: "This browser or app may not be secure"), pick desktop and point the user to the guide at ${HelpPath}#${HelpSignInTopicId}: they bring their session over with the Cookie-Editor extension. Computers: ${listed}. Defaults to ${computers[0]?.id}.`,
|
||||
schema: z.object({
|
||||
computer_id: z.number().int().optional(),
|
||||
mode: z.enum(Modes),
|
||||
|
||||
Reference in New Issue
Block a user