Open computers from the chat, let bots ask for a take-over, show task progress

- Serve each computer to its owner under /computers/<id>/ (HTTP and
  websockets proxied to the AIO sandbox) and show it in a shared
  ComputerView with browser, terminal and code modes, opened from the
  computers modal or a take-over button in the thread.
- ask_user_to_take_over lets a bot hand a computer to the user for a
  login or captcha; the message gets a take-over button and needs-you,
  and the viewer hands it back with a chat message.
- While a task runs the thread shows "<bot> is working..." with the
  task's latest step; steps read as plain words, not tool names.
- A coordinator with no workers no longer claims to delegate, and bots
  describe started tasks in the first person.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 05:35:44 +01:00
co-authored by Claude Opus 5.5
parent b1023ba8ff
commit 56daf0c462
44 changed files with 1289 additions and 75 deletions
+25 -9
View File
@@ -1,6 +1,6 @@
import type { BUN_SQLITE_COBBLE_BOTS } from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import { ChatReplyNote, CoordinatorReplyNote } from "@/src/data/harness-prompts";
import { ChatReplyNote, CoordinatorReplyNote, CoordinatorSoloNote } from "@/src/data/harness-prompts";
import grabBotMessages from "@/src/functions/backend/db/bots/grab-bot-messages";
import grabBotPrompt from "@/src/functions/backend/db/bots/grab-bot-prompt";
import grabBotComputers from "@/src/functions/backend/db/computers/grab-bot-computers";
@@ -9,6 +9,7 @@ import grabBotChatTools from "@/src/harness/tools/bot/grab-bot-chat-tools";
import botMessagesToHarness from "./bot-messages-to-harness";
import composeComputersNote from "./compose-computers-note";
import loadAttachmentParts from "./load-attachment-parts";
import withTakeOverRefs from "./with-take-over-refs";
import type { BotJobContext, BotJobOutcome } from "./types";
type Params = BotJobContext & {
@@ -40,12 +41,32 @@ export default async function runBotReply({
const prompt = await grabBotPrompt({ bot_id: bot.id, user_id: bot.user_id });
const computers = await grabBotComputers({ bot_id: bot.id, user_id: bot.user_id });
const take_overs: string[] = [];
const tools = await grabBotChatTools({
bot,
user_id: bot.user_id,
computers,
message_id: job.message_id || undefined,
onTakeOver: ({ ref }) => take_overs.push(ref),
});
/**
* delegate_task is only there while the coordinator has workers.
*/
const crew_note =
bot.kind != "coordinator"
? ""
: tools.some((tool) => tool.name == "delegate_task")
? CoordinatorReplyNote
: CoordinatorSoloNote;
const result = await runTextLoop({
system: [
prompt,
ChatReplyNote,
composeComputersNote({ computers, is_coordinator: bot.kind == "coordinator" }),
bot.kind == "coordinator" ? CoordinatorReplyNote : "",
crew_note,
`Now: ${new Date().toUTCString()}.`,
]
.filter(Boolean)
@@ -54,17 +75,12 @@ export default async function runBotReply({
messages: history,
parts: await loadAttachmentParts({ messages: history }),
}),
tools: await grabBotChatTools({
bot,
user_id: bot.user_id,
computers,
message_id: job.message_id || undefined,
}),
tools,
max_steps: AppData.BotReplyMaxToolSteps,
bot_id: bot.id,
signal,
onStep: report,
});
return { result: result.text };
return withTakeOverRefs({ text: result.text, refs: take_overs });
}