export type Ctx = import("../types").Ctx; export type ToolDef = import("../types").ToolDef; export type Usage = import("../types").Usage; /** * A bareguard Gate instance. Comes from the ambient `bareguard` module, so its * methods are accessed structurally here. */ export type Gate = { check: (action: any) => (GateDecision | Promise); record: (action: any, outcome?: any) => any; allows?: ((toolName: string) => (boolean | Promise)) | undefined; }; /** * A decision returned by `gate.check`. */ export type GateDecision = { /** * - 'allow' when permitted. */ outcome?: string | undefined; /** * - 'halt' for halt-severity denials. */ severity?: string | undefined; /** * - The matched rule name. */ rule?: string | undefined; /** * - Human-readable reason (bareguard's Decision emits null when absent). */ reason?: string | null | undefined; /** * - Arbitrary structured context. */ context?: Record | undefined; }; export type AnnotationLimits = { /** * - Max chars for `verdict` (bareguard clips silently at 80). */ verdict?: number | undefined; /** * - Max chars for `where` (bareguard clips silently at 300). */ where?: number | undefined; /** * - Max BYTES for the serialized `meta` (bareguard is all-or-nothing at 1000). */ meta?: number | undefined; }; export type JudgeToAnnotationOptions = { /** * - Carry the free-text `evidence` into `meta` (bounded here). */ includeEvidence?: boolean | undefined; /** * - Override the sink caps (pass bareguard's real numbers; not imported here). */ limits?: AnnotationLimits | undefined; }; export type Annotation = { /** * - `verdict !== 'honored'`. The load-bearing fail-open field — never omitted. */ surface: boolean; /** * - The verdict, char-bounded. */ verdict: string; /** * - A one-line mechanical address, char-bounded. */ where: string; /** * - `{field, stated, returned}` (+ bounded `evidence` if opted in). */ meta: Record; }; /** * Wire a bareguard Gate into bareagent's Loop. * * Returns: * - `policy` — async (toolName, args, ctx) closure for `new Loop({ policy })`. * Allow → true; deny → tagged reason string; halt → throws HaltError. * - `onLlmResult` — callback for `new Loop({ onLlmResult })`. Forwards every * provider.generate result to gate.record as a `{type:'llm'}` action * so `budget.maxCostUsd` covers token-only workloads. * - `onToolResult` — callback for `new Loop({ onToolResult })`. Forwards every * tool.execute result to gate.record with ctx in scope. * - `filterTools` — async (tools) => filtered. Drops tools denied by gate.allows * so the LLM never sees them. No audit, no record. Judges tool * IDENTITY ONLY (`{type: name, tool: name}`) — never the * `actionTranslator`'s real per-type shape (an `fs`/`bash` primitive * needs a real path/cmd that doesn't exist yet at discovery time; a * path-less probe's meaning is version-fragile — see the function doc * below). So filterTools reflects ONLY `tools.allowlist`/`tools.denylist`; * per-call scope (`fs.readScope`/`writeScope`, `bash.allow`) is enforced * by `policy` on every real call — an unset scope denies loudly there, * never silently hidden or silently offered at filter time. Bulk-only: * when MCP tools are exposed via `mcp_discover`+`mcp_invoke` * meta-tools, filterTools cannot drop the inner names (they * are not in the tool list). Gate those via bareguard's * `tools.denyArgPatterns: { mcp_invoke: [/"name":"…"/] }` * — see src/mcp-bridge.js (Gov shape). * - `wrapTool` / `wrapTools` — DEPRECATED. Pre-BA1 shim that wraps execute() to * call gate.record post-hoc. Loses _ctx and never sees LLM cost. * Prefer `onToolResult` (and `onLlmResult` for budget correctness). * * Halt-severity decisions (budget exhausted, limits.maxTurns hit, gate terminated) * throw HaltError from the policy closure; Loop catches it and exits cleanly with * loop:error{source:'halt'} + loop:done — the deny is NOT fed back to the LLM. * * @param {Gate} gate - A bareguard Gate instance (must have .check, .record, .allows). * @param {object} [options] * @param {Function} [options.formatDeny] - (decision, toolName) => string. Transforms * the deny string fed to the LLM. The second arg is the bareagent tool name (handy * for tool-specific deny copy). Default: "[deny: ] " or * "[deny: ] denied" when bareguard omits a reason. Halt bypasses * this (HaltError doesn't reach the LLM). * @param {Function} [options.actionTranslator] - (toolName, args, ctx) => action. * Builds the action object passed to `gate.check` and `gate.record`. Default (requires * bareguard >=0.19.0): the six `createShellTools` primitives map to bareguard's `fs`/`bash` * shapes — `shell_read`/`shell_grep` → `{type:'read', tool, path: resolveToolPath(args.path), args, _ctx}`, * `shell_write` → `{type:'write', ...}`, `shell_edit` → `{type:'edit', ...}`, `shell_run` → * `{type:'bash', tool, cmd: args.argv.join(' '), args, _ctx}`, `shell_exec` → `{type:'bash', tool, * cmd: args.command, args, _ctx}`; every other tool → `{type: toolName, tool: toolName, args, _ctx}`. * `tool` always carries the literal bareagent tool name — bareguard's action identity for * `tools.allowlist`/`tools.denylist` is `action.tool ?? action.type`, so a tool-name allowlist * (`tools.allowlist:['shell_read']`) keeps matching even though `type` is now `'read'`. This * activates `fs`/`bash` scoping automatically (`fs.readScope`/`fs.writeScope` deny by default * when unset — see CHANGELOG) — override when a different shape is needed. * `onToolResult` on a tool error records `{error: message, durationMs}` plus, only when present, an * allow-listed `errorContext` copied from `error.context` (`timeout`/`denied` booleans, `server`/`tool` * strings capped at 200 chars, finite `timeoutMs`; wrong-typed values dropped) — so an MCP timeout is * distinguishable in the audit row. `stderr` and every other context field are NEVER forwarded. * @returns {{policy: Function, onLlmResult: Function, onToolResult: Function, filterTools: Function, wrapTool: Function, wrapTools: Function}} * @when you have a bareguard Gate and want to wire it into a Loop as the policy + metering chokepoint in one line * @fails never throws; a gate halt surfaces as a HaltError the Loop catches and exits cleanly, and a deny is an advisory string fed back to the model. * * @example * const { Gate } = require('bareguard'); * const { Loop } = require('bare-agent'); * const { wireGate } = require('bare-agent/bareguard'); * * const gate = new Gate({ * budget: { maxCostUsd: 0.50 }, * limits: { maxTurns: 20 }, * audit: { path: './audit.jsonl' }, * }); * await gate.init(); * * const { policy, onLlmResult, onToolResult, filterTools } = wireGate(gate); * const loop = new Loop({ provider, policy, onLlmResult, onToolResult }); * const tools = await filterTools(myTools); * await loop.run(messages, tools); */ export function wireGate(gate: Gate, options?: { formatDeny?: Function | undefined; actionTranslator?: Function | undefined; }): { policy: Function; onLlmResult: Function; onToolResult: Function; filterTools: Function; wrapTool: Function; wrapTools: Function; }; /** * @param {string} toolName * @param {any} args * @param {Ctx} ctx */ export function defaultActionTranslator(toolName: string, args: any, ctx: Ctx): { tool: string; args: any; _ctx: any; type: string; path: string; } | { tool: string; args: any; _ctx: any; type: string; cmd: any; } | { tool: string; args: any; _ctx: any; type: string; }; /** * @typedef {object} AnnotationLimits * @property {number} [verdict=80] - Max chars for `verdict` (bareguard clips silently at 80). * @property {number} [where=300] - Max chars for `where` (bareguard clips silently at 300). * @property {number} [meta=1000] - Max BYTES for the serialized `meta` (bareguard is all-or-nothing at 1000). * * @typedef {object} JudgeToAnnotationOptions * @property {boolean} [includeEvidence=false] - Carry the free-text `evidence` into `meta` (bounded here). * @property {AnnotationLimits} [limits] - Override the sink caps (pass bareguard's real numbers; not imported here). * * @typedef {object} Annotation * @property {boolean} surface - `verdict !== 'honored'`. The load-bearing fail-open field — never omitted. * @property {string} verdict - The verdict, char-bounded. * @property {string} where - A one-line mechanical address, char-bounded. * @property {Record} meta - `{field, stated, returned}` (+ bounded `evidence` if opted in). */ /** * Map a `judge()` verdict into bareguard's `gate.annotate` shape. PURE — returns a ready-to-pass object and * NEVER calls the gate; the caller makes `gate.annotate(judgeToAnnotation(verdict))`. Imports no bareguard. * * @param {import('./judge').JudgeVerdict | { verdict?: string, where?: any }} verdict - a `judge()` return value. * @param {JudgeToAnnotationOptions} [opts] * @returns {Annotation} * @when you want to surface a judge() verdict on a gate's audit/humanChannel via gate.annotate, keeping the render pure (it never calls the gate) * @fails never throws and never calls the gate; surfaces fail-open (a non-honored verdict surfaces) and bounds fields against the sink's silent caps with a visible clip marker. * @example * gate.annotate(judgeToAnnotation(await judge({ request, artifact, provider }))); */ export function judgeToAnnotation(verdict: import("./judge").JudgeVerdict | { verdict?: string; where?: any; }, opts?: JudgeToAnnotationOptions): Annotation;