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
@@ -7,15 +7,18 @@ import useLiveBots from "@/src/hooks/use-live-bots";
import useServerEvent from "@/src/hooks/use-server-event";
import { BotComputersContext } from "../(data)/bot-computers-context";
import useBotComputers from "../(hooks)/use-bot-computers";
import useTaskProgress from "../(hooks)/use-task-progress";
import ConversationHeader from "../(partials)/conversation-header";
import MessageThread from "../(partials)/message-thread";
import Composer from "../(partials)/composer";
import ComputerViewerModal from "../(partials)/computer-viewer-modal";
/**
* The conversation column: header, bottom-anchored thread, composer, the
* thread and composer held to a readable width.
* The bot's computers are shared with the header's modal and the computer
* buttons in the thread, so a switch from either shows in both.
* buttons in the thread, so a switch from either shows in both; either
* can open one in the viewer.
*/
export default function Conversation() {
const { pageProps } = useContext(AppContext);
@@ -25,20 +28,32 @@ export default function Conversation() {
pageProps?.bot_messages || [],
);
const bots = useLiveBots({ bots: pageProps?.bots || [], open_bot_id: bot?.id || undefined });
const bots = useLiveBots({
bots: pageProps?.bots || [],
open_bot_id: bot?.id || undefined,
});
const live_bot = bots.find((b) => b.id == bot?.id) || bot;
const bot_computers = useBotComputers({
bot_id: bot?.id || 0,
computers: pageProps?.bot_computers || [],
});
const progress = useTaskProgress({
bot_id: bot?.id || 0,
task: pageProps?.bot_task || null,
});
console.log(`Refreshing ...`);
useServerEvent({
event: "server:bot-message",
onData: (message: BotMessageWithAttachments) => {
if (message.bot_id != bot?.id) return;
setMessages((prev) =>
prev.some((m) => m.id == message.id) ? prev : [...prev, message],
prev.some((m) => m.id == message.id)
? prev
: [...prev, message],
);
if (message.author == "bot") {
@@ -59,16 +74,23 @@ export default function Conversation() {
bot={live_bot}
harnesses={pageProps?.bot_harness_overview?.harnesses}
/>
<MessageThread bot={live_bot} messages={messages} />
<MessageThread
bot={live_bot}
messages={messages}
progress={progress}
/>
<Composer
bot={bot}
onSent={({ message }) =>
setMessages((prev) =>
prev.some((m) => m.id == message.id) ? prev : [...prev, message],
prev.some((m) => m.id == message.id)
? prev
: [...prev, message],
)
}
/>
</Stack>
<ComputerViewerModal bot={live_bot} />
</BotComputersContext.Provider>
);
}