{"version":3,"file":"prompt-tool.cjs","names":["z","resolve","relative","isAbsolute","sep","explain","stepSource"],"sources":["../src/prompt-tool.ts"],"sourcesContent":["/**\n * Permission-prompt handling: answer a host's \"may this tool call run?\" question from the policy.\n *\n * Claude Code's `--permission-prompt-tool <mcp tool>` (print mode only; interactive sessions never call it) sends each prompt to an MCP tool as `{ tool_name, input, tool_use_id }` and expects back a single text block whose text is JSON: `{ behavior: \"allow\", updatedInput? }` or `{ behavior: \"deny\", message }`. There is no third answer, so a call the policy only asks about is settled by an injected {@link AskHandler}, which the host owns, or denied when there is none.\n *\n * This module has no MCP dependency: `mcp.ts` serves it as a tool, and a library host can call {@link createPermissionPrompt} directly.\n */\n\nimport { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport * as z from \"zod\";\n\nimport {\n  explain,\n  stepSource,\n  type EvaluationContext,\n  type Explanation,\n  type PermissionDecision,\n  type PermissionPolicy,\n} from \"./evaluate.ts\";\n\n/** The arguments a permission-prompt tool is called with: the tool the agent wants to run, its input, and the id of that call. */\nexport const PermissionPromptRequest = z.object({\n  tool_name: z.string(),\n  input: z.record(z.string(), z.unknown()),\n  tool_use_id: z.string().optional(),\n});\nexport type PermissionPromptRequest = z.infer<typeof PermissionPromptRequest>;\n\n/** The answer to a permission prompt. An allow echoes the input (or a changed one); a deny tells the agent why. */\nexport type PermissionPromptResult =\n  | { behavior: \"allow\"; updatedInput: Record<string, unknown> }\n  | { behavior: \"deny\"; message: string };\n\n/** A call the policy neither allows nor denies, handed to the host to settle. */\nexport interface AskRequest {\n  request: PermissionPromptRequest;\n  /** The string whose explanation decided the call (see {@link toolSubjects}). */\n  subject: string;\n  /** How the policy reached `ask`, rule by rule. */\n  explanation: Explanation;\n}\n\n/**\n * Settles an `ask` decision. The host decides how: show a person the request, hold it until someone approves it elsewhere, or consult another policy. The signal aborts when the agent abandons the call.\n */\nexport type AskHandler = (\n  ask: AskRequest,\n  signal: AbortSignal,\n) => Promise<PermissionPromptResult>;\n\nexport interface PermissionPromptOptions {\n  /** Reads the policy for each prompt, so edits to the policy files apply to the next call. */\n  loadPolicy: () => Promise<PermissionPolicy>;\n  /** The project root: a file inside it is judged by its `./`-relative path as well as its absolute one. */\n  root: string;\n  /** Conditions on a field left out here are unknown: they never allow a call, and may still deny or ask. */\n  context?: EvaluationContext;\n  /** Settles `ask` decisions. Without it, a call the policy asks about is denied. */\n  onAsk?: AskHandler;\n}\n\n/** Answers one permission prompt. */\nexport type PermissionPrompt = (\n  request: PermissionPromptRequest,\n  signal: AbortSignal,\n) => Promise<PermissionPromptResult>;\n\n/** The input field each built-in tool is judged by, and whether it holds a file path. */\nconst SUBJECT_FIELDS: Readonly<\n  Record<string, { field: string; path: boolean }>\n> = {\n  bash: { field: \"command\", path: false },\n  read: { field: \"file_path\", path: true },\n  write: { field: \"file_path\", path: true },\n  edit: { field: \"file_path\", path: true },\n  multiedit: { field: \"file_path\", path: true },\n  notebookedit: { field: \"notebook_path\", path: true },\n  webfetch: { field: \"url\", path: false },\n  websearch: { field: \"query\", path: false },\n};\n\n/**\n * The strings a tool call's rule patterns are matched against: the command for `Bash`, the URL for `WebFetch`, the query for `WebSearch`. A file tool's path is resolved against `root` and judged in both forms a policy may write it: absolute, and `./`-relative when it lies inside `root`. Any other tool is judged by its name alone, so its one subject is empty. `undefined` when the field a tool is judged by is missing or not a string.\n */\nexport function toolSubjects(\n  toolName: string,\n  input: Readonly<Record<string, unknown>>,\n  root: string,\n): readonly string[] | undefined {\n  const spec = SUBJECT_FIELDS[toolName.toLowerCase()];\n  if (spec === undefined) return [\"\"];\n  const value = input[spec.field];\n  if (typeof value !== \"string\") return undefined;\n  if (!spec.path) return [value];\n  const absolute = resolve(root, value);\n  const rel = relative(root, absolute);\n  if (rel === \"\") return [absolute, \".\"];\n  if (isAbsolute(rel) || rel === \"..\" || rel.startsWith(`..${sep}`)) {\n    return [absolute];\n  }\n  // Policies write paths with forward slashes on every platform.\n  return [absolute, `./${rel.split(sep).join(\"/\")}`];\n}\n\nconst STRICTNESS: Readonly<Record<PermissionDecision, number>> = {\n  deny: 2,\n  ask: 1,\n  allow: 0,\n};\n\n/**\n * Judge every subject and keep the strictest decision among those a rule reached, so a rule written in either path form applies. Only when no subject matched any rule does the default mode decide.\n */\nfunction judge(\n  policy: PermissionPolicy,\n  toolName: string,\n  subjects: readonly string[],\n  context: EvaluationContext,\n): { subject: string; explanation: Explanation } {\n  const judged = subjects.map((subject) => ({\n    subject,\n    explanation: explain(policy, toolName, subject, context),\n  }));\n  const ruled = judged.filter(({ explanation }) =>\n    explanation.steps.some((step) => step.reason !== \"default\"),\n  );\n  const candidates = ruled.length > 0 ? ruled : judged;\n  return candidates.reduce((strictest, next) =>\n    STRICTNESS[next.explanation.decision] >\n    STRICTNESS[strictest.explanation.decision]\n      ? next\n      : strictest,\n  );\n}\n\n/** Builds a {@link PermissionPrompt} that judges each call against the policy. */\nexport function createPermissionPrompt(\n  options: PermissionPromptOptions,\n): PermissionPrompt {\n  const { loadPolicy, root, context = {}, onAsk } = options;\n  return async (request, signal) => {\n    const { tool_name: toolName, input } = request;\n    const subjects = toolSubjects(toolName, input, root);\n    if (subjects === undefined) {\n      const field = SUBJECT_FIELDS[toolName.toLowerCase()]?.field;\n      return {\n        behavior: \"deny\",\n        message: `agent-perms cannot judge ${toolName}: its input has no string \"${String(field)}\".`,\n      };\n    }\n\n    const { subject, explanation } = judge(\n      await loadPolicy(),\n      toolName,\n      subjects,\n      context,\n    );\n    switch (explanation.decision) {\n      case \"allow\":\n        return { behavior: \"allow\", updatedInput: input };\n      case \"deny\":\n        return {\n          behavior: \"deny\",\n          message: `agent-perms denied ${toolName}: ${reasons(explanation, \"deny\")}.`,\n        };\n      case \"ask\":\n        if (onAsk === undefined) {\n          return {\n            behavior: \"deny\",\n            message: `agent-perms needs approval for ${toolName} and no approval handler is configured: ${reasons(explanation, \"ask\")}.`,\n          };\n        }\n        return onAsk({ request, subject, explanation }, signal);\n    }\n  };\n}\n\n/** The steps that produced `decision`, each as `command (source)`. */\nfunction reasons(\n  explanation: Explanation,\n  decision: PermissionDecision,\n): string {\n  return explanation.steps\n    .filter((step) => step.decision === decision)\n    .map((step) => `${step.command} (${stepSource(step)})`)\n    .join(\"; \");\n}\n\n/** A result as an MCP tool returns it: one text block holding the result as JSON. */\nexport function promptToolContent(result: PermissionPromptResult): {\n  content: [{ type: \"text\"; text: string }];\n} {\n  return { content: [{ type: \"text\", text: JSON.stringify(result) }] };\n}\n"],"mappings":"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"}