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]>
This commit is contained in:
@@ -219,7 +219,7 @@ export default function delegateTaskTool({
|
||||
|
||||
if (open) {
|
||||
throw new Error(
|
||||
`${worker.name} already has task #${open.id} "${title}" ${open.status == "pending" ? "queued" : "under way"}. Don't hand it over again; it's reported back here when they finish.`,
|
||||
`${worker.name} already has run #${open.id} "${title}" ${open.status == "pending" ? "queued" : "under way"}. Don't hand it over again; it's reported back here when they finish.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -244,9 +244,9 @@ export default function delegateTaskTool({
|
||||
}),
|
||||
});
|
||||
|
||||
return `Task #${job_id} is with ${worker.name}${
|
||||
return `Run #${job_id} is with ${worker.name}${
|
||||
target_workspace_id ? ` in workspace ${target_workspace_id}` : " in their own chat"
|
||||
}.${joined} Their result will be in that conversation and reported back here.`;
|
||||
} (a run, not a task on the to-do list: list_tasks doesn't show it).${joined} Their result will be in that conversation and reported back here.`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function grabBotSecretTools({
|
||||
}: Params): HarnessTool[] {
|
||||
return [
|
||||
...(session_id && onRequest
|
||||
? [requestSecretTool({ user_id, bot_id, session_id, workspace_id, in_task, onRequest })]
|
||||
? [requestSecretTool({ user_id, bot_id, session_id, workspace_id, computer_id, in_task, onRequest })]
|
||||
: []),
|
||||
...(onShow ? [showSecretTool({ user_id, bot_id, workspace_id, computer_id, onShow })] : []),
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import grabReachableEnvs from "@/src/functions/backend/db/envs/grab-reachable-envs";
|
||||
import createSecretRequest from "@/src/functions/backend/db/secret-requests/create-secret-request";
|
||||
import secretRequestRef from "@/src/utils/secret-request-ref";
|
||||
import type { HarnessTool } from "../../../types";
|
||||
@@ -11,6 +12,10 @@ type Params = {
|
||||
*/
|
||||
session_id: number | string;
|
||||
workspace_id?: number | string | null;
|
||||
/**
|
||||
* The computer the run is on, whose envs count as already set.
|
||||
*/
|
||||
computer_id?: number | string | null;
|
||||
/**
|
||||
* Whether the run is a task, which stops and reports rather than waits.
|
||||
*/
|
||||
@@ -25,6 +30,7 @@ type Args = {
|
||||
key: string;
|
||||
description: string;
|
||||
reason: string;
|
||||
replace?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -38,33 +44,51 @@ export default function requestSecretTool({
|
||||
bot_id,
|
||||
session_id,
|
||||
workspace_id,
|
||||
computer_id,
|
||||
in_task,
|
||||
onRequest,
|
||||
}: Params): HarnessTool<Args> {
|
||||
return {
|
||||
name: "request_secret",
|
||||
description: `Ask the user for a secret you need (a password, token or API key) through a masked card in the chat, never by asking them to type it into a message: what they type there goes to you and the model provider, the card's value never does. It's saved as their own secret for you, under key, and set as $KEY in your shell (and {{env.KEY}} for tools) on this user's later runs; you never see the value. Check your envs first: if the key is already set, don't ask. Their answer comes as a message saying it's saved${
|
||||
description: `Ask the user for a secret you need (a password, token or API key) through a masked card in the chat, never by asking them to type it into a message: what they type there goes to you and the model provider, the card's value never does. It's saved as their own secret for you, under key, and set as $KEY in your shell (and {{env.KEY}} for tools) on this user's later runs; you never see the value. A key listed in "Your envs" is already set (the workspace, the computer or the user may have saved it): don't ask for it, use it. Their answer comes as a message saying it's saved${
|
||||
in_task ? `. In a task, ask, then stop and report that you're waiting for it.` : `.`
|
||||
}`,
|
||||
schema: z.object({
|
||||
key: z.string().trim().min(1).max(128).describe("The env key to save it under, e.g. GITHUB_TOKEN"),
|
||||
description: z.string().trim().min(1).max(500).describe("What it is, shown on the card, e.g. \"GitHub personal access token with repo scope\""),
|
||||
reason: z.string().trim().min(10, "Say why you need it").max(2000).describe("Why you need it and what you'll do with it, shown on the card as your words"),
|
||||
replace: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Only when the user asked to replace a key you already have: their answer is saved as their own and wins over the existing one."),
|
||||
}),
|
||||
run: async ({ args }) => {
|
||||
const key = args.key.trim();
|
||||
|
||||
if (!args.replace) {
|
||||
const { on_computer, tools_only } = await grabReachableEnvs({ bot_id, user_id, workspace_id, computer_id });
|
||||
const existing = [...on_computer, ...tools_only].find((env) => env.key == key);
|
||||
|
||||
if (existing) {
|
||||
throw new Error(
|
||||
`${key} is already set for you here (${existing.paradigm == "user" ? "the user's own secret" : `a ${existing.paradigm} env`}), so no card was sent. Use it as $${key} in a task on your computer or {{env.${key}}} in tools. Ask again with replace only if the user wants a new value.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const id = await createSecretRequest({
|
||||
user_id,
|
||||
bot_id,
|
||||
session_id,
|
||||
workspace_id,
|
||||
key: args.key,
|
||||
key,
|
||||
description: args.description,
|
||||
reason: args.reason,
|
||||
});
|
||||
|
||||
onRequest({ ref: secretRequestRef({ id }) });
|
||||
|
||||
return `Asked the user for ${args.key.trim()}: the card is on your message. Their answer arrives as a message once it's saved; then it's $${args.key.trim()} on your computers. Never ask them to paste it in the chat.`;
|
||||
return `Asked the user for ${key}: the card is on your message. Their answer arrives as a message once it's saved; then it's $${key} on your computers. Never ask them to paste it in the chat.`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import grabComputerEnvs from "@/src/functions/backend/db/envs/grab-computer-envs";
|
||||
import grabRunEnvs from "@/src/functions/backend/db/extensions/grab-run-envs";
|
||||
import grabReachableEnvs from "@/src/functions/backend/db/envs/grab-reachable-envs";
|
||||
import secretRef from "@/src/utils/secret-ref";
|
||||
import type { HarnessTool } from "../../../types";
|
||||
|
||||
@@ -30,18 +29,14 @@ type Args = {
|
||||
export default function showSecretTool({ user_id, bot_id, workspace_id, computer_id, onShow }: Params): HarnessTool<Args> {
|
||||
return {
|
||||
name: "show_secret",
|
||||
description: `Show the user one of your envs (by key, e.g. a password or token they saved or you captured) without reading it: you get a [secret:ID] ref to put in your message, which the user sees masked with Reveal and Copy. Never print a secret with a command to show it. Only its owner (for a personal secret) or a super admin can reveal it.`,
|
||||
description: `Show the user one of your envs (by key, e.g. a password or token they saved or you captured) without reading it: you get a [secret:ID] ref to put in your message, which the user sees masked with Reveal and Copy. Only when the user wants to see it: to check which envs you have, read "Your envs" in your instructions instead. Never print a secret with a command to show it. Only its owner (for a personal secret) or a super admin can reveal it.`,
|
||||
schema: z.object({
|
||||
key: z.string().trim().min(1).max(128),
|
||||
}),
|
||||
run: async ({ args }) => {
|
||||
const key = args.key.trim();
|
||||
const on_computer = computer_id
|
||||
? await grabComputerEnvs({ computer_id, bot_id, user_id, workspace_id })
|
||||
: [];
|
||||
const env =
|
||||
on_computer.find((e) => e.key == key) ||
|
||||
(await grabRunEnvs({ bot_id, user_id, workspace_id })).find((e) => e.key == key);
|
||||
const { on_computer, tools_only } = await grabReachableEnvs({ bot_id, user_id, workspace_id, computer_id });
|
||||
const env = [...on_computer, ...tools_only].find((e) => e.key == key);
|
||||
|
||||
if (!env) return `No env called ${key} reaches you here. Ask the user with request_secret if you need it.`;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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";
|
||||
|
||||
@@ -25,11 +26,17 @@ 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,
|
||||
@@ -44,7 +51,7 @@ export default function startTaskTool({
|
||||
|
||||
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. Write the instructions as a full brief for yourself: what to do, where, and how you'll know it's done. ${
|
||||
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.`
|
||||
@@ -53,18 +60,30 @@ export default function startTaskTool({
|
||||
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 open = await grabOpenBotTask({ bot_id, session_id, title: args.title });
|
||||
const same = await grabOpenBotTask({ bot_id, session_id, title: args.title });
|
||||
|
||||
if (open) {
|
||||
if (same) {
|
||||
throw new Error(
|
||||
`Task #${open.id} "${args.title}" is already ${open.status == "pending" ? "queued" : "under way"}. Don't start it again; its result will be posted here.`,
|
||||
`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,
|
||||
@@ -75,7 +94,13 @@ export default function startTaskTool({
|
||||
computer_id: computer_id || undefined,
|
||||
});
|
||||
|
||||
return `Task #${job_id} queued; you'll work on it yourself in the background. 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.`;
|
||||
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.`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user