Files
cobble/src/harness/tools/bot/start-task-tool.ts
T
tbenandClaude Opus 5.5 8890d80e6e Tell replies their envs and call queued work runs
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]>
2026-10-27 19:33:45 +01:00

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.`;
},
};
}