request_user_input

request_user_input

Metadata

Name

request_user_input

Updated

last month

Source

GitHub source

Risk

Low

Requires consent

false

Affected scope

user

Code

/**
 * mcp/skills/requestUserInput.ts — request_user_input synthetic user-wait gate
 *
 * Third user-wait gate alongside `wait_for_user_ack` (button picker) and
 * `present_preview` (multi-select card). This one captures **free-text input**
 * from the user mid-plan and returns it as `{ value: string }`.
 *
 * ...
 */

import { z } from "zod";

// -- Meta ---------------------------------------------------------------------

export const meta = {
  name: "request_user_input",
  description:
    "Pauses the plan and captures a single free-text input from the user. " +
    "Use when the agent needs a value (email, hostname, IP, account ID, etc.) " +
    "that cannot be auto-detected and isn't covered by a button-picker " +
    "(wait_for_user_ack, max 4 options) or a multi-select card (present_preview). " +
    "Returns { value: string } — empty string on cancel/timeout. Routed " +
    "through G4's runUserInputGate; runs OUTSIDE TOOL_TIMEOUT_MS.",
  riskLevel:       "low",
  destructive:     false,
  requiresConsent: false,
  supportsDryRun:  false,
  affectedScope:   ["user"],
  auditRequired:   false,

isUserWaitGate: true,

schema: {
    prompt: z
      .string()
      .min(1)
      .describe(
        "Question shown to the user above the text-input field " +
        "(e.g. 'What's your Okta email address?').",
      ),

placeholder: z
      .string()
      .optional()
      .describe(
        "Greyed-out hint text inside an EMPTY input. Disappears when " +
        "the user starts typing. NOT submitted as a value if the user " +
        "leaves the field blank. Use to suggest the expected format " +
        "without pre-filling text — e.g. 'alice@example.com'.",
      ),

initialValue: z
      .string()
      .optional()
      .describe(
        "Text PRE-FILLED into the input. User can accept (click Continue) " +
        "or edit. Submitted as the captured value if the user clicks " +
        "Continue without editing. Use ONLY when the agent has a real " +
        "best-guess (e.g. partial auto-detect result). Do NOT use as a " +
        "placeholder substitute.",
      ),

validator: z
      .string()
      .optional()
      .describe(
        "Optional regex (string source, no leading/trailing slashes) " +
        "the captured value must match before Continue enables. " +
        "Example: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$' for email format. " +
        "Validation happens client-side (button disabled until match) " +
        "AND server-side at gate resolution (defense in depth). " +
        "Server-side rejection returns empty string to the scratchpad.",
      ),

sensitive: z
      .boolean()
      .optional()
      .describe(
        "When true, the renderer shows a password-style masked input " +
        "(dots), AND the captured value is redacted in audit logs, " +
        "scratchpad replay, and consent-card previews. Use for tokens, " +
        "MFA codes, recovery keys. Do NOT use for usernames or emails " +
        "(masking those just frustrates the user when they want to " +
        "verify they typed correctly).",
      ),
  },
} as const;

// -- Stub run -----------------------------------------------------------------

/**
 * Synthetic tool — never actually invoked through the normal pipeline.
 * G4's executeStep() detects `meta.isUserWaitGate === true` and routes
 * the step through `runUserInputGate()` in execution.ts instead. This
 * stub exists only to satisfy the mcpTools registry's "every tool has
 * a run() function" invariant.
 *
 * If this throws in production, something is wrong with the gate routing
 * dispatcher — investigate `if (meta.isUserWaitGate)` in execution.ts.
 */
export async function run(): Promise<never> {
  throw new Error(
    "[request_user_input] This is a synthetic user-wait gate tool. " +
    "G4 is expected to route steps whose tool.meta.isUserWaitGate is true " +
    "through runUserInputGate() in execution.ts, NOT through the normal " +
    "tool pipeline. If you see this error, the gate routing is broken.",
  );
}

// -- CLI smoke test -----------------------------------------------------------

if (false) {
  console.log(JSON.stringify({ meta: { ...meta, schema: "..." } }, null, 2));
}