Let bots ask multiple-choice questions before acting

ask_user puts up to six questions on one card (radios for one pick,
checkboxes for several, an optional typed Other) as a [question:ID] chip.
The user's answers are checked against the questions, stored in
bot_questions and sent to the bot as their chat message; Skip says so.
Offered in chat replies and user-started tasks, never in routines, hook
calls or chatroom sessions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-28 11:10:23 +01:00
co-authored by Claude Opus 5.5
parent 07f87ce4ea
commit d7dfe0e78e
45 changed files with 1448 additions and 6 deletions
+10
View File
@@ -3,6 +3,7 @@ import { grabSkillBodyShownChars } from "@/src/functions/backend/db/app-settings
import { AppData } from "@/src/data/app-data";
import {
ActionCheckNote,
QuestionsNote,
AskCrewNote,
SecretsNote,
GraphicsNote,
@@ -53,6 +54,7 @@ import grabAnsweredMessageId from "@/src/functions/backend/db/bots-queue/grab-an
import markReplyAnswered from "@/src/functions/backend/db/bots-queue/mark-reply-answered";
import sendMessageTool from "@/src/harness/tools/bot/send-message-tool";
import grabBotSecretTools from "@/src/harness/tools/bot/secrets/grab-bot-secret-tools";
import askUserTool from "@/src/harness/tools/bot/questions/ask-user-tool";
import type { BotMessageWithAttachments } from "@/src/types";
import type { HarnessMessage } from "@/src/harness/types";
import type { BotJobContext, BotJobOutcome } from "./types";
@@ -324,6 +326,7 @@ export default async function runBotReply({
TasksNote,
TaskReminderNote,
ActionCheckNote,
QuestionsNote,
SecretsNote,
composeEnvsNote({ ...envs, has_computer: computers.length > 0 }),
GraphicsNote,
@@ -366,6 +369,13 @@ export default async function runBotReply({
onRequest: ({ ref }) => proposals.push(ref),
onShow: ({ ref }) => shown.push(ref),
}),
askUserTool({
user_id,
bot_id: bot.id,
session_id,
workspace_id,
onAsk: ({ ref }) => proposals.push(ref),
}),
...(has_past_sessions
? [searchPastSessionsTool(past_scope), readPastSessionTool(past_scope)]
: []),
+21
View File
@@ -5,6 +5,7 @@ import workspaceAdminsNote from "./workspace-admins-note";
import type { BUN_SQLITE_COBBLE_BOTS } from "@/db/types/db";
import {
ActionCheckNote,
QuestionsNote,
SecretsNote,
GraphicsNote,
botFilesNote,
@@ -27,6 +28,7 @@ import openBotSkills, { type OpenBotSkills } from "@/src/harness/tools/skills/op
import openBotKnowledgebases from "@/src/harness/tools/knowledgebases/open-bot-knowledgebases";
import grabBotTools from "@/src/harness/tools/bot/grab-bot-tools";
import grabBotSecretTools from "@/src/harness/tools/bot/secrets/grab-bot-secret-tools";
import askUserTool from "@/src/harness/tools/bot/questions/ask-user-tool";
import createWorkspaceTool from "@/src/harness/tools/bot/create-workspace-tool";
import updateWorkspaceTool from "@/src/harness/tools/bot/update-workspace-tool";
import askToTakeOverTool from "@/src/harness/tools/computer/ask-to-take-over-tool";
@@ -134,6 +136,12 @@ export default async function runBotTask({
* like a routine, and treats the call's payload as data.
*/
const hooked = Boolean(params.hook_id);
/**
* Only a task the user follows in their conversation asks them
* questions: routines and hook calls act on their own, and a
* chatroom's session is hidden from them.
*/
const can_ask = Boolean(session_id) && !scheduled && !hooked && !params.chatroom_id;
let failure = null as string | null;
/**
@@ -207,6 +215,18 @@ export default async function runBotTask({
onRequest: hooked ? undefined : ({ ref }) => take_overs.push(ref),
onShow: ({ ref }) => shown.push(ref),
}),
...(can_ask
? [
askUserTool({
user_id,
bot_id: bot.id,
session_id,
workspace_id,
in_task: true,
onAsk: ({ ref }) => take_overs.push(ref),
}),
]
: []),
...(scheduled || hooked ? [reportRunFailedTool({ onFail: ({ reason }) => (failure = reason) })] : []),
...(computer?.tools || []),
...(job.computer_id
@@ -270,6 +290,7 @@ export default async function runBotTask({
ComputerFilesNote({ computer_id: job.computer_id || 0, dirs }),
TasksNote,
ActionCheckNote,
can_ask ? QuestionsNote : "",
SecretsNote,
GraphicsNote,
workspace_id ? WorkspaceLearningNote : "",