feat: tool approval and a question tool for ai agent steps (#11826)

* feat(ai-agent): built-in approval tool that parks the agent

An AI agent step can now be given a built-in "Request approval" tool. The model
calls `request_approval` with a message; the agent stores its conversation in
the new `ai_agent_checkpoint` table, releases its worker by parking its own
queue row like a workflow-as-code parent, and resumes with the answer (approved,
approver, comment) or a timeout once someone answers from the run page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): tie approval answers to their request and hold the turn

- A turn that asks for approval runs none of its other calls; they are refused
  so the model repeats them after the answer.
- The run page sends the request's call id. An answer naming another request is
  ignored and the agent parks again for the time left; `suspend` is zeroed when
  the answer is read, so answers sent while the agent runs are refused.
- The checkpoint is read whatever the current roster holds, the answer's
  outcome is written by position, and a cancel that beats the park completes
  the job as cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): say why calls beside an invalid approval request were refused

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): one approval form per request, none on a finished run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-agent): answer approval requests from a flow's chat

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): show chat approvals for agents that do not stream

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-agent): approval as a per-tool setting, built-in tool asks a question

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): say when a nested agent leaves out tools that require approval

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-agent): show chat approvals and questions on the tool's own card

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-agent): question choices, and approval buttons shared with approval steps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-agent): show a waiting agent as suspended, and refused calls as not run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): each added chat card shows its own call, answered marker keyed by job

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ai-agent): approve and reject a held call through resume_suspended

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-agent): require approval in a tool settings tab, plain descriptions on built-in tool panes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): sentence case for the web search tool label

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-agent): built-in tool drawers explain themselves in the header subtitle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): clearer question tool subtitle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai-agent): badge the tool settings tab like run settings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(ai-agent): regenerate system prompts and MCP session files

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-10-26 19:22:45 +01:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a2b91fd17e
commit a1bc9ec0c2
60 changed files with 2193 additions and 199 deletions
+11 -5
View File
@@ -9,6 +9,12 @@
// frontend/src/lib/components/flows/agentToolUtils.ts applies the same rules.
const TOOL_NAME_REGEX = /^[a-zA-Z0-9_]+$/;
const WEBSEARCH_ENABLED_NAME = "__wm_web_search";
// The worker's `*_ENABLED_NAME`s: built-in tools, each named by its reserved name.
const BUILTIN_ENABLED_NAMES = new Set([
WEBSEARCH_ENABLED_NAME,
"__wm_question",
]);
const BUILTIN_TOOL_TYPES = new Set(["websearch", "question"]);
// `forbiddenIds` in frontend/src/lib/components/flows/idUtils.ts.
const RESERVED_IDS = new Set([
@@ -134,10 +140,10 @@ function checkAgentTools(
if (!Array.isArray(tools)) return;
const seen = new Map<string, number>();
tools.forEach((tool: any, i: number) => {
// An mcp entry exposes its server's own tool names and a websearch entry is
// named by `WEBSEARCH_ENABLED_NAME`; neither summary reaches the model.
// An mcp entry exposes its server's own tool names and a built-in entry is
// named by its reserved name; neither summary reaches the model.
const kind = tool?.value?.tool_type;
if (kind === "mcp" || kind === "websearch") return;
if (kind === "mcp" || BUILTIN_TOOL_TYPES.has(kind)) return;
const at = `${agent.pointer}/value/tools/${i}`;
const name = tool?.summary;
if (typeof name !== "string" || name.length === 0) {
@@ -152,9 +158,9 @@ function checkAgentTools(
);
return;
}
if (name === WEBSEARCH_ENABLED_NAME) {
if (BUILTIN_ENABLED_NAMES.has(name)) {
report.errors.push(
`${at} tool name '${name}' is reserved for enabling web search`,
`${at} tool name '${name}' is reserved for a built-in tool`,
);
return;
}
+8 -5
View File
File diff suppressed because one or more lines are too long