Chat replies now list the envs that reach them (keys and descriptions, never values), so a bot no longer asks for keys that are set or "checks" them with show_secret or printenv. request_secret refuses a key that's already set unless the user asked to replace it, and search_computers says envs aren't loaded in its shell. start_task and delegate_task results say "Run #N" and that list_tasks doesn't show it, since bots sent users to the to-do list for queue jobs. start_task refuses a second open run in the session on the same computer unless it's separate work, and says how many runs are ahead on that computer. Co-Authored-By: Claude Opus 5.5 <[email protected]>
107 lines
5.0 KiB
TypeScript
107 lines
5.0 KiB
TypeScript
import { z } from "zod";
|
|
import type { BUN_SQLITE_COBBLE_COMPUTERS } from "@/db/types/db";
|
|
import enqueueBotJob from "@/src/functions/backend/db/bots-queue/enqueue-bot-job";
|
|
import countComputerTaskJobs from "@/src/functions/backend/db/bots-queue/count-computer-task-jobs";
|
|
import grabOpenBotTask from "@/src/functions/backend/db/bots-queue/grab-open-bot-task";
|
|
import type { HarnessTool } from "../../types";
|
|
|
|
type Params = {
|
|
user_id: number | string;
|
|
bot_id: number | string;
|
|
/**
|
|
* The session the task is started from; its result is posted there.
|
|
*/
|
|
session_id: number | string;
|
|
/**
|
|
* The bot's computers, the current one first (`grabBotComputers`).
|
|
*/
|
|
computers: BUN_SQLITE_COBBLE_COMPUTERS[];
|
|
/**
|
|
* The user message that asked for the work.
|
|
*/
|
|
message_id?: number | string;
|
|
};
|
|
|
|
type Args = {
|
|
title: string;
|
|
instructions: string;
|
|
computer_id?: number;
|
|
separate?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Lets a bot queue long-running work for itself. The task runs in the
|
|
* background with the computer's tools and posts its result to the chat.
|
|
* It's a run in the queue (`bots_queue`), not a task on the to-do list
|
|
* (`tasks`), so it's called a run: told "Task #52", bots and users look
|
|
* for it in `list_tasks` and don't find it. While a run is open in the
|
|
* session on that computer a new one is refused unless it's separate
|
|
* work, since it would only wait behind it (one task per computer).
|
|
*/
|
|
export default function startTaskTool({
|
|
user_id,
|
|
bot_id,
|
|
session_id,
|
|
computers,
|
|
message_id,
|
|
}: Params): HarnessTool<Args> {
|
|
const listed = computers
|
|
.map((computer) => `${computer.id} (${computer.name || computer.kind})`)
|
|
.join(", ");
|
|
|
|
return {
|
|
name: "start_task",
|
|
description: `Queue work that takes a while or needs a computer. It runs in the background and its result is posted in this chat. It's a run, not a task on the to-do list: list_tasks never shows it, so don't send the user there to find it. Write the instructions as a full brief for yourself: what to do, where, and how you'll know it's done. Tasks run one at a time per computer, so while one of yours is open here, a new one only waits behind it: don't start the same work again with another title. ${
|
|
computers.length
|
|
? `Computers you can use: ${listed}. Defaults to ${computers[0]!.id}, the one you're on.`
|
|
: `You have no computer, so the task can't run commands or browse.`
|
|
}`,
|
|
schema: z.object({
|
|
title: z.string().min(1).max(80).describe("A short label, e.g. \"Check the backup logs\""),
|
|
instructions: z.string().min(1).max(8000),
|
|
computer_id: z.number().int().optional(),
|
|
separate: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("Only for different work while another run of yours is open here on that computer, never to retry or redo it."),
|
|
}),
|
|
run: async ({ args }) => {
|
|
const same = await grabOpenBotTask({ bot_id, session_id, title: args.title });
|
|
|
|
if (same) {
|
|
throw new Error(
|
|
`Run #${same.id} "${args.title}" is already ${same.status == "pending" ? "queued" : "under way"}. Don't start it again; its result will be posted here.`,
|
|
);
|
|
}
|
|
|
|
const computer_id = args.computer_id ?? computers[0]?.id ?? undefined;
|
|
|
|
const open = args.separate ? null : await grabOpenBotTask({ bot_id, session_id, computer_id });
|
|
|
|
if (open) {
|
|
throw new Error(
|
|
`Run #${open.id} "${open.title || "Task"}" is still ${open.status == "pending" ? "queued" : "under way"} here on that computer, and a new one would only wait behind it. If this is the same work, don't start it again: its result will be posted here (the user can Stop it to start over). Only for different work, call start_task again with separate.`,
|
|
);
|
|
}
|
|
|
|
const job_id = await enqueueBotJob({
|
|
user_id,
|
|
bot_id,
|
|
session_id,
|
|
title: args.title,
|
|
instructions: args.instructions,
|
|
message_id,
|
|
computer_id: computer_id || undefined,
|
|
});
|
|
|
|
const ahead = computer_id ? await countComputerTaskJobs({ computer_id, except_job_id: job_id }) : 0;
|
|
|
|
return `Run #${job_id} queued; you'll work on it yourself in the background. It's not on the task list (list_tasks), so never tell the user to look for it there. ${
|
|
ahead
|
|
? `The computer has ${ahead} other ${ahead == 1 ? "run" : "runs"} queued or working (they go one at a time), so it waits for ${ahead == 1 ? "it" : "them"} first: tell the user it's queued behind other work, not that it's running. `
|
|
: ""
|
|
}Tell the user in one sentence, in the first person, what you're doing (e.g. "I'm checking your inbox now. I'll report back when I'm done."); the result will be posted here when it's done.`;
|
|
},
|
|
};
|
|
}
|