import type { AgentTool } from "../internal/harness.js"; import type { ToolPolicy } from "./tool-policy.js"; /** The reserved tool name for AskUserQuestion — shared so the durable-resume path (runtask) can recognize it * as a content-ask whose SHOWN questions must not be rewritten by an operator `updatedInput` (design/80 D-F). */ export declare const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion"; /** * AskUserQuestion (design/64 §5). A model-callable tool that lets the agent ask the USER a structured * multiple-choice question (which approach? which of these?) — distinct from the design/37 permission `ask` * ("may I run this tool?"). The model only EMITS the call; the harness routes it to the caller via the * {@link OnQuestion} seam (deployment supplies a real human / UI / HITL). **LLM has zero IO** — it cannot * collect answers itself. Headless (no `onQuestion`) returns a typed "no human available" so the model * proceeds with a default instead of hanging. **design/80 D-F (answer fence):** the answer no longer assumes a * trusted control plane — in the Supervisor topology it arrives over the wire (an operator `/decide`), an * injection vector. So a `selected` value is echoed as a chosen option ONLY if it is one of the offered option * labels (`selected ⊆ options` — the model's own text), and the operator's free-text (`note`, plus any off-list * input) is wrapped in an untrusted-DATA fence so it can never be read as instructions. Abort while waiting is * handled by the §9 interrupt path (the pending call is closed). Opt-in: mounted only when `onQuestion` is wired. */ export interface AskQuestionOption { /** The choice text the user picks. */ label: string; /** What this option means / its trade-off. */ description: string; /** design/116 W3 (CC parity): optional preview content (mockup / code snippet / comparison) shown while this * option is focused. The engine only TRANSPORTS it — it rides the {@link AskQuestionRequest} to `onQuestion` * untouched; rendering (markdown box, side-by-side, …) is the shell/HITL surface's job. */ preview?: string; } export interface AskQuestion { /** The full question. */ question: string; /** A short header/label (≤ ~12 chars) for UIs. */ header: string; /** 2-4 mutually-exclusive choices (unless multiSelect). */ options: AskQuestionOption[]; /** Allow selecting more than one option. */ multiSelect?: boolean; } export interface AskQuestionRequest { toolCallId: string; /** 1-4 questions. */ questions: AskQuestion[]; /** Runner-filled source identity (parallel to {@link import("./tool-policy.js").AskRequest}.principal, * design S1d §1): the issuing task's end-user principal (design/62), for per-user attribution by an * AGGREGATING onQuestion. Worker/tool cannot set it. Undefined ⇒ none (a non-aggregating onQuestion * ignores it — additive, no-op). `readonly`: Runner-populated metadata. Populated ONLY on the * synchronous onQuestion path — a durable suspend does NOT invoke it; v1 requires `checkpointStore` * unset (design S1d §1.4 P-a). */ readonly principal?: string; /** Runner-filled source-task identity (= issuing task's session id), for per-worker attribution by an * aggregating onQuestion. **worker-unforgeable for Runner-created delegated subagents/team members** * (those child specs do not pass TaskSpec.sessionId — the Runner mints the id); a top-level caller CAN * still set `TaskSpec.sessionId`, so this is NOT a globally unforgeable run id (design S1d §1.3). * `readonly` + same sync-path-only / P-a caveat as `principal`. */ readonly sourceTaskId?: string; } /** One question's answer: the selected option label(s) + an optional free-text note. */ export interface QuestionAnswerItem { header: string; selected: string[]; note?: string; } export interface QuestionAnswer { answers: QuestionAnswerItem[]; } /** * The content-ask seam (design/64 §5.2): the deployment routes an AskUserQuestion to a real human/UI and * returns their selection. Parallel to (and independent of) {@link import("./tool-policy.js").OnAsk} — a * permission ask is allow/deny, a content ask is a choice. `signal` fires on task abort: race your wait so * an unanswered question is released at the deadline (it does not hang the worker). */ export type OnQuestion = (req: AskQuestionRequest, signal?: AbortSignal) => Promise; /** * Durable ask (design/64 TC-5.4 — ABOVE CC, the disconnected-human case): instead of the headless * "proceed with your best judgment" default, an AskUserQuestion **suspends the task durably** and the * answer resumes it — built ENTIRELY from existing primitives, no new gate kind: * * 1. Run the task with `durableApproval` + a durable `CheckpointStore` (+ durable session/offload * stores) and `combinePolicies(createDurableQuestionPolicy(), yourPolicy)`. The policy adjudicates * the AskUserQuestion call `ask`; in durable mode that suspends BEFORE the tool executes — * `onQuestion` is never called on the suspend side, so mount the tool with * {@link QUESTION_AWAITS_RESUME} when no live human is connected. * 2. The HITL surface reads the questions from `checkpoint.pendingAction.args.questions` and collects * the human's {@link QuestionAnswer} out-of-band (hours/days later, any replica). * 3. `runner.resume(token, { gate: "policy_ask", decision: "allow", boundCallId: * checkpoint.pendingAction.toolCallId }, { ...taskConfig, onQuestion: async () => answer })` — the resume * re-mounts the tool with the answer closure; the pending call executes against it and the model * continues with "The user answered: …". `boundCallId` (design/80 D-1) must name the pending call the * human answered, or the resume is rejected (`checkpoint.invalid_outcome`). * * A deployment that wants the CC-style threshold form ("wait N minutes for a live human, then go * durable") implements it inside its own policy: return `allow` while a human is connected (the live * `onQuestion` answers synchronously) and `ask` when none is — the sync-vs-durable choice is exactly * one policy decision. */ export declare function createDurableQuestionPolicy(): ToolPolicy; /** * Placeholder {@link OnQuestion} that mounts the AskUserQuestion tool on a worker with NO live human * (the durable-ask suspend side, TC-5.4). It must never actually run — the durable-question policy * suspends the call before execution — so reaching it means the wiring is missing the policy/store; * it throws a configuration error rather than silently telling the model "no human is available". */ export declare const QUESTION_AWAITS_RESUME: OnQuestion; /** Build the AskUserQuestion tool. When `onQuestion` is undefined the tool still mounts but returns the * headless default (so a run started without a human in the loop never hangs). */ export declare function createAskUserQuestionTool(onQuestion?: OnQuestion, /** design S1d §1.2: Runner-filled source identity, merged into the {@link AskQuestionRequest} so an * aggregating onQuestion can attribute per worker/user. Omitted ⇒ fields stay undefined (no-op). */ source?: { principal?: string; sourceTaskId?: string; }): AgentTool; //# sourceMappingURL=ask-question.d.ts.map