@alumnium/test 0.24.1 → 0.25.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js.map CHANGED
@@ -1,20 +1,25 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../test/src/FileTestCache.ts", "../../test/src/Recording.ts", "../../test/src/masker/HostVarMasker.ts", "../../test/src/masker/maskerUtils.ts", "../../test/src/masker/SubstringMasker.ts", "../../test/src/masker/ToolCallMasker.ts", "../../test/src/masker/ResponsePaths.ts", "../../test/src/masker/SecretMasker.ts", "../../test/src/masker/Masker.ts", "../../test/src/Recorder.ts", "../../test/src/RecordingLeakError.ts"],
3
+ "sources": ["../../test/src/FileTestCache.ts", "../../test/src/Recording.ts", "../../test/src/masker/HostVarMasker.ts", "../../test/src/masker/maskerUtils.ts", "../../test/src/masker/SubstringMasker.ts", "../../test/src/masker/ToolCallMasker.ts", "../../test/src/masker/ResponsePaths.ts", "../../test/src/masker/SecretMasker.ts", "../../test/src/masker/Masker.ts", "../../test/src/player/BashExecutor.ts", "../../test/src/player/McpExecutor.ts", "../../test/src/player/Player.ts", "../../test/src/masker/alumniumMaskerProps.ts", "../../test/src/player/ToolExecutor.ts", "../../test/src/Recorder.ts", "../../test/src/RecordingLeakError.ts"],
4
4
  "sourcesContent": [
5
5
  "import {\n Env,\n FileStore,\n GlobalFileStorePaths,\n safePathJoin,\n Telemetry,\n} from \"@alumnium/core/internal\";\nimport { Recording } from \"./Recording.ts\";\nimport type { TestCache } from \"./TestCache.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/FileTestCache.ts\");\n\nexport namespace FileTestCache {\n export interface Props {\n /** Directory to store recordings in, `<ALUMNIUM_CACHE_PATH>/tests` by default. */\n dir?: string | undefined;\n }\n}\n\n/**\n * Test cache storing each recording as `<xxh64(text)>.json`, in the `tests`\n * directory of the Alumnium cache.\n */\nexport class FileTestCache implements TestCache {\n #store: FileStore;\n #staged = new Map<Recording.Id, Recording.Type>();\n #lookups: TestCache.Lookups = { hits: 0, misses: 0 };\n\n constructor(props: FileTestCache.Props = {}) {\n this.#store = new FileStore(props.dir ?? FileTestCache.defaultDir());\n }\n\n static defaultDir(): string {\n return safePathJoin(\n Env.ALUMNIUM_CACHE_PATH ?? GlobalFileStorePaths.globalSubDir(\"cache\"),\n \"tests\",\n );\n }\n\n get dir(): string {\n return this.#store.dir;\n }\n\n get lookups(): TestCache.Lookups {\n return { ...this.#lookups };\n }\n\n async lookup(\n text: string,\n options: TestCache.LookupOptions = {},\n ): Promise<Recording.Type | null> {\n const { version = Recording.DEFAULT_VERSION } = options;\n const id = Recording.textToId(text);\n const found = this.#staged.get(id) ?? (await this.#read(id));\n if (found?.version === version) {\n logger.debug(`Test cache hit: ${id}`);\n this.#lookups.hits++;\n return structuredClone(found);\n }\n\n logger.debug(\n found\n ? `Test cache miss: ${id} is recorded in version ${found.version}, not ${version}`\n : `Test cache miss: ${id}`,\n );\n this.#lookups.misses++;\n return null;\n }\n\n async update(recording: Recording.Type): Promise<void> {\n this.#staged.set(recording.id, structuredClone(recording));\n }\n\n async save(): Promise<void> {\n const staged = [...this.#staged];\n if (!staged.length) return;\n\n logger.debug(`Saving ${staged.length} test recordings...`);\n\n await Promise.all(\n staged.map(([id, recording]) =>\n this.#store.writeJson(fileName(id), recording),\n ),\n );\n\n // Updates staged while writing stay staged.\n for (const [id, recording] of staged)\n if (this.#staged.get(id) === recording) this.#staged.delete(id);\n }\n\n async discard(): Promise<void> {\n this.#staged.clear();\n }\n\n /**\n * Reads a saved recording.\n *\n * NOTE: A file that fails to parse is treated as a miss rather than an error,\n * so that a recording made by an incompatible version is recorded again and\n * replaced instead of failing the test.\n */\n async #read(id: Recording.Id): Promise<Recording.Type | null> {\n try {\n return await this.#store.readJson(fileName(id), Recording.Schema);\n } catch (error) {\n logger.warn(`Failed to read test recording ${id}: {error}`, { error });\n return null;\n }\n }\n}\n\nfunction fileName(id: Recording.Id): string {\n return `${id}.json`;\n}\n",
6
6
  "import { xxh64Str } from \"@js-fns/xxhash/str\";\nimport z from \"zod\";\n\nexport namespace Recording {\n export type Id = z.infer<typeof Recording.Id>;\n export type Input = z.infer<typeof Recording.Input>;\n export type Content = z.infer<typeof Recording.Content>;\n export type ToolCall = z.infer<typeof Recording.ToolCall>;\n export type ToolResult = z.infer<typeof Recording.ToolResult>;\n export type ToolStep = z.infer<typeof Recording.ToolStep>;\n export type NarrationStep = z.infer<typeof Recording.NarrationStep>;\n export type Step = z.infer<typeof Recording.Step>;\n export type Verdict = z.infer<typeof Recording.Verdict>;\n export type Type = z.infer<typeof Recording.Schema>;\n}\n\n/**\n * A replayable recording of a test run: every tool call the host agent made,\n * paired with its result, and the agent's narration in between.\n *\n * Agent-neutral on purpose. The host (e.g. a test runner or the CLI)\n * owns the agent loop and translates its own messages into the calls and\n * results recorded here, so nothing in a recording is specific to the agent\n * that made it but its name.\n */\nexport abstract class Recording {\n static Id = z.string().brand(\"Recording.Id\");\n static Input = z.record(z.string(), z.unknown());\n\n /**\n * MCP content blocks a tool returned, or a bare string for a host tool that\n * returns plain output. Only text blocks are ever read, every other block is\n * kept as it was.\n */\n static Content = z.union([\n z.string(),\n z.array(z.looseObject({ type: z.string() })),\n ]);\n\n static ToolCall = z.object({\n /** Call id the host agent assigned, used to pair the call with its result. */\n id: z.string(),\n /** MCP server the tool belongs to, absent for a host tool (e.g. `Bash`). */\n server: z.string().optional(),\n /** Tool name as the server (or the host) exposes it, e.g. `do`. */\n tool: z.string(),\n input: this.Input,\n });\n\n static ToolResult = z.object({\n content: this.Content,\n isError: z.boolean(),\n });\n\n /**\n * A tool call and its result, whether it went to Alumnium's own MCP server,\n * another MCP server or a host tool - `call.server` tells them apart. Replay\n * re-issues every one, since a tool call can produce values later calls\n * consume.\n */\n static ToolStep = z.object({\n kind: z.literal(\"tool-call\"),\n call: this.ToolCall,\n result: this.ToolResult,\n });\n\n /**\n * Something the agent said rather than did: a piece of its thinking, or a\n * message meant for the person running the test. Recorded so that a replay\n * reads like the recording it came from, and never executed.\n */\n static NarrationStep = z.object({\n kind: z.literal(\"narration\"),\n narration: z.enum([\"thinking\", \"assistant\"]),\n text: z.string(),\n });\n\n static Step = z.union([this.ToolStep, this.NarrationStep]);\n\n static Verdict = z.object({\n status: z.enum([\"success\", \"failure\"]),\n /**\n * What happened, as the agent reported it: what the test verified when it\n * passed, what failed and how when it did not.\n */\n details: z.string(),\n });\n\n static DEFAULT_VERSION = 1;\n\n static Schema = z.object({\n id: this.Id,\n /**\n * Version of the host's recording setup. A host bumps it when its tools or\n * prompts change enough that existing recordings should be recorded\n * again, e.g. on a major release.\n */\n version: z.number().int().positive(),\n text: z.string(),\n /** Host agent that made the recording, e.g. `claude-code`. */\n agent: z.string(),\n alumnium: z.object({ version: z.string() }),\n /**\n * Names of the host variables the recording refers to as `<host-var.name>`\n * masks. Never their values: replay takes fresh ones from the host.\n */\n hostVars: z.array(z.string()),\n steps: z.array(this.Step),\n verdict: this.Verdict,\n });\n\n /**\n * Converts test text to a recording ID using a hash function.\n *\n * @param text - Test text to convert to ID.\n * @returns Recording ID generated from the trimmed text.\n */\n static textToId(text: string): Recording.Id {\n return xxh64Str(text.trim()) as Recording.Id;\n }\n\n /**\n * Key a tool call is configured by, e.g. in `maskResponsePaths`:\n * `<server>/<tool>` for an MCP tool, the bare tool name for a host tool.\n *\n * @param call - Tool call to get the key of.\n * @returns Tool key.\n */\n static toolKey(call: Pick<Recording.ToolCall, \"server\" | \"tool\">): string {\n return call.server ? `${call.server}/${call.tool}` : call.tool;\n }\n\n /**\n * Number of steps a replay executes, which is everything the agent did as\n * opposed to said.\n *\n * @param recording - Recording to count the steps of.\n * @returns Number of executable steps.\n */\n static executableStepsCount(recording: Recording.Type): number {\n return recording.steps.filter((step) => step.kind !== \"narration\").length;\n }\n}\n",
7
7
  "import { Telemetry } from \"@alumnium/core/internal\";\nimport { eachStringLeaf, findMasks } from \"./maskerUtils.ts\";\nimport { SubstringMasker } from \"./SubstringMasker.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/masker/HostVarMasker.ts\");\n\nconst HOST_MASK_PREFIX = \"<host-var.\";\nconst HOST_VAR_NAME_PATTERN = /^\\w+$/;\nconst HOST_MASK_PATTERN = /<host-var\\.\\w+(?::json)?>/g;\nconst HOST_MASK_NAME_PATTERN = /<host-var\\.(\\w+)(?::json)?>/g;\n\n/**\n * Masks host variables - values the host knows ahead of the run, by name:\n * credentials, base URLs, account ids - as `<host-var.name>` wherever they occur,\n * and substitutes the values the host passes on replay.\n */\nexport class HostVarMasker extends SubstringMasker {\n /**\n * @param hostVars - Host variables by name.\n */\n constructor(hostVars: Record<string, string> = {}) {\n super(HOST_MASK_PATTERN);\n\n for (const [name, value] of Object.entries(hostVars)) {\n if (!HOST_VAR_NAME_PATTERN.test(name))\n throw new RangeError(\n `Host variable name '${name}' must only contain word characters`,\n );\n\n // An empty value would match everywhere, and there is nothing in\n // it to keep out of a recording anyway.\n if (!value) {\n logger.debug(`Host variable ${name} is empty, not masking it`);\n continue;\n }\n\n this.register(`${HOST_MASK_PREFIX}${name}>`, value);\n }\n }\n\n /**\n * Finds host variable masks anywhere in a value.\n *\n * @param value - Value to scan.\n * @returns Masks, in order of appearance.\n */\n static findMasks(value: unknown): string[] {\n return findMasks(value, HOST_MASK_PATTERN);\n }\n\n /**\n * Finds the host variable names a value refers to through `<host-var.name>`\n * masks.\n *\n * @param value - Value to scan, e.g. recording steps.\n * @returns Unique host variable names, in order of appearance.\n */\n static findNames(value: unknown): string[] {\n const names = new Set<string>();\n\n eachStringLeaf({ value }, (leaf) => {\n for (const [, name] of leaf.matchAll(HOST_MASK_NAME_PATTERN))\n if (name) names.add(name);\n return undefined;\n });\n\n return [...names];\n }\n}\n",
8
8
  "import { Recording } from \"../Recording.ts\";\n\n/** Replacement for a string leaf, `undefined` to keep it. */\nexport type LeafFn = (value: string, path: string) => string | undefined;\n\n/**\n * Visits every string leaf of a value, at any nesting depth, so that the\n * values inside e.g. `do`'s `params` are covered too.\n *\n * Only string leaves are visited. Substituting a mask into a number or a\n * boolean leaf would change its type and can break the tool input schema.\n *\n * @param container - Value to walk, only objects and arrays are descended into.\n * @param mapLeaf - Called with each string leaf and its path, returning the\n * replacement for it or `undefined` to leave it alone.\n * @param path - Path of `container` itself, used for logging.\n */\nexport function eachStringLeaf(container: unknown, mapLeaf: LeafFn, path = \"\") {\n if (Array.isArray(container)) {\n container.forEach((item, index) => {\n const itemPath = `${path}[${index}]`;\n\n if (typeof item !== \"string\")\n return eachStringLeaf(item, mapLeaf, itemPath);\n\n const mapped = mapLeaf(item, itemPath);\n if (mapped !== undefined) container[index] = mapped;\n });\n return;\n }\n\n if (typeof container !== \"object\" || container === null) return;\n\n for (const [key, value] of Object.entries(container)) {\n const valuePath = path ? `${path}.${key}` : key;\n\n if (typeof value !== \"string\") {\n eachStringLeaf(value, mapLeaf, valuePath);\n continue;\n }\n\n const mapped = mapLeaf(value, valuePath);\n if (mapped !== undefined) Object.assign(container, { [key]: mapped });\n }\n}\n\n/**\n * Finds every match of a global mask pattern in the string leaves of a value.\n *\n * @param value - Value to scan.\n * @param pattern - Global mask pattern.\n * @returns Matched masks, in order of appearance.\n */\nexport function findMasks(value: unknown, pattern: RegExp): string[] {\n const masks: string[] = [];\n\n eachStringLeaf({ value }, (leaf) => {\n masks.push(...(leaf.match(pattern) ?? []));\n return undefined;\n });\n\n return masks;\n}\n\n/**\n * Parses a tool input, cloning it so that masking never touches the caller's\n * own object.\n *\n * @param value - Tool input.\n * @returns Cloned tool input.\n */\nexport function parseInput(value: unknown): Recording.Input {\n const parseResult = Recording.Input.safeParse(value);\n if (!parseResult.success)\n throw new TypeError(\"Invalid tool input, expected an object\");\n return structuredClone(parseResult.data);\n}\n\nexport function parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Parses a tool output as JSON, accepting only an object or an array.\n *\n * `JSON.parse` also accepts a bare scalar, so `echo 9` would otherwise\n * register a value with no key to name it, inconsistent with `echo nine`,\n * which is not JSON at all.\n *\n * @param text - Tool output text.\n * @returns Parsed JSON structure, `null` when the output is not one.\n */\nexport function parseJsonStructure(text: string): object | null {\n const parsed = parseJson(text);\n if (typeof parsed !== \"object\" || parsed === null) return null;\n return parsed;\n}\n\n/** Text of a tool result: the string itself, or every text block. */\nexport function contentTexts(content: Recording.Content): string[] {\n if (typeof content === \"string\") return [content];\n return content.flatMap((block) =>\n block.type === \"text\" && typeof block[\"text\"] === \"string\"\n ? [block[\"text\"]]\n : [],\n );\n}\n\n/** Maps the text of a tool result, keeping every other block as it was. */\nexport function mapTexts(\n content: Recording.Content,\n map: (text: string) => string,\n): Recording.Content {\n if (typeof content === \"string\") return map(content);\n return content.map((block) =>\n block.type === \"text\" && typeof block[\"text\"] === \"string\"\n ? { ...block, text: map(block[\"text\"]) }\n : block,\n );\n}\n\n/**\n * Tells whether a tool key is one of a set, by its own key or its server's\n * `<server>/*` key.\n *\n * @param keys - Tool keys, see `Recording.toolKey`.\n * @param toolKey - Tool key to look up.\n */\nexport function hasToolKey(keys: Set<string>, toolKey: string): boolean {\n if (keys.has(toolKey)) return true;\n\n const separatorIndex = toolKey.lastIndexOf(\"/\");\n return (\n separatorIndex >= 0 && keys.has(`${toolKey.slice(0, separatorIndex)}/*`)\n );\n}\n\n/**\n * Sanitizes a segment of a mask - a tool call id or a JSON path segment - to\n * word characters, so that a mask stays matchable by its pattern.\n *\n * @param segment - Segment to sanitize.\n */\nexport function maskSegment(segment: string): string {\n return segment.replace(/[^A-Za-z0-9_]+/g, \"_\");\n}\n\nconst ANY_MASK_PATTERN = /<(?:tool-call|secret|host-var)\\.[\\w.]+(?::json)?>/g;\n\n/**\n * Maps the parts of a string outside any mask, of any family, leaving the\n * masks themselves intact.\n *\n * @param value - String to map.\n * @param map - Called with each part between masks.\n */\nexport function mapOutsideMasks(\n value: string,\n map: (text: string) => string,\n): string {\n let result = \"\";\n let index = 0;\n for (const match of value.matchAll(ANY_MASK_PATTERN)) {\n result += map(value.slice(index, match.index)) + match[0];\n index = match.index + match[0].length;\n }\n return result + map(value.slice(index));\n}\n\n/** Escapes a string for use as a literal in a `RegExp`. */\nexport function escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n",
9
9
  "import {\n eachStringLeaf,\n escapeRegExp,\n mapOutsideMasks,\n} from \"./maskerUtils.ts\";\n\n/**\n * Masks known values wherever they occur in a string, not only as a whole\n * value or a quoted token. Meant for values specific enough to be matched\n * that way - host variables and secrets - which must never be stored in clear.\n */\nexport abstract class SubstringMasker {\n /** Suffix of a mask taking the place of a JSON-escaped value. */\n static JSON_SUFFIX = \":json\";\n\n #pattern: RegExp;\n #valueByMask = new Map<string, string>();\n #maskByValue = new Map<string, string>();\n\n constructor(pattern: RegExp) {\n this.#pattern = pattern;\n }\n\n get size(): number {\n return this.#valueByMask.size;\n }\n\n /**\n * Registers a value to mask, and the mask to unmask back to it.\n *\n * @param mask - Mask of the value.\n * @param value - Value to mask.\n */\n protected register(mask: string, value: string) {\n this.#valueByMask.set(mask, value);\n if (!this.#maskByValue.has(value)) this.#maskByValue.set(value, mask);\n }\n\n /**\n * Masks every registered value in every string leaf of a value.\n *\n * @param value - Value to mask in place, or a string to mask.\n * @returns The same value, or the masked string.\n */\n mask<Type>(value: Type): Type {\n if (!this.#maskByValue.size) return value;\n\n if (typeof value === \"string\") return this.maskString(value) as Type;\n\n eachStringLeaf(value, (leaf) => {\n const masked = this.maskString(leaf);\n return masked === leaf ? undefined : masked;\n });\n\n return value;\n }\n\n maskString(value: string): string {\n if (!this.#maskByValue.size) return value;\n\n const maskByText = new Map<string, string>();\n for (const [secret, mask] of this.#maskByValue) {\n // A value also reaches a recording JSON-escaped,\n // e.g. inside a JSON document a tool writes to a file.\n const escaped = this.#jsonEscaped(secret);\n maskByText.set(secret, mask);\n if (escaped !== secret && !maskByText.has(escaped))\n maskByText.set(escaped, this.#jsonMask(mask));\n }\n\n // Longest first, so a value containing another one is masked whole.\n const pattern = new RegExp(\n [...maskByText.keys()]\n .sort((a, b) => b.length - a.length)\n .map(escapeRegExp)\n .join(\"|\"),\n \"g\",\n );\n\n return mapOutsideMasks(value, (text) =>\n text.replace(pattern, (match) => maskByText.get(match) ?? match),\n );\n }\n\n /**\n * Substitutes registered values for their masks in every string leaf.\n *\n * @param value - Value to unmask in place.\n * @returns The same value.\n */\n unmask<Type>(value: Type): Type {\n if (!this.#valueByMask.size) return value;\n\n eachStringLeaf(value, (leaf) => {\n const unmasked = leaf.replace(this.#pattern, (mask) => {\n const escaped = mask.endsWith(`${SubstringMasker.JSON_SUFFIX}>`);\n const value = this.#valueByMask.get(\n escaped\n ? `${mask.slice(0, -SubstringMasker.JSON_SUFFIX.length - 1)}>`\n : mask,\n );\n if (value === undefined) return mask;\n return escaped ? this.#jsonEscaped(value) : value;\n });\n return unmasked === leaf ? undefined : unmasked;\n });\n\n return value;\n }\n\n /**\n * Finds registered values that survived masking anywhere in a value.\n *\n * @param value - Value to scan.\n * @returns Masks of the leaked values, never the values themselves.\n */\n findLeaks(value: unknown): string[] {\n const leaks = new Set<string>();\n\n eachStringLeaf({ value }, (leaf) => {\n mapOutsideMasks(leaf, (text) => {\n for (const [secret, mask] of this.#maskByValue) {\n if (text.includes(secret) || text.includes(this.#jsonEscaped(secret)))\n leaks.add(mask);\n }\n return text;\n });\n\n return undefined;\n });\n\n return [...leaks];\n }\n\n #jsonEscaped(value: string): string {\n return JSON.stringify(value).slice(1, -1);\n }\n\n #jsonMask(mask: string): string {\n return `${mask.slice(0, -1)}${SubstringMasker.JSON_SUFFIX}>`;\n }\n}\n",
10
- "import { Telemetry } from \"@alumnium/core/internal\";\nimport type { Recording } from \"../Recording.ts\";\nimport {\n contentTexts,\n eachStringLeaf,\n hasToolKey,\n maskSegment,\n parseJsonStructure,\n} from \"./maskerUtils.ts\";\nimport { ResponsePaths } from \"./ResponsePaths.ts\";\nimport { SecretMasker } from \"./SecretMasker.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/masker/ToolCallMasker.ts\");\n\nconst TOOL_CALL_MASK_PREFIX = \"<tool-call.\";\nconst TOOL_CALL_MASK_PATTERN = /^<tool-call\\.\\w+(?:\\.\\w+)+>$/;\n\n// A quote, escaped by any number of backslashes, e.g. `\"`, `\\\"` or `\\\\\"`.\nconst QUOTED_DELIMITER_SOURCE = String.raw`\\\\*['\"]`;\n\n// A run between two identical delimiters, e.g. `\"a1\"` or `\\\"a1\\\"`. A run\n// holds the other quote freely, so `\"it's\"` is one run.\nconst QUOTED_RUN_PATTERN = new RegExp(\n String.raw`(\\\\*)(?:'([^'\\\\]*)\\1'|\"([^\"\\\\]*)\\1\")`,\n \"g\",\n);\nconst QUOTED_TOOL_CALL_MASK_PATTERN = new RegExp(\n `(${QUOTED_DELIMITER_SOURCE})(<tool-call\\\\.\\\\w+(?:\\\\.\\\\w+)+>)\\\\1`,\n \"g\",\n);\n\nconst UNMASKED_INPUT_PATHS = new Set([\"description\"]);\n\nconst DEFAULT_VERBATIM_TOOLS = [\"TodoWrite\"];\n\n// Dot-separated, since a dot rarely appears in a JSON key, so masks of distinct\n// paths rarely collide. See `maskSegment`.\nconst PATH_SEPARATOR = \".\";\n\nexport namespace ToolCallMasker {\n export interface Props {\n /** @see Masker.Props.verbatimTools */\n verbatimTools?: string[] | undefined;\n /** @see Masker.Props.wholeValueOnlyTools */\n wholeValueOnlyTools?: string[] | undefined;\n /** @see `Masker.Props.maskResponsePaths`. */\n maskResponsePaths?: Record<string, string[]> | undefined;\n /** @see `Masker.Props.registerResponsePaths`. */\n registerResponsePaths?: Record<string, string[]> | undefined;\n }\n\n /**\n * Which strings a mask can take the place of.\n * - whole: a tool call value in full (e.g. `{\"id\": \"a1\"}`).\n * - embedded: a quote-delimited value inside a larger string (e.g. `echo '{\"id\": \"a1\"}'`).\n */\n export type Scope = \"whole\" | \"embedded\";\n\n export interface Leaf {\n path: string[];\n value: string;\n }\n\n export interface RegisterOptions {\n /** Registers only the leaves at `maskResponsePaths`, e.g. of a failed call. */\n secretsOnly?: boolean | undefined;\n }\n\n export interface Registered {\n mask: string;\n value: string;\n }\n}\n\n/**\n * Masks values tool calls produced, and substitutes the values the replay's\n * fresh calls produce. Masks are derived from the JSON path of each output\n * leaf, so the same leaf yields the same mask in both phases:\n *\n * - `<tool-call.id.path>` - an ordinary value, masked only where a later tool\n * input uses it as a whole value (or a quoted one, in the `embedded` scope),\n * since it can be short or common;\n * - `<secret.id.path>` - a value at one of the tool's `maskResponsePaths`,\n * masked like an ordinary one and also wherever else it occurs, results and\n * narration included, since it must never be stored in clear (see\n * `SecretMasker`).\n */\nexport class ToolCallMasker {\n #verbatimTools: Set<string>;\n #wholeValueOnlyTools: Set<string>;\n #registerPaths: ResponsePaths;\n #secrets: SecretMasker;\n\n #valueByMask = new Map<string, string>(); // Mask -> tool call value, for unmasking.\n #maskByValue = new Map<string, string>(); // Tool call value -> mask, for masking.\n\n constructor(props: ToolCallMasker.Props = {}) {\n this.#verbatimTools = new Set(\n props.verbatimTools ?? DEFAULT_VERBATIM_TOOLS,\n );\n this.#wholeValueOnlyTools = new Set(props.wholeValueOnlyTools);\n this.#registerPaths = new ResponsePaths(props.registerResponsePaths);\n this.#secrets = new SecretMasker(props.maskResponsePaths);\n }\n\n /**\n * Tells whether tool call values are masked in a tool's input at all.\n *\n * Every tool but a verbatim one qualifies, including one replay cannot\n * execute: the point of masking such an input is that the value a run\n * produced does not end up persisted in the recording, not that it gets\n * substituted again.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @returns `false` when the tool's input is prose that must stay verbatim.\n */\n masksInputOf(toolKey: string): boolean {\n return !hasToolKey(this.#verbatimTools, toolKey);\n }\n\n /**\n * Scope tool call values are masked in for a tool's input: `whole` for one\n * of `wholeValueOnlyTools`, `embedded` for any other.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n */\n scopeOf(toolKey: string): ToolCallMasker.Scope {\n return hasToolKey(this.#wholeValueOnlyTools, toolKey)\n ? \"whole\"\n : \"embedded\";\n }\n\n /**\n * Masks a tool's input in the tool's scope (see `scopeOf`), or only its\n * secrets when the tool's input must stay verbatim.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param input - Tool input, masked in place.\n * @returns The same input.\n */\n maskInput(toolKey: string, input: Recording.Input): Recording.Input {\n if (!this.masksInputOf(toolKey)) return this.maskSecrets(input);\n return this.mask(input, this.scopeOf(toolKey));\n }\n\n /**\n * Substitutes fresh values back into a tool's input, in the tool's scope.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param input - Masked tool input, unmasked in place.\n * @returns The same input.\n */\n unmaskInput(toolKey: string, input: Recording.Input): Recording.Input {\n return this.unmask(input, this.scopeOf(toolKey));\n }\n\n //#region Registration\n\n /**\n * Finds masks unmasking could not resolve: a `<tool-call...>` one that\n * takes up a whole value, a `<secret...>` one anywhere.\n *\n * An ordinary mask inside a quoted token does not count. An unresolved\n * one there just replays the command as recorded rather than failing the\n * replay, whereas a secret replayed as its mask is never right.\n *\n * @param input - Unmasked tool input.\n * @returns Masks, in order of appearance.\n */\n static findUnresolved(input: Recording.Input): string[] {\n const masks: string[] = [];\n\n eachStringLeaf(input, (value) => {\n if (TOOL_CALL_MASK_PATTERN.test(value)) masks.push(value);\n return undefined;\n });\n\n return [...masks, ...SecretMasker.findMasks(input)];\n }\n\n /**\n * Finds secrets that survived masking anywhere in a value, see\n * `SecretMasker.findLeaks`.\n *\n * @param value - Value to scan.\n * @returns Masks of the leaked secrets, never the secrets themselves.\n */\n findLeaks(value: unknown): string[] {\n return this.#secrets.findLeaks(value);\n }\n\n /**\n * Registers the values of a tool call result. Only JSON output is\n * considered, so the same leaf yields the same mask in both phases even\n * when a fresh output reorders its keys or adds new ones.\n *\n * @param callId - Id of the tool call, as the host agent assigned it.\n * @param content - Tool call result content.\n * @param toolKey - Tool key (see `Recording.toolKey`) for the response paths.\n * @returns Leaves registered, with their masks.\n */\n registerContent(\n callId: string,\n content: Recording.Content,\n toolKey?: string,\n options: ToolCallMasker.RegisterOptions = {},\n ): ToolCallMasker.Registered[] {\n // Checked before parsing, so that a tool registering nothing - e.g.\n // one returning an accessibility tree - is not reported as non-JSON.\n if (this.#registersNothing(toolKey, options)) return [];\n\n const text = contentTexts(content).join(\"\\n\").trim();\n if (!text) {\n logger.debug(`Tool call ${callId} produced no text output`);\n return [];\n }\n\n const json = parseJsonStructure(text);\n if (!json) {\n logger.info(\n `Tool call ${callId} output is not a JSON object or array, ignoring it for masking. ` +\n \"The values it produced will be replayed as recorded.\",\n );\n logger.debug(`-> Output: ${text}`);\n if (this.#secrets.hasPaths(toolKey)) this.#secrets.markUnread(callId);\n return [];\n }\n\n return this.registerOutput(callId, json, toolKey, options);\n }\n\n /**\n * Registers the scalar leaves of a tool call's JSON output, so that they\n * can be masked in the tool inputs that follow it (recording) or\n * substituted back into them (replay).\n *\n * A tool with `registerResponsePaths` registers only the leaves they cover,\n * any other tool every leaf. A leaf at one of the tool's\n * `maskResponsePaths` becomes a secret, and is registered regardless, since\n * it must be kept out of the recording either way.\n *\n * @param callId - Id of the tool call, as the host agent assigned it.\n * @param json - Parsed JSON output of the call.\n * @param toolKey - Tool key (see `Recording.toolKey`) for the response paths.\n * @returns Leaves registered, with their masks.\n */\n registerOutput(\n callId: string,\n json: object,\n toolKey?: string,\n options: ToolCallMasker.RegisterOptions = {},\n ): ToolCallMasker.Registered[] {\n const registered: ToolCallMasker.Registered[] = [];\n\n this.#collectJsonLeaves(json).forEach(({ path, value }) => {\n const secret = this.#secrets.isSecret(toolKey, path);\n if (!secret && (options.secretsOnly || !this.#registers(toolKey, path)))\n return;\n\n const prefix = secret ? SecretMasker.PREFIX : TOOL_CALL_MASK_PREFIX;\n let mask = this.#leafMask(prefix, callId, path);\n\n // Distinct paths can sanitize to the same mask (`a-b` and `a_b`).\n if (this.#valueByMask.has(mask)) {\n if (!secret) {\n logger.warn(`Tool call mask ${mask} is already registered, skipping`);\n return;\n }\n mask = this.#uniqueMask(mask);\n }\n\n this.#valueByMask.set(mask, value);\n // Keep the earliest call's mask, so replay substitutes that call's fresh value.\n if (!this.#maskByValue.has(value)) this.#maskByValue.set(value, mask);\n if (secret) this.#secrets.registerSecret(mask, value);\n\n logger.debug(`Registered tool call value ${mask}`);\n registered.push({ mask, value });\n });\n\n return registered;\n }\n\n #registers(toolKey: string | undefined, path: string[]): boolean {\n return (\n !this.#registerPaths.of(toolKey) ||\n this.#registerPaths.covers(toolKey, path)\n );\n }\n\n #registersNothing(\n toolKey: string | undefined,\n options: ToolCallMasker.RegisterOptions,\n ): boolean {\n if (this.#secrets.hasPaths(toolKey)) return false;\n return (\n !!options.secretsOnly || this.#registerPaths.of(toolKey)?.length === 0\n );\n }\n\n /**\n * Collects every scalar leaf of a JSON structure together with its path.\n *\n * @param json - JSON structure to walk.\n * @returns Leaves sorted by path.\n */\n #collectJsonLeaves(json: object): ToolCallMasker.Leaf[] {\n const leaves: ToolCallMasker.Leaf[] = [];\n\n const walk = (value: unknown, path: string[]) => {\n if (Array.isArray(value)) {\n value.forEach((item, index) => walk(item, [...path, String(index)]));\n return;\n }\n\n if (typeof value === \"object\" && value !== null) {\n for (const [key, nested] of Object.entries(value))\n walk(nested, [...path, key]);\n return;\n }\n\n // NOTE: Booleans and nulls carry nothing specific to the run that produced\n // them, so a tool input that happens to equal `true` would be a false\n // positive rather than a value worth substituting.\n if (typeof value !== \"string\" && typeof value !== \"number\") return;\n\n const leafValue = String(value);\n if (!leafValue.trim()) return;\n\n leaves.push({ path, value: leafValue });\n };\n\n walk(json, []);\n\n // Sorted by path rather than kept in key order, which a fresh output can\n // change: distinct paths sanitizing to the same mask then resolve the same\n // way in both phases.\n return leaves.sort((a, b) => comparePaths(a.path, b.path));\n }\n\n /**\n * Suffixes a mask already registered with the first free `_<n>`.\n *\n * @param mask - Mask taken by another leaf.\n * @returns Mask no leaf has yet.\n */\n #uniqueMask(mask: string): string {\n const base = mask.slice(0, -1);\n let index = 2;\n while (this.#valueByMask.has(`${base}_${index}>`)) index++;\n return `${base}_${index}>`;\n }\n\n /**\n * Builds the mask for a JSON leaf, e.g. `[\"items\", \"2\", \"id\"]` of tool call\n * `toolu_01` becomes `<tool-call.toolu_01.items.2.id>`.\n *\n * @param prefix - Mask family prefix, e.g. `<tool-call.`.\n * @param callId - Id of the tool call, as the host agent assigned it.\n * @param path - JSON path of the leaf.\n * @returns Mask to replace the leaf's value with.\n */\n #leafMask(prefix: string, callId: string, path: string[]): string {\n return `${prefix}${[callId, ...path].map(maskSegment).join(PATH_SEPARATOR)}>`;\n }\n\n //#endregion\n //#region Masking\n\n /**\n * Replaces values produced by tool calls with masks, so that replay can\n * substitute freshly produced ones.\n *\n * In the `whole` scope only a leaf that equals a tool call value in full is\n * replaced. A value quoted inside free-form prose is left alone: there is no\n * way to tell a value apart from the prose around it without guessing, and\n * every Alumnium tool that takes prose takes such values through `params`.\n *\n * Secrets are then masked wherever else they occur, see `maskSecrets`.\n *\n * @param input - Tool input, masked in place.\n * @param scope - Which strings a mask can take the place of.\n * @returns The same input.\n */\n mask(input: Recording.Input, scope: ToolCallMasker.Scope): Recording.Input {\n if (!this.#maskByValue.size) return input;\n\n eachStringLeaf(input, (value, path) => {\n if (scope === \"embedded\" && UNMASKED_INPUT_PATHS.has(path))\n return undefined;\n\n const mask = this.#maskByValue.get(value);\n if (mask) {\n logger.debug(\n `Masked tool call value in '${path}': '${value}' -> '${mask}'`,\n );\n return mask;\n }\n\n if (scope !== \"embedded\") return undefined;\n\n return this.#maskQuoted(value, path);\n });\n\n return this.maskSecrets(input);\n }\n\n /**\n * Masks secrets wherever they occur in every string leaf of a value, e.g. a\n * tool result, narration, or the input of a tool that must stay verbatim.\n *\n * @param value - Value to mask in place, or a string to mask.\n * @returns The same value, or the masked string.\n */\n maskSecrets<Type>(value: Type): Type {\n return this.#secrets.mask(value);\n }\n\n /**\n * Masks every quote-delimited token of a string that names a tool call value.\n *\n * Only a token a quote pair delimits is looked up. An unquoted one has no\n * boundary to trust, and guessing one is exactly what whole-value matching\n * avoids - a call that produced `9` would otherwise rewrite `--retries 19`.\n *\n * A short or common value can still match a quoted token that has\n * nothing to do with it. That ambiguity is inherent to matching by value,\n * and a length or numeric heuristic would silently break the legitimate case\n * of `{\"count\": 2}` feeding `--count \"2\"`, so there is none.\n *\n * @param value - Tool input string to scan.\n * @param path - Path of the string, used for logging.\n * @returns The string with its tokens masked, `undefined` when none matched.\n */\n #maskQuoted(value: string, path: string): string | undefined {\n const result = this.#maskQuotedRuns(value, path);\n return result === value ? undefined : result;\n }\n\n /**\n * Masks the quoted runs of a string, see `#maskQuoted`. Innermost run wins:\n * `echo '{\"id\": \"a1\"}'` yields `id` and `a1`, not the JSON around them.\n *\n * @param value - String to scan.\n * @param path - Path of the string, used for logging.\n * @returns The string with its tokens masked.\n */\n #maskQuotedRuns(value: string, path: string): string {\n return value.replace(\n QUOTED_RUN_PATTERN,\n (match, escapes: string, single?: string, double?: string) => {\n const delimiter = `${escapes}${single === undefined ? '\"' : \"'\"}`;\n const token = single ?? double ?? \"\";\n\n if (token.search(QUOTED_RUN_PATTERN) >= 0)\n return `${delimiter}${this.#maskQuotedRuns(token, path)}${delimiter}`;\n\n const mask = this.#maskByValue.get(token);\n if (!mask) return match;\n\n logger.debug(\n `Masked quoted tool call value in '${path}': '${token}' -> '${mask}'`,\n );\n\n return `${delimiter}${mask}${delimiter}`;\n },\n );\n }\n\n //#endregion\n //#region Unmasking\n\n /**\n * Substitutes fresh tool call values back into a tool input, secrets\n * wherever they occur.\n *\n * A mask with no registered value is left in place, so that\n * `findUnresolved` can name the input it was needed for.\n *\n * @param input - Masked tool input, unmasked in place.\n * @param scope - Which strings a mask can have taken the place of.\n * @returns The same input.\n */\n unmask(input: Recording.Input, scope: ToolCallMasker.Scope): Recording.Input {\n if (!this.#valueByMask.size) return input;\n\n eachStringLeaf(input, (mask, path) => {\n const value = this.#valueByMask.get(mask);\n if (value !== undefined) {\n logger.debug(\n `Unmasked tool call value in '${path}': '${mask}' -> '${value}'`,\n );\n return value;\n }\n\n if (scope === \"embedded\") return this.#unmaskQuoted(mask, path);\n });\n\n // Substituted as is, with no quoting check: a secret was masked\n // wherever it occurred, quoted or not.\n return this.#secrets.unmask(input);\n }\n\n /**\n * Substitutes fresh values for every quote-delimited mask of a string, the\n * inverse of `#maskQuoted`.\n *\n * Masking only ever emits a mask between two identical delimiters, and a mask\n * carries neither a quote nor a backslash of its own, so the quoted shape here\n * is exactly the one masking produced, escaping included.\n *\n * @param value - Masked tool input string to scan.\n * @param path - Path of the string, used for logging.\n * @returns The string with its masks replaced, `undefined` when none were.\n */\n #unmaskQuoted(value: string, path: string): string | undefined {\n let unmasked = false;\n\n const result = value.replace(\n QUOTED_TOOL_CALL_MASK_PATTERN,\n (match, delimiter: string, mask: string) => {\n const fresh = this.#valueByMask.get(mask);\n if (fresh === undefined) return match;\n\n if (this.#breaksQuoting(fresh, delimiter)) {\n logger.warn(\n `Fresh tool call value for ${mask} cannot be quoted with ${delimiter} in '${path}', leaving the mask in place`,\n );\n return match;\n }\n\n logger.debug(\n `Unmasked quoted tool call value in '${path}': '${mask}' -> '${fresh}'`,\n );\n unmasked = true;\n\n return `${delimiter}${fresh}${delimiter}`;\n },\n );\n\n return unmasked ? result : undefined;\n }\n\n /**\n * Tells whether substituting a value into a quoted run would end that run early\n * or change what the value means.\n *\n * Refusing beats escaping: an escape is shell-dialect specific, and the string\n * the value goes into is not necessarily a shell command at all. Leaving the\n * mask in place replays the command as recorded, which is visible, whereas a\n * mis-quoted command could quietly do something else.\n *\n * Inside single quotes a shell takes everything literally, so only the quote\n * itself is a problem. Inside double quotes, or the escape layer an escaped\n * delimiter implies, a backslash, `$` or backtick would still be interpreted.\n *\n * @param value - Fresh tool call value to substitute.\n * @param delimiter - Quote, possibly escaped, the recorded token was between.\n * @returns `true` when the value cannot go inside that delimiter.\n */\n #breaksQuoting(value: string, delimiter: string): boolean {\n const quote = delimiter.slice(-1);\n if (value.includes(quote)) return true;\n\n const interprets = quote === '\"' || delimiter.length > 1;\n return interprets && /[\\\\$`]/.test(value);\n }\n\n //#endregion\n}\n\nfunction comparePaths(a: string[], b: string[]): number {\n for (let index = 0; index < Math.min(a.length, b.length); index++) {\n const [left = \"\", right = \"\"] = [a[index], b[index]];\n if (left !== right) return left < right ? -1 : 1;\n }\n return a.length - b.length;\n}\n",
10
+ "import { Telemetry } from \"@alumnium/core/internal\";\nimport type { Recording } from \"../Recording.ts\";\nimport {\n contentTexts,\n eachStringLeaf,\n hasToolKey,\n maskSegment,\n parseJsonStructure,\n} from \"./maskerUtils.ts\";\nimport { ResponsePaths } from \"./ResponsePaths.ts\";\nimport { SecretMasker } from \"./SecretMasker.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/masker/ToolCallMasker.ts\");\n\nconst TOOL_CALL_MASK_PREFIX = \"<tool-call.\";\nconst TOOL_CALL_MASK_PATTERN = /^<tool-call\\.\\w+(?:\\.\\w+)+>$/;\n\n// A quote, escaped by any number of backslashes, e.g. `\"`, `\\\"` or `\\\\\"`.\nconst QUOTED_DELIMITER_SOURCE = String.raw`\\\\*['\"]`;\n\n// A run between two identical delimiters, e.g. `\"a1\"` or `\\\"a1\\\"`. A run\n// holds the other quote freely, so `\"it's\"` is one run.\nconst QUOTED_RUN_PATTERN = new RegExp(\n String.raw`(\\\\*)(?:'([^'\\\\]*)\\1'|\"([^\"\\\\]*)\\1\")`,\n \"g\",\n);\nconst QUOTED_TOOL_CALL_MASK_PATTERN = new RegExp(\n `(${QUOTED_DELIMITER_SOURCE})(<tool-call\\\\.\\\\w+(?:\\\\.\\\\w+)+>)\\\\1`,\n \"g\",\n);\n\nconst UNMASKED_INPUT_PATHS = new Set([\"description\"]);\n\nconst DEFAULT_VERBATIM_TOOLS = [\"TodoWrite\"];\n\n// Dot-separated, since a dot rarely appears in a JSON key, so masks of distinct\n// paths rarely collide. See `maskSegment`.\nconst PATH_SEPARATOR = \".\";\n\nexport namespace ToolCallMasker {\n export interface Props {\n /** @see Masker.Props.verbatimTools */\n verbatimTools?: string[] | undefined;\n /** @see Masker.Props.wholeValueOnlyTools */\n wholeValueOnlyTools?: string[] | undefined;\n /** @see `Masker.Props.maskResponsePaths`. */\n maskResponsePaths?: Record<string, string[]> | undefined;\n /** @see `Masker.Props.registerResponsePaths`. */\n registerResponsePaths?: Record<string, string[]> | undefined;\n }\n\n /**\n * Which strings a mask can take the place of.\n * - whole: a tool call value in full (e.g. `{\"id\": \"a1\"}`).\n * - embedded: a quote-delimited value inside a larger string (e.g. `echo '{\"id\": \"a1\"}'`).\n */\n export type Scope = \"whole\" | \"embedded\";\n\n export interface Leaf {\n path: string[];\n value: string;\n }\n\n export interface RegisterOptions {\n /**\n * Registers only the leaves at `maskResponsePaths`, for a failed call. An\n * output that is not JSON is then its error message rather than the\n * payload the paths describe, so it is not reported as unread either.\n */\n secretsOnly?: boolean | undefined;\n }\n\n export interface Registered {\n mask: string;\n value: string;\n }\n}\n\n/**\n * Masks values tool calls produced, and substitutes the values the replay's\n * fresh calls produce. Masks are derived from the JSON path of each output\n * leaf, so the same leaf yields the same mask in both phases:\n *\n * - `<tool-call.id.path>` - an ordinary value, masked only where a later tool\n * input uses it as a whole value (or a quoted one, in the `embedded` scope),\n * since it can be short or common;\n * - `<secret.id.path>` - a value at one of the tool's `maskResponsePaths`,\n * masked like an ordinary one and also wherever else it occurs, results and\n * narration included, since it must never be stored in clear (see\n * `SecretMasker`).\n */\nexport class ToolCallMasker {\n #verbatimTools: Set<string>;\n #wholeValueOnlyTools: Set<string>;\n #registerPaths: ResponsePaths;\n #secrets: SecretMasker;\n\n #valueByMask = new Map<string, string>(); // Mask -> tool call value, for unmasking.\n #maskByValue = new Map<string, string>(); // Tool call value -> mask, for masking.\n\n constructor(props: ToolCallMasker.Props = {}) {\n this.#verbatimTools = new Set(\n props.verbatimTools ?? DEFAULT_VERBATIM_TOOLS,\n );\n this.#wholeValueOnlyTools = new Set(props.wholeValueOnlyTools);\n this.#registerPaths = new ResponsePaths(props.registerResponsePaths);\n this.#secrets = new SecretMasker(props.maskResponsePaths);\n }\n\n /**\n * Tells whether tool call values are masked in a tool's input at all.\n *\n * Every tool but a verbatim one qualifies, including one replay cannot\n * execute: the point of masking such an input is that the value a run\n * produced does not end up persisted in the recording, not that it gets\n * substituted again.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @returns `false` when the tool's input is prose that must stay verbatim.\n */\n masksInputOf(toolKey: string): boolean {\n return !hasToolKey(this.#verbatimTools, toolKey);\n }\n\n /**\n * Scope tool call values are masked in for a tool's input: `whole` for one\n * of `wholeValueOnlyTools`, `embedded` for any other.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n */\n scopeOf(toolKey: string): ToolCallMasker.Scope {\n return hasToolKey(this.#wholeValueOnlyTools, toolKey)\n ? \"whole\"\n : \"embedded\";\n }\n\n /**\n * Masks a tool's input in the tool's scope (see `scopeOf`), or only its\n * secrets when the tool's input must stay verbatim.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param input - Tool input, masked in place.\n * @returns The same input.\n */\n maskInput(toolKey: string, input: Recording.Input): Recording.Input {\n if (!this.masksInputOf(toolKey)) return this.maskSecrets(input);\n return this.mask(input, this.scopeOf(toolKey));\n }\n\n /**\n * Substitutes fresh values back into a tool's input, in the tool's scope.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param input - Masked tool input, unmasked in place.\n * @returns The same input.\n */\n unmaskInput(toolKey: string, input: Recording.Input): Recording.Input {\n return this.unmask(input, this.scopeOf(toolKey));\n }\n\n //#region Registration\n\n /**\n * Finds masks unmasking could not resolve: a `<tool-call...>` one that\n * takes up a whole value, a `<secret...>` one anywhere.\n *\n * An ordinary mask inside a quoted token does not count. An unresolved\n * one there just replays the command as recorded rather than failing the\n * replay, whereas a secret replayed as its mask is never right.\n *\n * @param input - Unmasked tool input.\n * @returns Masks, in order of appearance.\n */\n static findUnresolved(input: Recording.Input): string[] {\n const masks: string[] = [];\n\n eachStringLeaf(input, (value) => {\n if (TOOL_CALL_MASK_PATTERN.test(value)) masks.push(value);\n return undefined;\n });\n\n return [...masks, ...SecretMasker.findMasks(input)];\n }\n\n /**\n * Finds secrets that survived masking anywhere in a value, see\n * `SecretMasker.findLeaks`.\n *\n * @param value - Value to scan.\n * @returns Masks of the leaked secrets, never the secrets themselves.\n */\n findLeaks(value: unknown): string[] {\n return this.#secrets.findLeaks(value);\n }\n\n /**\n * Registers the values of a tool call result. Only JSON output is\n * considered, so the same leaf yields the same mask in both phases even\n * when a fresh output reorders its keys or adds new ones.\n *\n * @param callId - Id of the tool call, as the host agent assigned it.\n * @param content - Tool call result content.\n * @param toolKey - Tool key (see `Recording.toolKey`) for the response paths.\n * @returns Leaves registered, with their masks.\n */\n registerContent(\n callId: string,\n content: Recording.Content,\n toolKey?: string,\n options: ToolCallMasker.RegisterOptions = {},\n ): ToolCallMasker.Registered[] {\n // Checked before parsing, so that a tool registering nothing - e.g.\n // one returning an accessibility tree - is not reported as non-JSON.\n if (this.#registersNothing(toolKey, options)) return [];\n\n const text = contentTexts(content).join(\"\\n\").trim();\n if (!text) {\n logger.debug(`Tool call ${callId} produced no text output`);\n return [];\n }\n\n const json = parseJsonStructure(text);\n if (!json && options.secretsOnly) {\n logger.debug(\n `Failed tool call ${callId} output is not JSON, ignoring it`,\n );\n return [];\n }\n\n if (!json) {\n logger.info(\n `Tool call ${callId} output is not a JSON object or array, ignoring it for masking. ` +\n \"The values it produced will be replayed as recorded.\",\n );\n logger.debug(`-> Output: ${text}`);\n if (this.#secrets.hasPaths(toolKey)) this.#secrets.markUnread(callId);\n return [];\n }\n\n return this.registerOutput(callId, json, toolKey, options);\n }\n\n /**\n * Registers the scalar leaves of a tool call's JSON output, so that they\n * can be masked in the tool inputs that follow it (recording) or\n * substituted back into them (replay).\n *\n * A tool with `registerResponsePaths` registers only the leaves they cover,\n * any other tool every leaf. A leaf at one of the tool's\n * `maskResponsePaths` becomes a secret, and is registered regardless, since\n * it must be kept out of the recording either way.\n *\n * @param callId - Id of the tool call, as the host agent assigned it.\n * @param json - Parsed JSON output of the call.\n * @param toolKey - Tool key (see `Recording.toolKey`) for the response paths.\n * @returns Leaves registered, with their masks.\n */\n registerOutput(\n callId: string,\n json: object,\n toolKey?: string,\n options: ToolCallMasker.RegisterOptions = {},\n ): ToolCallMasker.Registered[] {\n const registered: ToolCallMasker.Registered[] = [];\n\n this.#collectJsonLeaves(json).forEach(({ path, value }) => {\n const secret = this.#secrets.isSecret(toolKey, path);\n if (!secret && (options.secretsOnly || !this.#registers(toolKey, path)))\n return;\n\n const prefix = secret ? SecretMasker.PREFIX : TOOL_CALL_MASK_PREFIX;\n let mask = this.#leafMask(prefix, callId, path);\n\n // Distinct paths can sanitize to the same mask (`a-b` and `a_b`).\n if (this.#valueByMask.has(mask)) {\n if (!secret) {\n logger.warn(`Tool call mask ${mask} is already registered, skipping`);\n return;\n }\n mask = this.#uniqueMask(mask);\n }\n\n this.#valueByMask.set(mask, value);\n // Keep the earliest call's mask, so replay substitutes that call's fresh value.\n if (!this.#maskByValue.has(value)) this.#maskByValue.set(value, mask);\n if (secret) this.#secrets.registerSecret(mask, value);\n\n logger.debug(`Registered tool call value ${mask}`);\n registered.push({ mask, value });\n });\n\n return registered;\n }\n\n #registers(toolKey: string | undefined, path: string[]): boolean {\n return (\n !this.#registerPaths.of(toolKey) ||\n this.#registerPaths.covers(toolKey, path)\n );\n }\n\n #registersNothing(\n toolKey: string | undefined,\n options: ToolCallMasker.RegisterOptions,\n ): boolean {\n if (this.#secrets.hasPaths(toolKey)) return false;\n return (\n !!options.secretsOnly || this.#registerPaths.of(toolKey)?.length === 0\n );\n }\n\n /**\n * Collects every scalar leaf of a JSON structure together with its path.\n *\n * @param json - JSON structure to walk.\n * @returns Leaves sorted by path.\n */\n #collectJsonLeaves(json: object): ToolCallMasker.Leaf[] {\n const leaves: ToolCallMasker.Leaf[] = [];\n\n const walk = (value: unknown, path: string[]) => {\n if (Array.isArray(value)) {\n value.forEach((item, index) => walk(item, [...path, String(index)]));\n return;\n }\n\n if (typeof value === \"object\" && value !== null) {\n for (const [key, nested] of Object.entries(value))\n walk(nested, [...path, key]);\n return;\n }\n\n // NOTE: Booleans and nulls carry nothing specific to the run that produced\n // them, so a tool input that happens to equal `true` would be a false\n // positive rather than a value worth substituting.\n if (typeof value !== \"string\" && typeof value !== \"number\") return;\n\n const leafValue = String(value);\n if (!leafValue.trim()) return;\n\n leaves.push({ path, value: leafValue });\n };\n\n walk(json, []);\n\n // Sorted by path rather than kept in key order, which a fresh output can\n // change: distinct paths sanitizing to the same mask then resolve the same\n // way in both phases.\n return leaves.sort((a, b) => comparePaths(a.path, b.path));\n }\n\n /**\n * Suffixes a mask already registered with the first free `_<n>`.\n *\n * @param mask - Mask taken by another leaf.\n * @returns Mask no leaf has yet.\n */\n #uniqueMask(mask: string): string {\n const base = mask.slice(0, -1);\n let index = 2;\n while (this.#valueByMask.has(`${base}_${index}>`)) index++;\n return `${base}_${index}>`;\n }\n\n /**\n * Builds the mask for a JSON leaf, e.g. `[\"items\", \"2\", \"id\"]` of tool call\n * `toolu_01` becomes `<tool-call.toolu_01.items.2.id>`.\n *\n * @param prefix - Mask family prefix, e.g. `<tool-call.`.\n * @param callId - Id of the tool call, as the host agent assigned it.\n * @param path - JSON path of the leaf.\n * @returns Mask to replace the leaf's value with.\n */\n #leafMask(prefix: string, callId: string, path: string[]): string {\n return `${prefix}${[callId, ...path].map(maskSegment).join(PATH_SEPARATOR)}>`;\n }\n\n //#endregion\n //#region Masking\n\n /**\n * Replaces values produced by tool calls with masks, so that replay can\n * substitute freshly produced ones.\n *\n * In the `whole` scope only a leaf that equals a tool call value in full is\n * replaced. A value quoted inside free-form prose is left alone: there is no\n * way to tell a value apart from the prose around it without guessing, and\n * every Alumnium tool that takes prose takes such values through `params`.\n *\n * Secrets are then masked wherever else they occur, see `maskSecrets`.\n *\n * @param input - Tool input, masked in place.\n * @param scope - Which strings a mask can take the place of.\n * @returns The same input.\n */\n mask(input: Recording.Input, scope: ToolCallMasker.Scope): Recording.Input {\n if (!this.#maskByValue.size) return input;\n\n eachStringLeaf(input, (value, path) => {\n if (scope === \"embedded\" && UNMASKED_INPUT_PATHS.has(path))\n return undefined;\n\n const mask = this.#maskByValue.get(value);\n if (mask) {\n logger.debug(\n `Masked tool call value in '${path}': '${value}' -> '${mask}'`,\n );\n return mask;\n }\n\n if (scope !== \"embedded\") return undefined;\n\n return this.#maskQuoted(value, path);\n });\n\n return this.maskSecrets(input);\n }\n\n /**\n * Masks secrets wherever they occur in every string leaf of a value, e.g. a\n * tool result, narration, or the input of a tool that must stay verbatim.\n *\n * @param value - Value to mask in place, or a string to mask.\n * @returns The same value, or the masked string.\n */\n maskSecrets<Type>(value: Type): Type {\n return this.#secrets.mask(value);\n }\n\n /**\n * Masks every quote-delimited token of a string that names a tool call value.\n *\n * Only a token a quote pair delimits is looked up. An unquoted one has no\n * boundary to trust, and guessing one is exactly what whole-value matching\n * avoids - a call that produced `9` would otherwise rewrite `--retries 19`.\n *\n * A short or common value can still match a quoted token that has\n * nothing to do with it. That ambiguity is inherent to matching by value,\n * and a length or numeric heuristic would silently break the legitimate case\n * of `{\"count\": 2}` feeding `--count \"2\"`, so there is none.\n *\n * @param value - Tool input string to scan.\n * @param path - Path of the string, used for logging.\n * @returns The string with its tokens masked, `undefined` when none matched.\n */\n #maskQuoted(value: string, path: string): string | undefined {\n const result = this.#maskQuotedRuns(value, path);\n return result === value ? undefined : result;\n }\n\n /**\n * Masks the quoted runs of a string, see `#maskQuoted`. Innermost run wins:\n * `echo '{\"id\": \"a1\"}'` yields `id` and `a1`, not the JSON around them.\n *\n * @param value - String to scan.\n * @param path - Path of the string, used for logging.\n * @returns The string with its tokens masked.\n */\n #maskQuotedRuns(value: string, path: string): string {\n return value.replace(\n QUOTED_RUN_PATTERN,\n (match, escapes: string, single?: string, double?: string) => {\n const delimiter = `${escapes}${single === undefined ? '\"' : \"'\"}`;\n const token = single ?? double ?? \"\";\n\n if (token.search(QUOTED_RUN_PATTERN) >= 0)\n return `${delimiter}${this.#maskQuotedRuns(token, path)}${delimiter}`;\n\n const mask = this.#maskByValue.get(token);\n if (!mask) return match;\n\n logger.debug(\n `Masked quoted tool call value in '${path}': '${token}' -> '${mask}'`,\n );\n\n return `${delimiter}${mask}${delimiter}`;\n },\n );\n }\n\n //#endregion\n //#region Unmasking\n\n /**\n * Substitutes fresh tool call values back into a tool input, secrets\n * wherever they occur.\n *\n * A mask with no registered value is left in place, so that\n * `findUnresolved` can name the input it was needed for.\n *\n * @param input - Masked tool input, unmasked in place.\n * @param scope - Which strings a mask can have taken the place of.\n * @returns The same input.\n */\n unmask(input: Recording.Input, scope: ToolCallMasker.Scope): Recording.Input {\n if (!this.#valueByMask.size) return input;\n\n eachStringLeaf(input, (mask, path) => {\n const value = this.#valueByMask.get(mask);\n if (value !== undefined) {\n logger.debug(\n `Unmasked tool call value in '${path}': '${mask}' -> '${value}'`,\n );\n return value;\n }\n\n if (scope === \"embedded\") return this.#unmaskQuoted(mask, path);\n });\n\n // Substituted as is, with no quoting check: a secret was masked\n // wherever it occurred, quoted or not.\n return this.#secrets.unmask(input);\n }\n\n /**\n * Substitutes fresh values for every quote-delimited mask of a string, the\n * inverse of `#maskQuoted`.\n *\n * Masking only ever emits a mask between two identical delimiters, and a mask\n * carries neither a quote nor a backslash of its own, so the quoted shape here\n * is exactly the one masking produced, escaping included.\n *\n * @param value - Masked tool input string to scan.\n * @param path - Path of the string, used for logging.\n * @returns The string with its masks replaced, `undefined` when none were.\n */\n #unmaskQuoted(value: string, path: string): string | undefined {\n let unmasked = false;\n\n const result = value.replace(\n QUOTED_TOOL_CALL_MASK_PATTERN,\n (match, delimiter: string, mask: string) => {\n const fresh = this.#valueByMask.get(mask);\n if (fresh === undefined) return match;\n\n if (this.#breaksQuoting(fresh, delimiter)) {\n logger.warn(\n `Fresh tool call value for ${mask} cannot be quoted with ${delimiter} in '${path}', leaving the mask in place`,\n );\n return match;\n }\n\n logger.debug(\n `Unmasked quoted tool call value in '${path}': '${mask}' -> '${fresh}'`,\n );\n unmasked = true;\n\n return `${delimiter}${fresh}${delimiter}`;\n },\n );\n\n return unmasked ? result : undefined;\n }\n\n /**\n * Tells whether substituting a value into a quoted run would end that run early\n * or change what the value means.\n *\n * Refusing beats escaping: an escape is shell-dialect specific, and the string\n * the value goes into is not necessarily a shell command at all. Leaving the\n * mask in place replays the command as recorded, which is visible, whereas a\n * mis-quoted command could quietly do something else.\n *\n * Inside single quotes a shell takes everything literally, so only the quote\n * itself is a problem. Inside double quotes, or the escape layer an escaped\n * delimiter implies, a backslash, `$` or backtick would still be interpreted.\n *\n * @param value - Fresh tool call value to substitute.\n * @param delimiter - Quote, possibly escaped, the recorded token was between.\n * @returns `true` when the value cannot go inside that delimiter.\n */\n #breaksQuoting(value: string, delimiter: string): boolean {\n const quote = delimiter.slice(-1);\n if (value.includes(quote)) return true;\n\n const interprets = quote === '\"' || delimiter.length > 1;\n return interprets && /[\\\\$`]/.test(value);\n }\n\n //#endregion\n}\n\nfunction comparePaths(a: string[], b: string[]): number {\n for (let index = 0; index < Math.min(a.length, b.length); index++) {\n const [left = \"\", right = \"\"] = [a[index], b[index]];\n if (left !== right) return left < right ? -1 : 1;\n }\n return a.length - b.length;\n}\n",
11
11
  "const PATH_SEPARATOR = \".\";\nconst PATH_WILDCARD = \"*\";\nconst PATH_DEEP_WILDCARD = \"**\";\nconst TOOL_KEY_SEPARATOR = \"/\";\n\n/**\n * JSON paths into tool call results, by tool key (see `Recording.toolKey`),\n * e.g. `{\"test-data/createUser\": [\"user.token\", \"sessions.*.id\", \"**.password\"]}`.\n *\n * A path is dot-separated, `*` matches any one key or array index, `**` any\n * number of them (none included), and a path also covers every leaf nested\n * under it. A `<server>/*` key applies to every tool\n * of the server that has no key of its own.\n */\nexport class ResponsePaths {\n #patterns: Record<string, string[][]>;\n\n constructor(paths: Record<string, string[]> = {}) {\n this.#patterns = Object.fromEntries(\n Object.entries(paths).map(([key, keyPaths]) => [\n key,\n keyPaths.map((path) => path.split(PATH_SEPARATOR)),\n ]),\n );\n }\n\n /**\n * Path patterns configured for a tool, its own or its server's.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @returns Patterns, `undefined` when the tool has none configured.\n */\n of(toolKey: string | undefined): string[][] | undefined {\n if (!toolKey) return undefined;\n\n const patterns = this.#patterns[toolKey];\n if (patterns) return patterns;\n\n const separatorIndex = toolKey.lastIndexOf(TOOL_KEY_SEPARATOR);\n if (separatorIndex < 0) return undefined;\n\n const server = toolKey.slice(0, separatorIndex);\n return this.#patterns[`${server}${TOOL_KEY_SEPARATOR}${PATH_WILDCARD}`];\n }\n\n /**\n * Tells whether a tool has any path configured.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n */\n has(toolKey: string | undefined): boolean {\n return !!this.of(toolKey)?.length;\n }\n\n /**\n * Tells whether a JSON leaf of a tool's result is covered: a pattern covers\n * it when its segments match the start of the leaf path - each one equal to\n * a path segment, `*` matching any one segment and `**` any number of them.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param path - JSON leaf path.\n */\n covers(toolKey: string | undefined, path: string[]): boolean {\n return (this.of(toolKey) ?? []).some((pattern) =>\n this.#matchesStart(pattern, path),\n );\n }\n\n /**\n * Tells whether a pattern matches the start of a path, see\n * `ResponsePaths.covers`.\n *\n * @param pattern - Pattern segments.\n * @param path - Path segments.\n * @param patternIndex - Pattern segment to match from.\n * @param pathIndex - Path segment to match from.\n */\n #matchesStart(\n pattern: string[],\n path: string[],\n patternIndex = 0,\n pathIndex = 0,\n ): boolean {\n if (patternIndex === pattern.length) return true;\n\n const segment = pattern[patternIndex];\n if (segment === PATH_DEEP_WILDCARD) {\n for (let index = pathIndex; index <= path.length; index++)\n if (this.#matchesStart(pattern, path, patternIndex + 1, index))\n return true;\n return false;\n }\n\n if (pathIndex === path.length) return false;\n if (segment !== PATH_WILDCARD && segment !== path[pathIndex]) return false;\n\n return this.#matchesStart(pattern, path, patternIndex + 1, pathIndex + 1);\n }\n}\n",
12
12
  "import { Telemetry } from \"@alumnium/core/internal\";\nimport { findMasks, maskSegment } from \"./maskerUtils.ts\";\nimport { ResponsePaths } from \"./ResponsePaths.ts\";\nimport { SubstringMasker } from \"./SubstringMasker.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/masker/SecretMasker.ts\");\n\nconst SECRET_MASK_PREFIX = \"<secret.\";\nconst SECRET_MASK_PATTERN = /<secret\\.\\w+(?:\\.\\w+)+(?::json)?>/g;\n\n/**\n * Masks secrets tool calls return at configured JSON paths\n * (`maskResponsePaths`) as `<secret.id.path>` wherever they occur, the stored\n * result included, so a recording never holds them in clear. Replay\n * substitutes the secrets the fresh calls return.\n *\n * Owned by `ToolCallMasker`, which decides which leaves are secrets and\n * registers them here on top of its own whole-value masking.\n */\nexport class SecretMasker extends SubstringMasker {\n static PREFIX = SECRET_MASK_PREFIX;\n\n #paths: ResponsePaths;\n // `<secret.id>` of calls with response paths but unreadable output.\n #unreadOutputs = new Set<string>();\n\n /**\n * Finds secret masks anywhere in a value.\n *\n * @param value - Value to scan.\n * @returns Masks, in order of appearance.\n */\n static findMasks(value: unknown): string[] {\n return findMasks(value, SECRET_MASK_PATTERN);\n }\n\n /**\n * @param maskResponsePaths - Response paths by tool key, see\n * `Masker.Props.maskResponsePaths`.\n */\n constructor(maskResponsePaths: Record<string, string[]> = {}) {\n super(SECRET_MASK_PATTERN);\n\n this.#paths = new ResponsePaths(maskResponsePaths);\n }\n\n /**\n * Tells whether a tool has response paths configured.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n */\n hasPaths(toolKey: string | undefined): boolean {\n return this.#paths.has(toolKey);\n }\n\n /**\n * Tells whether a JSON leaf of a tool's output is a secret, see\n * `ResponsePaths.covers`.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param path - JSON leaf path.\n */\n isSecret(toolKey: string | undefined, path: string[]): boolean {\n return this.#paths.covers(toolKey, path);\n }\n\n /**\n * Registers a secret a tool call produced.\n *\n * @param mask - Secret mask, `<secret.id.path>`.\n * @param value - Secret value.\n */\n registerSecret(mask: string, value: string) {\n this.register(mask, value);\n }\n\n /**\n * Marks the output of a call with response paths as unreadable: its secrets\n * cannot be told apart, so they are still in it in clear.\n *\n * @param callId - Id of the tool call, as the host agent assigned it.\n */\n markUnread(callId: string) {\n logger.warn(\n `Tool call ${callId} has response paths to mask, but its output is not JSON`,\n );\n this.#unreadOutputs.add(`${SECRET_MASK_PREFIX}${maskSegment(callId)}>`);\n }\n\n /**\n * An unreadable output (see `markUnread`) is always reported, as `<secret.id>`.\n */\n override findLeaks(value: unknown): string[] {\n return [...this.#unreadOutputs, ...super.findLeaks(value)];\n }\n}\n",
13
13
  "import type { Recording } from \"../Recording.ts\";\nimport { HostVarMasker } from \"./HostVarMasker.ts\";\nimport { parseInput } from \"./maskerUtils.ts\";\nimport { ToolCallMasker } from \"./ToolCallMasker.ts\";\n\nexport namespace Masker {\n export interface Props {\n /**\n * Values the host knows ahead of the run, by name: credentials, base URLs,\n * account ids. Each one is masked as `<host-var.name>` wherever it occurs in a\n * recording, and replaced with the value the host passes on replay.\n */\n hostVars?: Record<string, string> | undefined;\n /**\n * JSON paths of secrets in tool results, by tool key (see\n * `Recording.toolKey`), e.g. `{\"test-data/createUser\": [\"**.token\"]}`.\n * A path is dot-separated, `*` matches any one key or array index, `**`\n * any number of them, and a path also covers every leaf nested under it\n * (see `ResponsePaths`). Each value it matches is masked as `<secret...>`\n * wherever it occurs, the stored result included,\n * so a recording neverholds it in clear.\n */\n maskResponsePaths?: Record<string, string[]> | undefined;\n /**\n * JSON paths of the values to register in tool results, by tool key, in\n * the same format as `maskResponsePaths`. A tool with paths registers only\n * the values they cover - none for `[]` - and any other tool every value\n * of its JSON output, e.g. `{\"alumnium/*\": [], \"alumnium/start\": [\"id\"]}`\n * registers only the driver id of Alumnium's tools. A `<server>/*` key\n * applies to every tool of the server without a key of its own.\n */\n registerResponsePaths?: Record<string, string[]> | undefined;\n /**\n * Tools whose input is prose that must stay verbatim, so values produced\n * by tool calls are not masked in it. Host variables and secrets are\n * masked regardless.\n */\n verbatimTools?: string[] | undefined;\n /**\n * Tools whose input takes tool call values only as whole values, by tool\n * key (`<server>/*` covers a server), e.g. `[\"alumnium/*\"]`. A quoted value\n * is masked in any other tool's input too: in a command line a quote is\n * what delimits a value, whereas an Alumnium goal is prose an LLM reads,\n * which takes its values from `params`.\n */\n wholeValueOnlyTools?: string[] | undefined;\n }\n}\n\n/**\n * Masks the values a recording depends on but must not freeze, and substitutes\n * fresh ones back on replay. One instance covers one run, recording or replay.\n * Masks name the tool call a value came from by the id the host agent gave\n * it, so replay registers each fresh output under the id of the recorded call\n * it re-issued.\n *\n * Composes one masker per source of values:\n *\n * - `ToolCallMasker` - values tool calls produced, as `registerResponsePaths`\n * selects them. Masked as `<tool-call.id.path>` where a later tool input uses\n * them, or as `<secret.id.path>` wherever they occur when at one of the\n * `maskResponsePaths`;\n * - `HostVarMasker` - values the host passes, masked as `<host-var.name>`\n * wherever they occur.\n */\nexport class Masker {\n #toolCalls: ToolCallMasker;\n #hostVars: HostVarMasker;\n\n constructor(props: Masker.Props = {}) {\n const { hostVars, ...toolCallProps } = props;\n this.#toolCalls = new ToolCallMasker(toolCallProps);\n this.#hostVars = new HostVarMasker(hostVars);\n }\n\n /**\n * Tells whether tool call values are masked in a tool's input at all, see\n * `ToolCallMasker.masksInputOf`.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n */\n masksToolCallValuesOf(toolKey: string): boolean {\n return this.#toolCalls.masksInputOf(toolKey);\n }\n\n /**\n * Registers values produced by a tool call, Alumnium or any other, so that\n * they can be masked in the tool inputs that follow it (recording) or\n * substituted back into them (replay). Which values count is up to\n * `registerResponsePaths`, which are secrets up to `maskResponsePaths`.\n *\n * Only JSON output is considered, and masks are derived from the JSON path of\n * each leaf. The same leaf therefore yields the same mask in both phases, even\n * when a fresh output reorders its keys or adds new ones.\n *\n * @param callId - Id of the tool call, as the host agent assigned it.\n * @param content - Tool call result content.\n * @param toolKey - Tool key (see `Recording.toolKey`) for the response paths.\n */\n registerOutput(\n callId: string,\n content: Recording.Content,\n toolKey?: string,\n options?: ToolCallMasker.RegisterOptions,\n ) {\n this.#toolCalls.registerContent(callId, content, toolKey, options);\n }\n\n //#region Inspection\n\n /**\n * Finds masks that unmasking could not resolve, which means a replay lacks a\n * value its recording had: a tool produced fewer values, or the\n * host did not pass a variable.\n *\n * A tool call value mask only counts when it takes up a whole value\n * (see `ToolCallMasker.findUnresolved`). Host variable and secret masks\n * count anywhere.\n *\n * @param input - Unmasked tool input.\n * @returns Unresolved masks, empty when everything resolved.\n */\n static findUnresolvedMasks(input: Recording.Input): string[] {\n return [\n ...new Set([\n ...ToolCallMasker.findUnresolved(input),\n ...HostVarMasker.findMasks(input),\n ]),\n ];\n }\n\n /**\n * Finds the host variable names a value refers to through `<host-var.name>`\n * masks.\n *\n * @param value - Value to scan, e.g. recording steps.\n * @returns Unique host variable names, in order of appearance.\n */\n static findHostVarNames(value: unknown): string[] {\n return HostVarMasker.findNames(value);\n }\n\n /**\n * Finds host variables and secrets whose values survived masking anywhere in\n * a value, e.g. a recording about to be saved.\n *\n * @param value - Value to scan.\n * @returns Masks of the leaked values, never the values themselves.\n */\n findLeaks(value: unknown): string[] {\n return [\n ...this.#hostVars.findLeaks(value),\n ...this.#toolCalls.findLeaks(value),\n ];\n }\n\n //#endregion\n //#region Masking\n\n /**\n * Masks a tool input: tool call values (see `wholeValueOnlyTools` and\n * `verbatimTools`), host variables and secrets.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param input - Tool input, left untouched.\n * @returns Masked copy of the input.\n */\n maskInput(toolKey: string, input: unknown): Recording.Input {\n return this.#hostVars.mask(\n this.#toolCalls.maskInput(toolKey, parseInput(input)),\n );\n }\n\n /**\n * Masks a tool result, Alumnium or any other. Tool call values stay as they\n * were, since nothing reads them back from a recording, but host variables\n * and secrets do not - including the ones this very result produced, so\n * register the output first.\n *\n * @param content - Tool result content, left untouched.\n * @returns Masked copy of the content.\n */\n maskResult(content: Recording.Content): Recording.Content {\n return this.#maskSecrets(structuredClone(content));\n }\n\n /**\n * Masks host variables and secrets in the agent's narration.\n *\n * Tool call values are not masked here. Masking matches whole values\n * and quoted tokens, neither of which is how prose mentions a value, so a\n * recording can carry a value the agent happened to write out.\n *\n * @param text - Narration as the agent wrote it.\n * @returns Masked narration.\n */\n maskNarration(text: string): string {\n return this.#maskSecrets(text);\n }\n\n /**\n * Secrets go first, since they are specific to the run, so a host\n * variable that happens to be part of one does not break it apart. The same\n * goes for every other `mask` method.\n */\n #maskSecrets<Type>(value: Type): Type {\n return this.#hostVars.mask(this.#toolCalls.maskSecrets(value));\n }\n\n //#endregion\n\n //#region Unmasking\n\n /**\n * Substitutes fresh values back into a tool input, e.g. so that a shell\n * command runs with the value the fresh run produced.\n *\n * A mask with no registered value is left in place on purpose, so\n * that `findUnresolvedMasks` can name the input it was needed for.\n *\n * @param toolKey - Tool key, see `Recording.toolKey`.\n * @param input - Masked tool input, left untouched.\n * @returns Unmasked copy of the input.\n */\n unmaskInput(toolKey: string, input: unknown): Recording.Input {\n return this.#hostVars.unmask(\n this.#toolCalls.unmaskInput(toolKey, parseInput(input)),\n );\n }\n\n //#endregion\n}\n",
14
- "import { ALUMNIUM_VERSION, Telemetry } from \"@alumnium/core/internal\";\nimport { Masker } from \"./masker/Masker.ts\";\nimport { Recording } from \"./Recording.ts\";\nimport { RecordingLeakError } from \"./RecordingLeakError.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/Recorder.ts\");\n\nconst DEFAULT_ALUMNIUM_SERVER = \"alumnium\";\nconst ALUMNIUM_START_TOOL = \"start\";\n\nexport namespace Recorder {\n export interface Props extends Masker.Props {\n text: string;\n agent: string;\n /** See `Recording.Schema` `version`, `Recording.DEFAULT_VERSION` by default. */\n version?: number | undefined;\n /** Name the host gave Alumnium's MCP server, `alumnium` by default. */\n alumniumServer?: string | undefined;\n }\n\n export interface ToolUse {\n id: string;\n /** MCP server the tool belongs to, absent for a host tool (e.g. `Bash`). */\n server?: string | undefined;\n tool: string;\n input: unknown;\n }\n\n export interface ToolResult {\n id: string;\n content: Recording.Content;\n isError?: boolean | undefined;\n }\n\n export interface Narration {\n narration: Recording.NarrationStep[\"narration\"];\n text: string;\n }\n}\n\n/**\n * Agent-agnostic sink turning the host agent's tool calls and narration into a\n * replayable recording. The host owns the agent loop and reports what happens\n * in it. The recorder masks the values a replay has to refresh or must never\n * see in clear (see `Masker`) and pairs each call with its result.\n */\nexport class Recorder {\n #text: string;\n #agent: string;\n #version: number;\n #masker: Masker;\n #steps: Recording.Step[] = [];\n #pendingCalls = new Map<string, Recording.ToolCall>();\n #recording: Recording.Type | undefined;\n\n constructor(props: Recorder.Props) {\n const { text, agent, version, alumniumServer, ...maskerProps } = props;\n this.#version = version ?? Recording.DEFAULT_VERSION;\n this.#text = text;\n this.#agent = agent;\n const server = alumniumServer ?? DEFAULT_ALUMNIUM_SERVER;\n this.#masker = new Masker({\n ...maskerProps,\n registerResponsePaths: {\n ...this.#alumniumRegisterResponsePaths(server),\n ...maskerProps.registerResponsePaths,\n },\n // Alumnium's goals and statements are prose an LLM reads, which takes\n // its values from `params`.\n wholeValueOnlyTools: [\n `${server}/*`,\n ...(maskerProps.wholeValueOnlyTools ?? []),\n ],\n });\n }\n\n /**\n * Records a tool call, held until its result arrives.\n *\n * Masked here, on the call, and not once the step is pushed in `onToolResult`.\n * A tool call's output is only registered once its result arrives,\n * so a call can neither mask its own input, nor - when the\n * agent runs tools in parallel - be masked with a value that did not exist\n * yet when it was issued.\n *\n * A tool replay cannot execute is masked too. Its input is\n * never re-run, so nothing gets substituted back into it, but masking still\n * keeps the value a recording happened to produce - often a credential - out\n * of the stored recording.\n *\n * @param use - Tool call as the host agent issued it.\n */\n onToolUse(use: Recorder.ToolUse) {\n this.#assertNotFinished();\n\n const { id, server, tool, input } = use;\n const toolKey = Recording.toolKey({ server, tool });\n\n logger.debug(`Recording tool use '${toolKey}' ({id})`, { id });\n\n this.#pendingCalls.set(id, {\n id,\n ...(server ? { server } : {}),\n tool,\n input: this.#masker.maskInput(toolKey, input),\n });\n }\n\n /**\n * Records the result of a tool call, pushing the call as a step.\n *\n * Steps land in the order results arrive. The values a call produced are\n * registered under its id, which later masks refer to.\n *\n * @param toolResult - Result of a call reported with `onToolUse`.\n */\n onToolResult(toolResult: Recorder.ToolResult) {\n this.#assertNotFinished();\n\n const { id, content } = toolResult;\n const isError = Boolean(toolResult.isError);\n\n const call = this.#pendingCalls.get(id);\n if (!call) {\n logger.debug(`No recorded tool use for result '${id}', ignoring it`);\n return;\n }\n\n this.#pendingCalls.delete(id);\n\n logger.debug(`Recording '${call.tool}' tool result`);\n\n // Replay skips a failed call, so only its secrets are registered.\n this.#masker.registerOutput(id, content, Recording.toolKey(call), {\n secretsOnly: isError,\n });\n\n this.#steps.push({\n kind: \"tool-call\",\n call,\n result: { content: structuredClone(content), isError },\n });\n }\n\n /**\n * Records what the agent said, so that a replay of this recording reads the\n * way the recording did. Nothing is executed from it.\n *\n * Pushed as it arrives, while a tool call is pushed once its result\n * does. Narration therefore lands ahead of the call it introduced, which is\n * the order the agent produced them in.\n *\n * @param narration - Prose as the agent wrote it.\n */\n onNarration(narration: Recorder.Narration) {\n this.#assertNotFinished();\n\n // Empty prose is dropped rather than stored.\n if (!narration.text.trim()) return;\n\n this.#steps.push({\n kind: \"narration\",\n narration: narration.narration,\n text: narration.text,\n });\n }\n\n /**\n * Finishes the recording with the run's verdict. Calls still waiting for a\n * result are dropped, since replay has nothing to compare them against.\n *\n * @param verdict - Verdict the host agent reached.\n * @returns Finished recording.\n * @throws {RecordingLeakError} when a host variable or a secret survived\n * masking, so that the recording is never stored with it.\n */\n finish(verdict: Recording.Verdict): Recording.Type {\n this.#assertNotFinished();\n\n if (this.#pendingCalls.size)\n logger.warn(\n `Dropping ${this.#pendingCalls.size} tool calls with no result: ${[\n ...this.#pendingCalls.values(),\n ]\n .map(({ tool }) => tool)\n .join(\", \")}`,\n );\n this.#pendingCalls.clear();\n\n const recording: Recording.Type = {\n id: Recording.textToId(this.#text),\n version: this.#version,\n text: this.#text,\n agent: this.#agent,\n alumnium: { version: ALUMNIUM_VERSION },\n hostVars: [],\n steps: this.#steps.map((step) => this.#maskStep(step)),\n verdict: {\n status: verdict.status,\n details: this.#masker.maskNarration(verdict.details),\n },\n };\n\n const masked = [\n recording.steps.map((step) =>\n step.kind === \"narration\"\n ? step.text\n : [step.call.input, step.result.content],\n ),\n recording.verdict.details,\n ];\n\n const leaks = this.#masker.findLeaks(masked);\n if (leaks.length) throw new RecordingLeakError(leaks);\n\n recording.hostVars = Masker.findHostVarNames(masked);\n this.#recording = recording;\n\n return recording;\n }\n\n /**\n * Masks a step's result or narration. Done on finish rather than as the step\n * arrives, once every secret of the run is registered: a result can arrive\n * ahead of the parallel call producing a secret it holds. Neither is ever\n * executed, so a later secret can be masked in them safely, unlike in an\n * input (see `onToolUse`).\n *\n * @param step - Step as recorded.\n * @returns Masked step.\n */\n #maskStep(step: Recording.Step): Recording.Step {\n if (step.kind === \"narration\") {\n return { ...step, text: this.#masker.maskNarration(step.text) };\n } else {\n return {\n ...step,\n result: {\n ...step.result,\n content: this.#masker.maskResult(step.result.content),\n },\n };\n }\n }\n\n /**\n * Response paths of Alumnium's own tools worth registering: only the driver\n * id a `start` returns. Every other output is the outcome of the call,\n * re-produced by replaying it, and its small values - seconds waited, token\n * counts - would otherwise take the mask of a value a later non-Alumnium call\n * produces, since the earliest call's mask wins.\n *\n * @param server - Name the host gave Alumnium's MCP server.\n * @returns `registerResponsePaths` for Alumnium's tools.\n */\n #alumniumRegisterResponsePaths(\n server: string = DEFAULT_ALUMNIUM_SERVER,\n ): Record<string, string[]> {\n return {\n [`${server}/*`]: [],\n [`${server}/${ALUMNIUM_START_TOOL}`]: [\"id\"],\n };\n }\n\n #assertNotFinished() {\n if (this.#recording) throw new Error(\"Recording is already finished\");\n }\n}\n",
14
+ "import { spawn } from \"node:child_process\";\nimport z from \"zod\";\nimport { Telemetry } from \"@alumnium/core/internal\";\nimport type { Recording } from \"../Recording.ts\";\nimport type { ToolExecutor } from \"./ToolExecutor.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/player/BashExecutor.ts\");\n\nconst BASH_TOOL = \"Bash\";\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\nconst BashInput = z.object({\n command: z.string(),\n /** Milliseconds, as the host agent's `Bash` tool takes it. */\n timeout: z.number().positive().optional(),\n});\n\nexport namespace BashExecutor {\n export interface Props {\n allow: (string | RegExp)[];\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n timeoutMs?: number | undefined;\n }\n}\n\nexport class BashExecutor implements ToolExecutor {\n #props: BashExecutor.Props;\n\n constructor(props: BashExecutor.Props) {\n this.#props = props;\n }\n\n handles(call: ToolExecutor.Call): boolean {\n return !call.server && call.tool === BASH_TOOL;\n }\n\n async execute(call: ToolExecutor.Call): Promise<Recording.ToolResult> {\n const { command, timeout } = BashInput.parse(call.input);\n\n if (!this.#allows(command))\n throw new Error(`Command is not allowed to replay: ${command}`);\n\n logger.debug(`Running command: ${command}`);\n\n const { code, stdout, stderr } = await this.#run(command, {\n cwd: this.#props.cwd,\n // oxlint-disable-next-line node/no-process-env -- Commands inherit it\n env: { ...process.env, ...this.#props.env },\n timeoutMs: timeout ?? this.#props.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n });\n\n // Only stdout on success, so that whatever a command logs to stderr does\n // not break the JSON a later call takes its values from.\n if (code === 0) return { content: stdout, isError: false };\n\n return {\n content: [stdout, stderr, `Exit code ${code}`].filter(Boolean).join(\"\\n\"),\n isError: true,\n };\n }\n\n #allows(command: string): boolean {\n return this.#props.allow.some((allowed) =>\n typeof allowed === \"string\"\n ? command.startsWith(allowed)\n : allowed.test(command),\n );\n }\n\n #run(command: string, options: RunOptions): Promise<RunResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(\"bash\", [\"-c\", command], {\n cwd: options.cwd,\n env: options.env,\n timeout: options.timeoutMs,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n child.stdout.setEncoding(\"utf8\").on(\"data\", (data) => (stdout += data));\n child.stderr.setEncoding(\"utf8\").on(\"data\", (data) => (stderr += data));\n\n child.on(\"error\", reject);\n child.on(\"close\", (code) => resolve({ code, stdout, stderr }));\n });\n }\n}\n\ninterface RunOptions {\n cwd: string | undefined;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n}\n\ninterface RunResult {\n code: number | null;\n stdout: string;\n stderr: string;\n}\n",
15
+ "import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { ALUMNIUM_CORE_VERSION, Telemetry } from \"@alumnium/core/internal\";\nimport { Recording } from \"../Recording.ts\";\nimport type { ToolExecutor } from \"./ToolExecutor.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/player/McpExecutor.ts\");\n\nconst DEFAULT_RETRIES = 2;\nconst DEFAULT_RETRY_DELAY_MS = 1000;\n\nexport namespace McpExecutor {\n export interface StdioServer {\n type?: \"stdio\" | undefined;\n command: string;\n args?: string[] | undefined;\n env?: Record<string, string> | undefined;\n cwd?: string | undefined;\n }\n\n export interface HttpServer {\n type: \"http\";\n url: string;\n headers?: Record<string, string> | undefined;\n }\n\n export interface ClientServer {\n type: \"client\";\n client: Client;\n }\n\n export type Server = StdioServer | HttpServer | ClientServer;\n\n export interface Props {\n servers: Record<string, Server>;\n retries?: number | undefined;\n retryDelayMs?: number | undefined;\n }\n}\n\nexport class McpExecutor implements ToolExecutor {\n #servers: Record<string, McpExecutor.Server>;\n #retries: number;\n #retryDelayMs: number;\n #clients = new Map<string, Promise<Client>>();\n\n constructor(props: McpExecutor.Props) {\n this.#servers = props.servers;\n this.#retries = props.retries ?? DEFAULT_RETRIES;\n this.#retryDelayMs = props.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n }\n\n handles(call: ToolExecutor.Call): boolean {\n return Boolean(call.server && call.server in this.#servers);\n }\n\n async execute(\n call: ToolExecutor.Call,\n options: ToolExecutor.ExecuteOptions = {},\n ): Promise<Recording.ToolResult> {\n const toolKey = Recording.toolKey(call);\n const client = await this.#client(call.server!);\n\n for (let attempt = 0; ; attempt++) {\n const canRetry = attempt < this.#retries;\n const delayMs = this.#retryDelayMs * 2 ** attempt;\n\n try {\n const result = await client.callTool({\n name: call.tool,\n arguments: call.input,\n ...(options.meta ? { _meta: options.meta } : {}),\n });\n\n const toolResult: Recording.ToolResult = {\n content: (result.content ?? []) as Recording.Content,\n isError: Boolean(result.isError),\n };\n if (!toolResult.isError || !canRetry) return toolResult;\n\n logger.warn(`Tool '${toolKey}' reported an error, retrying`);\n } catch (error) {\n if (!canRetry) throw error;\n\n logger.warn(`Tool '${toolKey}' failed, retrying: {error}`, { error });\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n\n async close(): Promise<void> {\n const clients = [...this.#clients];\n this.#clients.clear();\n\n await Promise.all(\n clients.map(async ([name, client]) => {\n // A client the host connected is the host's to close.\n if (this.#servers[name]?.type === \"client\") return;\n\n try {\n await (await client).close();\n } catch (error) {\n logger.warn(`Failed to close MCP server '${name}': {error}`, {\n error,\n });\n }\n }),\n );\n }\n\n #client(name: string): Promise<Client> {\n let client = this.#clients.get(name);\n if (!client) {\n client = this.#connect(name);\n // A failed connection is retried on the next call.\n client.catch(() => this.#clients.delete(name));\n this.#clients.set(name, client);\n }\n return client;\n }\n\n async #connect(name: string): Promise<Client> {\n const server = this.#servers[name];\n if (!server) throw new Error(`Unknown MCP server '${name}'`);\n if (server.type === \"client\") return server.client;\n\n logger.debug(`Connecting to MCP server '${name}'`);\n\n const transport: Transport =\n server.type === \"http\"\n ? // Its optional `sessionId` breaks `exactOptionalPropertyTypes`.\n (new StreamableHTTPClientTransport(new URL(server.url), {\n requestInit: { headers: server.headers ?? {} },\n }) as Transport)\n : new StdioClientTransport({\n command: server.command,\n args: server.args ?? [],\n env: { ...this.#definedEnv(), ...server.env },\n ...(server.cwd ? { cwd: server.cwd } : {}),\n stderr: \"inherit\",\n });\n\n const client = new Client({\n name: \"alumnium-test-player\",\n version: ALUMNIUM_CORE_VERSION,\n });\n await client.connect(transport);\n return client;\n }\n\n #definedEnv(): Record<string, string> {\n return Object.fromEntries(\n // oxlint-disable-next-line node/no-process-env -- Servers inherit it\n Object.entries(process.env).filter(\n (entry): entry is [string, string] => entry[1] !== undefined,\n ),\n );\n }\n}\n",
16
+ "import z from \"zod\";\nimport { Env, Telemetry } from \"@alumnium/core/internal\";\nimport {\n alumniumMaskerProps,\n DEFAULT_ALUMNIUM_SERVER,\n} from \"../masker/alumniumMaskerProps.ts\";\nimport { Masker } from \"../masker/Masker.ts\";\nimport { contentTexts, parseJson } from \"../masker/maskerUtils.ts\";\nimport { Recording } from \"../Recording.ts\";\nimport type { ToolExecutor } from \"./ToolExecutor.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/player/Player.ts\");\n\nconst START_TOOL = \"start\";\nconst STOP_TOOL = \"stop\";\nconst CHECK_TOOL = \"check\";\n\nconst NO_CACHE_META = { \"alumnium.ai/cache\": false };\nconst NO_CHANGE_ANALYSIS_META = { \"alumnium.ai/change-analysis\": false };\n\nconst DEFAULT_SKIP_TOOLS = [\"ToolSearch\", \"TodoWrite\"];\n\nconst DriverOutput = z.object({ id: z.string() });\nconst CheckOutput = z.object({ result: z.enum([\"success\", \"failure\"]) });\n\nexport namespace Player {\n export interface Props extends Masker.Props {\n executor: ToolExecutor;\n /** Name of Alumnium MCP server, `alumnium` by default. */\n alumniumServer?: string | undefined;\n skipTools?: string[] | undefined;\n checkConfirmations?: number | undefined;\n observer?: Observer | undefined;\n }\n\n export interface Observer {\n onNarration?(step: Recording.NarrationStep): void;\n onToolCall?(call: ToolExecutor.Call): void;\n onToolResult?(call: ToolExecutor.Call, result: Recording.ToolResult): void;\n onToolSkipped?(call: Recording.ToolCall, reason: string): void;\n onCheck?(call: ToolExecutor.Call, outcome: CheckOutcome): void;\n }\n\n /**\n * How a replayed `check` verdict compares with the recorded one:\n *\n * - `agreed` - the same verdict;\n * - `improved` - recorded failing, passing now, which does not fail the\n * replay: the agent may have gone on past it, or the app got fixed;\n * - `unstable` - disagreed, then agreed when re-asked;\n * - `disagreed` - disagreed however many times it was re-asked, which\n * fails the replay.\n */\n export type CheckOutcome = \"agreed\" | \"improved\" | \"unstable\" | \"disagreed\";\n\n export interface Replayed {\n status: \"replayed\";\n verdict: Recording.Verdict;\n }\n\n export interface Diverged {\n status: \"diverged\";\n error: string;\n /** Index of the step it diverged at in `Recording.steps`. */\n stepIndex: number;\n }\n\n export type Result = Replayed | Diverged;\n}\n\nexport class Player {\n #options: PlayerRun.Options;\n #maskerProps: Masker.Props;\n\n constructor(props: Player.Props) {\n const {\n executor,\n alumniumServer = DEFAULT_ALUMNIUM_SERVER,\n skipTools = DEFAULT_SKIP_TOOLS,\n checkConfirmations,\n observer = {},\n ...maskerProps\n } = props;\n\n this.#options = {\n executor,\n server: alumniumServer,\n skipTools: new Set(skipTools),\n checkConfirmations,\n observer,\n };\n this.#maskerProps = alumniumMaskerProps(maskerProps, alumniumServer);\n }\n\n /**\n * Replays a recording, e.g. one `TestCache.lookup` found.\n *\n * @param recording - Recording to replay.\n * @returns Replay result.\n */\n async play(recording: Recording.Type): Promise<Player.Result> {\n const run = new PlayerRun(this.#options, new Masker(this.#maskerProps));\n try {\n return await run.play(recording);\n } finally {\n await run.stopDrivers();\n }\n }\n}\n\nclass Diverged extends Error {}\n\nnamespace PlayerRun {\n export interface Options {\n executor: ToolExecutor;\n server: string;\n skipTools: Set<string>;\n checkConfirmations: number | undefined;\n observer: Player.Observer;\n }\n}\n\nclass PlayerRun {\n #options: PlayerRun.Options;\n #masker: Masker;\n #driverIds = new Set<string>(); // Started and not stopped yet.\n\n constructor(options: PlayerRun.Options, masker: Masker) {\n this.#options = options;\n this.#masker = masker;\n }\n\n get #observer(): Player.Observer {\n return this.#options.observer;\n }\n\n async play(recording: Recording.Type): Promise<Player.Result> {\n const stepsCount = Recording.executableStepsCount(recording);\n let stepNumber = 0;\n\n for (const [stepIndex, step] of recording.steps.entries()) {\n if (step.kind === \"narration\") {\n this.#observer.onNarration?.(step);\n continue;\n }\n\n logger.info(`Replaying step ${++stepNumber}/${stepsCount}`);\n\n try {\n await this.#playToolStep(step);\n } catch (error) {\n const message = error instanceof Diverged ? error.message : `${error}`;\n logger.error(`Replay diverged at step ${stepNumber}: ${message}`);\n return { status: \"diverged\", error: message, stepIndex };\n }\n }\n\n logger.info(`Replayed all ${stepsCount} steps`);\n return { status: \"replayed\", verdict: recording.verdict };\n }\n\n async stopDrivers() {\n for (const id of this.#driverIds) {\n try {\n logger.info(`Stopping driver ${id} the replay did not stop`);\n await this.#options.executor.execute({\n server: this.#options.server,\n tool: STOP_TOOL,\n input: { id, save_cache: false },\n });\n } catch (error) {\n logger.warn(`Failed to stop driver ${id}: {error}`, { error });\n }\n }\n this.#driverIds.clear();\n }\n\n async #playToolStep(step: Recording.ToolStep) {\n const { call: recordedCall, result: recordedResult } = step;\n const { id, ...callProps } = recordedCall;\n const toolKey = Recording.toolKey(recordedCall);\n\n // A call that failed when recorded produced no values, and the calls\n // that follow are there because it failed - usually its retry, which\n // running it again could get in the way of.\n if (recordedResult.isError)\n return this.#skip(recordedCall, \"it failed when recorded\");\n\n if (!this.#options.executor.handles(callProps)) {\n if (this.#options.skipTools.has(toolKey))\n return this.#skip(recordedCall, \"it only exists inside the agent\");\n throw new Diverged(`No executor for tool '${toolKey}'`);\n }\n\n const call: ToolExecutor.Call = {\n ...callProps,\n input: this.#masker.unmaskInput(toolKey, recordedCall.input),\n };\n\n const unresolved = Masker.findUnresolvedMasks(call.input);\n if (unresolved.length)\n throw new Diverged(\n `Tool '${toolKey}' input has unresolved masks: ${unresolved.join(\", \")}`,\n );\n\n const result = await this.#execute(\n call,\n call.server === this.#options.server && call.tool === START_TOOL\n ? { meta: NO_CHANGE_ANALYSIS_META }\n : undefined,\n );\n\n if (result.isError)\n throw new Diverged(\n `Tool '${toolKey}' failed: ${contentTexts(result.content).join(\"\\n\")}`,\n );\n\n // Under the recorded call's id, which the masks of later inputs name.\n this.#masker.registerOutput(id, result.content, toolKey);\n\n if (call.server !== this.#options.server) return;\n\n switch (call.tool) {\n case START_TOOL: {\n const driverId = this.#parseOutput(result.content, DriverOutput)?.id;\n if (driverId) this.#driverIds.add(driverId);\n break;\n }\n\n case STOP_TOOL: {\n const driverId = DriverOutput.safeParse(call.input).data?.id;\n if (driverId) this.#driverIds.delete(driverId);\n break;\n }\n\n case CHECK_TOOL:\n await this.#compareCheck(call, recordedResult, result);\n break;\n }\n }\n\n async #execute(\n call: ToolExecutor.Call,\n options?: ToolExecutor.ExecuteOptions,\n ): Promise<Recording.ToolResult> {\n this.#observer.onToolCall?.(call);\n const result = await this.#options.executor.execute(call, options);\n this.#observer.onToolResult?.(call, result);\n return result;\n }\n\n #skip(call: Recording.ToolCall, reason: string) {\n logger.info(`Skipping tool '${Recording.toolKey(call)}', ${reason}`);\n this.#observer.onToolSkipped?.(call, reason);\n }\n\n async #compareCheck(\n call: ToolExecutor.Call,\n recorded: Recording.ToolResult,\n replayed: Recording.ToolResult,\n ) {\n const confirmations =\n this.#options.checkConfirmations ?? Env.ALUMNIUM_CHECK_CONFIRMATIONS;\n const expected = this.#checkVerdict(recorded);\n let actual = this.#checkVerdict(replayed);\n let outcome: Player.CheckOutcome;\n\n if (!expected || !actual) {\n throw new Diverged(\n `Cannot read '${Recording.toolKey(call)}' verdict to compare`,\n );\n } else if (expected === actual) {\n outcome = \"agreed\";\n } else if (expected === \"failure\") {\n outcome = \"improved\";\n } else {\n let attempt = 0;\n while (actual !== expected && attempt++ < confirmations) {\n logger.info(`Re-asking check ${attempt}/${confirmations}`);\n const result = await this.#execute(call, { meta: NO_CACHE_META });\n actual = result.isError ? null : this.#checkVerdict(result);\n }\n\n outcome = actual === expected ? \"unstable\" : \"disagreed\";\n }\n\n this.#observer.onCheck?.(call, outcome);\n\n if (outcome === \"disagreed\")\n throw new Diverged(\n `Check '${call.input[\"statement\"]}' failed where it passed when recorded` +\n (confirmations\n ? `, re-asked ${confirmations} times with the cache bypassed`\n : \"\"),\n );\n }\n\n #checkVerdict(\n result: Recording.ToolResult,\n ): z.infer<typeof CheckOutput>[\"result\"] | null {\n return this.#parseOutput(result.content, CheckOutput)?.result ?? null;\n }\n\n #parseOutput<Type>(\n content: Recording.Content,\n schema: z.ZodType<Type>,\n ): Type | null {\n for (const text of contentTexts(content)) {\n const parsed = schema.safeParse(parseJson(text));\n if (parsed.success) return parsed.data;\n }\n return null;\n }\n}\n",
17
+ "import type { Masker } from \"./Masker.ts\";\n\nexport const DEFAULT_ALUMNIUM_SERVER = \"alumnium\";\nexport const ALUMNIUM_START_TOOL = \"start\";\n\n/**\n * Masker props a run against Alumnium's MCP server needs on top of the host's.\n * Recording and replay must mask with the same props, or a replay would not\n * derive the masks its recording holds.\n *\n * @param props - Host's masker props.\n * @param server - Name the host gave Alumnium's MCP server.\n * @returns Masker props.\n */\nexport function alumniumMaskerProps(\n props: Masker.Props,\n server: string = DEFAULT_ALUMNIUM_SERVER,\n): Masker.Props {\n return {\n ...props,\n registerResponsePaths: {\n ...alumniumRegisterResponsePaths(server),\n ...props.registerResponsePaths,\n },\n // Alumnium's goals and statements are prose an LLM reads, which takes\n // its values from `params`.\n wholeValueOnlyTools: [`${server}/*`, ...(props.wholeValueOnlyTools ?? [])],\n };\n}\n\n/**\n * Response paths of Alumnium's own tools worth registering: only the driver\n * id a `start` returns. Every other output is the outcome of the call,\n * re-produced by replaying it, and its small values - seconds waited, token\n * counts - would otherwise take the mask of a value a later non-Alumnium call\n * produces, since the earliest call's mask wins.\n *\n * @param server - Name the host gave Alumnium's MCP server.\n * @returns `registerResponsePaths` for Alumnium's tools.\n */\nfunction alumniumRegisterResponsePaths(\n server: string,\n): Record<string, string[]> {\n return {\n [`${server}/*`]: [],\n [`${server}/${ALUMNIUM_START_TOOL}`]: [\"id\"],\n };\n}\n",
18
+ "import { Recording } from \"../Recording.ts\";\n\nexport namespace ToolExecutor {\n export type Call = Omit<Recording.ToolCall, \"id\">;\n\n export interface ExecuteOptions {\n meta?: Record<string, unknown> | undefined;\n }\n}\n\nexport interface ToolExecutor {\n handles(call: ToolExecutor.Call): boolean;\n\n execute(\n call: ToolExecutor.Call,\n options?: ToolExecutor.ExecuteOptions,\n ): Promise<Recording.ToolResult>;\n\n close?(): Promise<void>;\n}\n\n/**\n * Puts executors together into one, which executes a call with the first\n * executor handling it.\n *\n * @param executors - Executors, in order of precedence.\n * @returns Composed executor.\n */\nexport function composeExecutors(...executors: ToolExecutor[]): ToolExecutor {\n const find = (call: ToolExecutor.Call) =>\n executors.find((executor) => executor.handles(call));\n\n return {\n handles: (call) => Boolean(find(call)),\n\n async execute(call, options) {\n const executor = find(call);\n if (!executor)\n throw new Error(\n `No executor handles tool '${Recording.toolKey(call)}'`,\n );\n return executor.execute(call, options);\n },\n\n async close() {\n await Promise.all(\n executors.map(async (executor) => await executor.close?.()),\n );\n },\n };\n}\n",
19
+ "import { ALUMNIUM_CORE_VERSION, Telemetry } from \"@alumnium/core/internal\";\nimport { alumniumMaskerProps } from \"./masker/alumniumMaskerProps.ts\";\nimport { Masker } from \"./masker/Masker.ts\";\nimport { Recording } from \"./Recording.ts\";\nimport { RecordingLeakError } from \"./RecordingLeakError.ts\";\n\nconst { logger } = Telemetry.get(\"packages/typescript/test/src/Recorder.ts\");\n\nexport namespace Recorder {\n export interface Props extends Masker.Props {\n text: string;\n agent: string;\n /** See `Recording.Schema` `version`, `Recording.DEFAULT_VERSION` by default. */\n version?: number | undefined;\n /** Name the host gave Alumnium's MCP server, `alumnium` by default. */\n alumniumServer?: string | undefined;\n }\n\n export interface ToolUse {\n id: string;\n /** MCP server the tool belongs to, absent for a host tool (e.g. `Bash`). */\n server?: string | undefined;\n tool: string;\n input: unknown;\n }\n\n export interface ToolResult {\n id: string;\n content: Recording.Content;\n isError?: boolean | undefined;\n }\n\n export interface Narration {\n narration: Recording.NarrationStep[\"narration\"];\n text: string;\n }\n}\n\n/**\n * Agent-agnostic sink turning the host agent's tool calls and narration into a\n * replayable recording. The host owns the agent loop and reports what happens\n * in it. The recorder masks the values a replay has to refresh or must never\n * see in clear (see `Masker`) and pairs each call with its result.\n */\nexport class Recorder {\n #text: string;\n #agent: string;\n #version: number;\n #masker: Masker;\n #steps: Recording.Step[] = [];\n #pendingCalls = new Map<string, Recording.ToolCall>();\n #recording: Recording.Type | undefined;\n\n constructor(props: Recorder.Props) {\n const { text, agent, version, alumniumServer, ...maskerProps } = props;\n this.#version = version ?? Recording.DEFAULT_VERSION;\n this.#text = text;\n this.#agent = agent;\n this.#masker = new Masker(alumniumMaskerProps(maskerProps, alumniumServer));\n }\n\n /**\n * Records a tool call, held until its result arrives.\n *\n * Masked here, on the call, and not once the step is pushed in `onToolResult`.\n * A tool call's output is only registered once its result arrives,\n * so a call can neither mask its own input, nor - when the\n * agent runs tools in parallel - be masked with a value that did not exist\n * yet when it was issued.\n *\n * A tool replay cannot execute is masked too. Its input is\n * never re-run, so nothing gets substituted back into it, but masking still\n * keeps the value a recording happened to produce - often a credential - out\n * of the stored recording.\n *\n * @param use - Tool call as the host agent issued it.\n */\n onToolUse(use: Recorder.ToolUse) {\n this.#assertNotFinished();\n\n const { id, server, tool, input } = use;\n const toolKey = Recording.toolKey({ server, tool });\n\n logger.debug(`Recording tool use '${toolKey}' ({id})`, { id });\n\n this.#pendingCalls.set(id, {\n id,\n ...(server ? { server } : {}),\n tool,\n input: this.#masker.maskInput(toolKey, input),\n });\n }\n\n /**\n * Records the result of a tool call, pushing the call as a step.\n *\n * Steps land in the order results arrive. The values a call produced are\n * registered under its id, which later masks refer to.\n *\n * @param toolResult - Result of a call reported with `onToolUse`.\n */\n onToolResult(toolResult: Recorder.ToolResult) {\n this.#assertNotFinished();\n\n const { id, content } = toolResult;\n const isError = Boolean(toolResult.isError);\n\n const call = this.#pendingCalls.get(id);\n if (!call) {\n logger.debug(`No recorded tool use for result '${id}', ignoring it`);\n return;\n }\n\n this.#pendingCalls.delete(id);\n\n logger.debug(`Recording '${call.tool}' tool result`);\n\n // Replay skips a failed call, so only its secrets are registered.\n this.#masker.registerOutput(id, content, Recording.toolKey(call), {\n secretsOnly: isError,\n });\n\n this.#steps.push({\n kind: \"tool-call\",\n call,\n result: { content: structuredClone(content), isError },\n });\n }\n\n /**\n * Records what the agent said, so that a replay of this recording reads the\n * way the recording did. Nothing is executed from it.\n *\n * Pushed as it arrives, while a tool call is pushed once its result\n * does. Narration therefore lands ahead of the call it introduced, which is\n * the order the agent produced them in.\n *\n * @param narration - Prose as the agent wrote it.\n */\n onNarration(narration: Recorder.Narration) {\n this.#assertNotFinished();\n\n // Empty prose is dropped rather than stored.\n if (!narration.text.trim()) return;\n\n this.#steps.push({\n kind: \"narration\",\n narration: narration.narration,\n text: narration.text,\n });\n }\n\n /**\n * Finishes the recording with the run's verdict. Calls still waiting for a\n * result are dropped, since replay has nothing to compare them against.\n *\n * @param verdict - Verdict the host agent reached.\n * @returns Finished recording.\n * @throws {RecordingLeakError} when a host variable or a secret survived\n * masking, so that the recording is never stored with it.\n */\n finish(verdict: Recording.Verdict): Recording.Type {\n this.#assertNotFinished();\n\n if (this.#pendingCalls.size)\n logger.warn(\n `Dropping ${this.#pendingCalls.size} tool calls with no result: ${[\n ...this.#pendingCalls.values(),\n ]\n .map(({ tool }) => tool)\n .join(\", \")}`,\n );\n this.#pendingCalls.clear();\n\n const recording: Recording.Type = {\n id: Recording.textToId(this.#text),\n version: this.#version,\n text: this.#text,\n agent: this.#agent,\n alumnium: { version: ALUMNIUM_CORE_VERSION },\n hostVars: [],\n steps: this.#steps.map((step) => this.#maskStep(step)),\n verdict: {\n status: verdict.status,\n details: this.#masker.maskNarration(verdict.details),\n },\n };\n\n const masked = [\n recording.steps.map((step) =>\n step.kind === \"narration\"\n ? step.text\n : [step.call.input, step.result.content],\n ),\n recording.verdict.details,\n ];\n\n const leaks = this.#masker.findLeaks(masked);\n if (leaks.length) throw new RecordingLeakError(leaks);\n\n recording.hostVars = Masker.findHostVarNames(masked);\n this.#recording = recording;\n\n return recording;\n }\n\n /**\n * Masks a step's result or narration. Done on finish rather than as the step\n * arrives, once every secret of the run is registered: a result can arrive\n * ahead of the parallel call producing a secret it holds. Neither is ever\n * executed, so a later secret can be masked in them safely, unlike in an\n * input (see `onToolUse`).\n *\n * @param step - Step as recorded.\n * @returns Masked step.\n */\n #maskStep(step: Recording.Step): Recording.Step {\n if (step.kind === \"narration\") {\n return { ...step, text: this.#masker.maskNarration(step.text) };\n } else {\n return {\n ...step,\n result: {\n ...step.result,\n content: this.#masker.maskResult(step.result.content),\n },\n };\n }\n }\n\n #assertNotFinished() {\n if (this.#recording) throw new Error(\"Recording is already finished\");\n }\n}\n",
15
20
  "export class RecordingLeakError extends Error {\n readonly masks: string[];\n\n constructor(masks: string[]) {\n super(\n `Recording would store values in clear: ${masks.join(\", \")}. ` +\n \"Check the host variables and response paths configured for it.\",\n );\n this.name = \"RecordingLeakError\";\n this.masks = masks;\n }\n}\n"
16
21
  ],
17
- "mappings": ";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AACA;AAAA;AAwBO,MAAe,UAAU;AAAA,SACvB,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc;AAAA,SACpC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,SAOxC,UAAU,EAAE,MAAM;AAAA,IACvB,EAAE,OAAO;AAAA,IACT,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,EAC7C,CAAC;AAAA,SAEM,WAAW,EAAE,OAAO;AAAA,IAEzB,IAAI,EAAE,OAAO;AAAA,IAEb,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,IAE5B,MAAM,EAAE,OAAO;AAAA,IACf,OAAO,KAAK;AAAA,EACd,CAAC;AAAA,SAEM,aAAa,EAAE,OAAO;AAAA,IAC3B,SAAS,KAAK;AAAA,IACd,SAAS,EAAE,QAAQ;AAAA,EACrB,CAAC;AAAA,SAQM,WAAW,EAAE,OAAO;AAAA,IACzB,MAAM,EAAE,QAAQ,WAAW;AAAA,IAC3B,MAAM,KAAK;AAAA,IACX,QAAQ,KAAK;AAAA,EACf,CAAC;AAAA,SAOM,gBAAgB,EAAE,OAAO;AAAA,IAC9B,MAAM,EAAE,QAAQ,WAAW;AAAA,IAC3B,WAAW,EAAE,KAAK,CAAC,YAAY,WAAW,CAAC;AAAA,IAC3C,MAAM,EAAE,OAAO;AAAA,EACjB,CAAC;AAAA,SAEM,OAAO,EAAE,MAAM,CAAC,KAAK,UAAU,KAAK,aAAa,CAAC;AAAA,SAElD,UAAU,EAAE,OAAO;AAAA,IACxB,QAAQ,EAAE,KAAK,CAAC,WAAW,SAAS,CAAC;AAAA,IAKrC,SAAS,EAAE,OAAO;AAAA,EACpB,CAAC;AAAA,SAEM,kBAAkB;AAAA,SAElB,SAAS,EAAE,OAAO;AAAA,IACvB,IAAI,KAAK;AAAA,IAMT,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,IACnC,MAAM,EAAE,OAAO;AAAA,IAEf,OAAO,EAAE,OAAO;AAAA,IAChB,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAAA,IAK1C,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,IAC5B,OAAO,EAAE,MAAM,KAAK,IAAI;AAAA,IACxB,SAAS,KAAK;AAAA,EAChB,CAAC;AAAA,SAQM,QAAQ,CAAC,MAA4B;AAAA,IAC1C,OAAO,SAAS,KAAK,KAAK,CAAC;AAAA;AAAA,SAUtB,OAAO,CAAC,MAA2D;AAAA,IACxE,OAAO,KAAK,SAAS,GAAG,KAAK,UAAU,KAAK,SAAS,KAAK;AAAA;AAAA,SAUrD,oBAAoB,CAAC,WAAmC;AAAA,IAC7D,OAAO,UAAU,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,WAAW,EAAE;AAAA;AAEvE;;;ADpIA,MAAQ,WAAW,UAAU,IAAI,+CAA+C;AAAA;AAazE,MAAM,cAAmC;AAAA,EAC9C;AAAA,EACA,UAAU,IAAI;AAAA,EACd,WAA8B,EAAE,MAAM,GAAG,QAAQ,EAAE;AAAA,EAEnD,WAAW,CAAC,QAA6B,CAAC,GAAG;AAAA,IAC3C,KAAK,SAAS,IAAI,UAAU,MAAM,OAAO,cAAc,WAAW,CAAC;AAAA;AAAA,SAG9D,UAAU,GAAW;AAAA,IAC1B,OAAO,aACL,IAAI,uBAAuB,qBAAqB,aAAa,OAAO,GACpE,OACF;AAAA;AAAA,MAGE,GAAG,GAAW;AAAA,IAChB,OAAO,KAAK,OAAO;AAAA;AAAA,MAGjB,OAAO,GAAsB;AAAA,IAC/B,OAAO,KAAK,KAAK,SAAS;AAAA;AAAA,OAGtB,OAAM,CACV,MACA,UAAmC,CAAC,GACJ;AAAA,IAChC,QAAQ,UAAU,UAAU,oBAAoB;AAAA,IAChD,MAAM,KAAK,UAAU,SAAS,IAAI;AAAA,IAClC,MAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE,KAAM,MAAM,KAAK,MAAM,EAAE;AAAA,IAC1D,IAAI,OAAO,YAAY,SAAS;AAAA,MAC9B,OAAO,MAAM,mBAAmB,IAAI;AAAA,MACpC,KAAK,SAAS;AAAA,MACd,OAAO,gBAAgB,KAAK;AAAA,IAC9B;AAAA,IAEA,OAAO,MACL,QACI,oBAAoB,6BAA6B,MAAM,gBAAgB,YACvE,oBAAoB,IAC1B;AAAA,IACA,KAAK,SAAS;AAAA,IACd,OAAO;AAAA;AAAA,OAGH,OAAM,CAAC,WAA0C;AAAA,IACrD,KAAK,QAAQ,IAAI,UAAU,IAAI,gBAAgB,SAAS,CAAC;AAAA;AAAA,OAGrD,KAAI,GAAkB;AAAA,IAC1B,MAAM,SAAS,CAAC,GAAG,KAAK,OAAO;AAAA,IAC/B,IAAI,CAAC,OAAO;AAAA,MAAQ;AAAA,IAEpB,OAAO,MAAM,UAAU,OAAO,2BAA2B;AAAA,IAEzD,MAAM,QAAQ,IACZ,OAAO,IAAI,EAAE,IAAI,eACf,KAAK,OAAO,UAAU,SAAS,EAAE,GAAG,SAAS,CAC/C,CACF;AAAA,IAGA,YAAY,IAAI,cAAc;AAAA,MAC5B,IAAI,KAAK,QAAQ,IAAI,EAAE,MAAM;AAAA,QAAW,KAAK,QAAQ,OAAO,EAAE;AAAA;AAAA,OAG5D,QAAO,GAAkB;AAAA,IAC7B,KAAK,QAAQ,MAAM;AAAA;AAAA,OAUf,KAAK,CAAC,IAAkD;AAAA,IAC5D,IAAI;AAAA,MACF,OAAO,MAAM,KAAK,OAAO,SAAS,SAAS,EAAE,GAAG,UAAU,MAAM;AAAA,MAChE,OAAO,OAAO;AAAA,MACd,OAAO,KAAK,iCAAiC,eAAe,EAAE,MAAM,CAAC;AAAA,MACrE,OAAO;AAAA;AAAA;AAGb;AAEA,SAAS,QAAQ,CAAC,IAA0B;AAAA,EAC1C,OAAO,GAAG;AAAA;;AEhHZ,sBAAS;;;ACiBF,SAAS,cAAc,CAAC,WAAoB,SAAiB,OAAO,IAAI;AAAA,EAC7E,IAAI,MAAM,QAAQ,SAAS,GAAG;AAAA,IAC5B,UAAU,QAAQ,CAAC,MAAM,UAAU;AAAA,MACjC,MAAM,WAAW,GAAG,QAAQ;AAAA,MAE5B,IAAI,OAAO,SAAS;AAAA,QAClB,OAAO,eAAe,MAAM,SAAS,QAAQ;AAAA,MAE/C,MAAM,SAAS,QAAQ,MAAM,QAAQ;AAAA,MACrC,IAAI,WAAW;AAAA,QAAW,UAAU,SAAS;AAAA,KAC9C;AAAA,IACD;AAAA,EACF;AAAA,EAEA,IAAI,OAAO,cAAc,YAAY,cAAc;AAAA,IAAM;AAAA,EAEzD,YAAY,KAAK,UAAU,OAAO,QAAQ,SAAS,GAAG;AAAA,IACpD,MAAM,YAAY,OAAO,GAAG,QAAQ,QAAQ;AAAA,IAE5C,IAAI,OAAO,UAAU,UAAU;AAAA,MAC7B,eAAe,OAAO,SAAS,SAAS;AAAA,MACxC;AAAA,IACF;AAAA,IAEA,MAAM,SAAS,QAAQ,OAAO,SAAS;AAAA,IACvC,IAAI,WAAW;AAAA,MAAW,OAAO,OAAO,WAAW,GAAG,MAAM,OAAO,CAAC;AAAA,EACtE;AAAA;AAUK,SAAS,SAAS,CAAC,OAAgB,SAA2B;AAAA,EACnE,MAAM,QAAkB,CAAC;AAAA,EAEzB,eAAe,EAAE,MAAM,GAAG,CAAC,SAAS;AAAA,IAClC,MAAM,KAAK,GAAI,KAAK,MAAM,OAAO,KAAK,CAAC,CAAE;AAAA,IACzC;AAAA,GACD;AAAA,EAED,OAAO;AAAA;AAUF,SAAS,UAAU,CAAC,OAAiC;AAAA,EAC1D,MAAM,cAAc,UAAU,MAAM,UAAU,KAAK;AAAA,EACnD,IAAI,CAAC,YAAY;AAAA,IACf,MAAM,IAAI,UAAU,wCAAwC;AAAA,EAC9D,OAAO,gBAAgB,YAAY,IAAI;AAAA;AAGlC,SAAS,SAAS,CAAC,MAAuB;AAAA,EAC/C,IAAI;AAAA,IACF,OAAO,KAAK,MAAM,IAAI;AAAA,IACtB,MAAM;AAAA,IACN;AAAA;AAAA;AAcG,SAAS,kBAAkB,CAAC,MAA6B;AAAA,EAC9D,MAAM,SAAS,UAAU,IAAI;AAAA,EAC7B,IAAI,OAAO,WAAW,YAAY,WAAW;AAAA,IAAM,OAAO;AAAA,EAC1D,OAAO;AAAA;AAIF,SAAS,YAAY,CAAC,SAAsC;AAAA,EACjE,IAAI,OAAO,YAAY;AAAA,IAAU,OAAO,CAAC,OAAO;AAAA,EAChD,OAAO,QAAQ,QAAQ,CAAC,UACtB,MAAM,SAAS,UAAU,OAAO,MAAM,YAAY,WAC9C,CAAC,MAAM,OAAO,IACd,CAAC,CACP;AAAA;AAuBK,SAAS,UAAU,CAAC,MAAmB,SAA0B;AAAA,EACtE,IAAI,KAAK,IAAI,OAAO;AAAA,IAAG,OAAO;AAAA,EAE9B,MAAM,iBAAiB,QAAQ,YAAY,GAAG;AAAA,EAC9C,OACE,kBAAkB,KAAK,KAAK,IAAI,GAAG,QAAQ,MAAM,GAAG,cAAc,KAAK;AAAA;AAUpE,SAAS,WAAW,CAAC,SAAyB;AAAA,EACnD,OAAO,QAAQ,QAAQ,mBAAmB,GAAG;AAAA;AAG/C,IAAM,mBAAmB;AASlB,SAAS,eAAe,CAC7B,OACA,KACQ;AAAA,EACR,IAAI,SAAS;AAAA,EACb,IAAI,QAAQ;AAAA,EACZ,WAAW,SAAS,MAAM,SAAS,gBAAgB,GAAG;AAAA,IACpD,UAAU,IAAI,MAAM,MAAM,OAAO,MAAM,KAAK,CAAC,IAAI,MAAM;AAAA,IACvD,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAAA,EACjC;AAAA,EACA,OAAO,SAAS,IAAI,MAAM,MAAM,KAAK,CAAC;AAAA;AAIjC,SAAS,YAAY,CAAC,OAAuB;AAAA,EAClD,OAAO,MAAM,QAAQ,uBAAuB,MAAM;AAAA;;;ACpK7C,MAAe,gBAAgB;AAAA,SAE7B,cAAc;AAAA,EAErB;AAAA,EACA,eAAe,IAAI;AAAA,EACnB,eAAe,IAAI;AAAA,EAEnB,WAAW,CAAC,SAAiB;AAAA,IAC3B,KAAK,WAAW;AAAA;AAAA,MAGd,IAAI,GAAW;AAAA,IACjB,OAAO,KAAK,aAAa;AAAA;AAAA,EASjB,QAAQ,CAAC,MAAc,OAAe;AAAA,IAC9C,KAAK,aAAa,IAAI,MAAM,KAAK;AAAA,IACjC,IAAI,CAAC,KAAK,aAAa,IAAI,KAAK;AAAA,MAAG,KAAK,aAAa,IAAI,OAAO,IAAI;AAAA;AAAA,EAStE,IAAU,CAAC,OAAmB;AAAA,IAC5B,IAAI,CAAC,KAAK,aAAa;AAAA,MAAM,OAAO;AAAA,IAEpC,IAAI,OAAO,UAAU;AAAA,MAAU,OAAO,KAAK,WAAW,KAAK;AAAA,IAE3D,eAAe,OAAO,CAAC,SAAS;AAAA,MAC9B,MAAM,SAAS,KAAK,WAAW,IAAI;AAAA,MACnC,OAAO,WAAW,OAAO,YAAY;AAAA,KACtC;AAAA,IAED,OAAO;AAAA;AAAA,EAGT,UAAU,CAAC,OAAuB;AAAA,IAChC,IAAI,CAAC,KAAK,aAAa;AAAA,MAAM,OAAO;AAAA,IAEpC,MAAM,aAAa,IAAI;AAAA,IACvB,YAAY,QAAQ,SAAS,KAAK,cAAc;AAAA,MAG9C,MAAM,UAAU,KAAK,aAAa,MAAM;AAAA,MACxC,WAAW,IAAI,QAAQ,IAAI;AAAA,MAC3B,IAAI,YAAY,UAAU,CAAC,WAAW,IAAI,OAAO;AAAA,QAC/C,WAAW,IAAI,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA,IAChD;AAAA,IAGA,MAAM,UAAU,IAAI,OAClB,CAAC,GAAG,WAAW,KAAK,CAAC,EAClB,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,IAAI,YAAY,EAChB,KAAK,GAAG,GACX,GACF;AAAA,IAEA,OAAO,gBAAgB,OAAO,CAAC,SAC7B,KAAK,QAAQ,SAAS,CAAC,UAAU,WAAW,IAAI,KAAK,KAAK,KAAK,CACjE;AAAA;AAAA,EASF,MAAY,CAAC,OAAmB;AAAA,IAC9B,IAAI,CAAC,KAAK,aAAa;AAAA,MAAM,OAAO;AAAA,IAEpC,eAAe,OAAO,CAAC,SAAS;AAAA,MAC9B,MAAM,WAAW,KAAK,QAAQ,KAAK,UAAU,CAAC,SAAS;AAAA,QACrD,MAAM,UAAU,KAAK,SAAS,GAAG,gBAAgB,cAAc;AAAA,QAC/D,MAAM,QAAQ,KAAK,aAAa,IAC9B,UACI,GAAG,KAAK,MAAM,GAAG,CAAC,gBAAgB,YAAY,SAAS,CAAC,OACxD,IACN;AAAA,QACA,IAAI,UAAU;AAAA,UAAW,OAAO;AAAA,QAChC,OAAO,UAAU,KAAK,aAAa,KAAK,IAAI;AAAA,OAC7C;AAAA,MACD,OAAO,aAAa,OAAO,YAAY;AAAA,KACxC;AAAA,IAED,OAAO;AAAA;AAAA,EAST,SAAS,CAAC,OAA0B;AAAA,IAClC,MAAM,QAAQ,IAAI;AAAA,IAElB,eAAe,EAAE,MAAM,GAAG,CAAC,SAAS;AAAA,MAClC,gBAAgB,MAAM,CAAC,SAAS;AAAA,QAC9B,YAAY,QAAQ,SAAS,KAAK,cAAc;AAAA,UAC9C,IAAI,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,KAAK,aAAa,MAAM,CAAC;AAAA,YAClE,MAAM,IAAI,IAAI;AAAA,QAClB;AAAA,QACA,OAAO;AAAA,OACR;AAAA,MAED;AAAA,KACD;AAAA,IAED,OAAO,CAAC,GAAG,KAAK;AAAA;AAAA,EAGlB,YAAY,CAAC,OAAuB;AAAA,IAClC,OAAO,KAAK,UAAU,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA;AAAA,EAG1C,SAAS,CAAC,MAAsB;AAAA,IAC9B,OAAO,GAAG,KAAK,MAAM,GAAG,EAAE,IAAI,gBAAgB;AAAA;AAElD;;;AFzIA,MAAQ,oBAAW,WAAU,IAAI,sDAAsD;AAEvF,IAAM,mBAAmB;AACzB,IAAM,wBAAwB;AAC9B,IAAM,oBAAoB;AAC1B,IAAM,yBAAyB;AAAA;AAOxB,MAAM,sBAAsB,gBAAgB;AAAA,EAIjD,WAAW,CAAC,WAAmC,CAAC,GAAG;AAAA,IACjD,MAAM,iBAAiB;AAAA,IAEvB,YAAY,MAAM,UAAU,OAAO,QAAQ,QAAQ,GAAG;AAAA,MACpD,IAAI,CAAC,sBAAsB,KAAK,IAAI;AAAA,QAClC,MAAM,IAAI,WACR,uBAAuB,yCACzB;AAAA,MAIF,IAAI,CAAC,OAAO;AAAA,QACV,QAAO,MAAM,iBAAiB,+BAA+B;AAAA,QAC7D;AAAA,MACF;AAAA,MAEA,KAAK,SAAS,GAAG,mBAAmB,SAAS,KAAK;AAAA,IACpD;AAAA;AAAA,SASK,SAAS,CAAC,OAA0B;AAAA,IACzC,OAAO,UAAU,OAAO,iBAAiB;AAAA;AAAA,SAUpC,SAAS,CAAC,OAA0B;AAAA,IACzC,MAAM,QAAQ,IAAI;AAAA,IAElB,eAAe,EAAE,MAAM,GAAG,CAAC,SAAS;AAAA,MAClC,cAAc,SAAS,KAAK,SAAS,sBAAsB;AAAA,QACzD,IAAI;AAAA,UAAM,MAAM,IAAI,IAAI;AAAA,MAC1B;AAAA,KACD;AAAA,IAED,OAAO,CAAC,GAAG,KAAK;AAAA;AAEpB;;;AGpEA,sBAAS;;;ACAT,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AACtB,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAAA;AAWpB,MAAM,cAAc;AAAA,EACzB;AAAA,EAEA,WAAW,CAAC,QAAkC,CAAC,GAAG;AAAA,IAChD,KAAK,YAAY,OAAO,YACtB,OAAO,QAAQ,KAAK,EAAE,IAAI,EAAE,KAAK,cAAc;AAAA,MAC7C;AAAA,MACA,SAAS,IAAI,CAAC,SAAS,KAAK,MAAM,cAAc,CAAC;AAAA,IACnD,CAAC,CACH;AAAA;AAAA,EASF,EAAE,CAAC,SAAqD;AAAA,IACtD,IAAI,CAAC;AAAA,MAAS;AAAA,IAEd,MAAM,WAAW,KAAK,UAAU;AAAA,IAChC,IAAI;AAAA,MAAU,OAAO;AAAA,IAErB,MAAM,iBAAiB,QAAQ,YAAY,kBAAkB;AAAA,IAC7D,IAAI,iBAAiB;AAAA,MAAG;AAAA,IAExB,MAAM,SAAS,QAAQ,MAAM,GAAG,cAAc;AAAA,IAC9C,OAAO,KAAK,UAAU,GAAG,SAAS,qBAAqB;AAAA;AAAA,EAQzD,GAAG,CAAC,SAAsC;AAAA,IACxC,OAAO,CAAC,CAAC,KAAK,GAAG,OAAO,GAAG;AAAA;AAAA,EAW7B,MAAM,CAAC,SAA6B,MAAyB;AAAA,IAC3D,QAAQ,KAAK,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,CAAC,YACpC,KAAK,cAAc,SAAS,IAAI,CAClC;AAAA;AAAA,EAYF,aAAa,CACX,SACA,MACA,eAAe,GACf,YAAY,GACH;AAAA,IACT,IAAI,iBAAiB,QAAQ;AAAA,MAAQ,OAAO;AAAA,IAE5C,MAAM,UAAU,QAAQ;AAAA,IACxB,IAAI,YAAY,oBAAoB;AAAA,MAClC,SAAS,QAAQ,UAAW,SAAS,KAAK,QAAQ;AAAA,QAChD,IAAI,KAAK,cAAc,SAAS,MAAM,eAAe,GAAG,KAAK;AAAA,UAC3D,OAAO;AAAA,MACX,OAAO;AAAA,IACT;AAAA,IAEA,IAAI,cAAc,KAAK;AAAA,MAAQ,OAAO;AAAA,IACtC,IAAI,YAAY,iBAAiB,YAAY,KAAK;AAAA,MAAY,OAAO;AAAA,IAErE,OAAO,KAAK,cAAc,SAAS,MAAM,eAAe,GAAG,YAAY,CAAC;AAAA;AAE5E;;;AClGA,sBAAS;AAKT,MAAQ,oBAAW,WAAU,IAAI,qDAAqD;AAEtF,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAAA;AAWrB,MAAM,qBAAqB,gBAAgB;AAAA,SACzC,SAAS;AAAA,EAEhB;AAAA,EAEA,iBAAiB,IAAI;AAAA,SAQd,SAAS,CAAC,OAA0B;AAAA,IACzC,OAAO,UAAU,OAAO,mBAAmB;AAAA;AAAA,EAO7C,WAAW,CAAC,oBAA8C,CAAC,GAAG;AAAA,IAC5D,MAAM,mBAAmB;AAAA,IAEzB,KAAK,SAAS,IAAI,cAAc,iBAAiB;AAAA;AAAA,EAQnD,QAAQ,CAAC,SAAsC;AAAA,IAC7C,OAAO,KAAK,OAAO,IAAI,OAAO;AAAA;AAAA,EAUhC,QAAQ,CAAC,SAA6B,MAAyB;AAAA,IAC7D,OAAO,KAAK,OAAO,OAAO,SAAS,IAAI;AAAA;AAAA,EASzC,cAAc,CAAC,MAAc,OAAe;AAAA,IAC1C,KAAK,SAAS,MAAM,KAAK;AAAA;AAAA,EAS3B,UAAU,CAAC,QAAgB;AAAA,IACzB,QAAO,KACL,aAAa,+DACf;AAAA,IACA,KAAK,eAAe,IAAI,GAAG,qBAAqB,YAAY,MAAM,IAAI;AAAA;AAAA,EAM/D,SAAS,CAAC,OAA0B;AAAA,IAC3C,OAAO,CAAC,GAAG,KAAK,gBAAgB,GAAG,MAAM,UAAU,KAAK,CAAC;AAAA;AAE7D;;;AFnFA,MAAQ,oBAAW,WAAU,IAAI,uDAAuD;AAExF,IAAM,wBAAwB;AAC9B,IAAM,yBAAyB;AAG/B,IAAM,0BAA0B,OAAO;AAIvC,IAAM,qBAAqB,IAAI,OAC7B,OAAO,2CACP,GACF;AACA,IAAM,gCAAgC,IAAI,OACxC,IAAI,+DACJ,GACF;AAEA,IAAM,uBAAuB,IAAI,IAAI,CAAC,aAAa,CAAC;AAEpD,IAAM,yBAAyB,CAAC,WAAW;AAI3C,IAAM,kBAAiB;AAAA;AAkDhB,MAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,eAAe,IAAI;AAAA,EACnB,eAAe,IAAI;AAAA,EAEnB,WAAW,CAAC,QAA8B,CAAC,GAAG;AAAA,IAC5C,KAAK,iBAAiB,IAAI,IACxB,MAAM,iBAAiB,sBACzB;AAAA,IACA,KAAK,uBAAuB,IAAI,IAAI,MAAM,mBAAmB;AAAA,IAC7D,KAAK,iBAAiB,IAAI,cAAc,MAAM,qBAAqB;AAAA,IACnE,KAAK,WAAW,IAAI,aAAa,MAAM,iBAAiB;AAAA;AAAA,EAc1D,YAAY,CAAC,SAA0B;AAAA,IACrC,OAAO,CAAC,WAAW,KAAK,gBAAgB,OAAO;AAAA;AAAA,EASjD,OAAO,CAAC,SAAuC;AAAA,IAC7C,OAAO,WAAW,KAAK,sBAAsB,OAAO,IAChD,UACA;AAAA;AAAA,EAWN,SAAS,CAAC,SAAiB,OAAyC;AAAA,IAClE,IAAI,CAAC,KAAK,aAAa,OAAO;AAAA,MAAG,OAAO,KAAK,YAAY,KAAK;AAAA,IAC9D,OAAO,KAAK,KAAK,OAAO,KAAK,QAAQ,OAAO,CAAC;AAAA;AAAA,EAU/C,WAAW,CAAC,SAAiB,OAAyC;AAAA,IACpE,OAAO,KAAK,OAAO,OAAO,KAAK,QAAQ,OAAO,CAAC;AAAA;AAAA,SAgB1C,cAAc,CAAC,OAAkC;AAAA,IACtD,MAAM,QAAkB,CAAC;AAAA,IAEzB,eAAe,OAAO,CAAC,UAAU;AAAA,MAC/B,IAAI,uBAAuB,KAAK,KAAK;AAAA,QAAG,MAAM,KAAK,KAAK;AAAA,MACxD;AAAA,KACD;AAAA,IAED,OAAO,CAAC,GAAG,OAAO,GAAG,aAAa,UAAU,KAAK,CAAC;AAAA;AAAA,EAUpD,SAAS,CAAC,OAA0B;AAAA,IAClC,OAAO,KAAK,SAAS,UAAU,KAAK;AAAA;AAAA,EAatC,eAAe,CACb,QACA,SACA,SACA,UAA0C,CAAC,GACd;AAAA,IAG7B,IAAI,KAAK,kBAAkB,SAAS,OAAO;AAAA,MAAG,OAAO,CAAC;AAAA,IAEtD,MAAM,OAAO,aAAa,OAAO,EAAE,KAAK;AAAA,CAAI,EAAE,KAAK;AAAA,IACnD,IAAI,CAAC,MAAM;AAAA,MACT,QAAO,MAAM,aAAa,gCAAgC;AAAA,MAC1D,OAAO,CAAC;AAAA,IACV;AAAA,IAEA,MAAM,OAAO,mBAAmB,IAAI;AAAA,IACpC,IAAI,CAAC,MAAM;AAAA,MACT,QAAO,KACL,aAAa,2EACX,sDACJ;AAAA,MACA,QAAO,MAAM,cAAc,MAAM;AAAA,MACjC,IAAI,KAAK,SAAS,SAAS,OAAO;AAAA,QAAG,KAAK,SAAS,WAAW,MAAM;AAAA,MACpE,OAAO,CAAC;AAAA,IACV;AAAA,IAEA,OAAO,KAAK,eAAe,QAAQ,MAAM,SAAS,OAAO;AAAA;AAAA,EAkB3D,cAAc,CACZ,QACA,MACA,SACA,UAA0C,CAAC,GACd;AAAA,IAC7B,MAAM,aAA0C,CAAC;AAAA,IAEjD,KAAK,mBAAmB,IAAI,EAAE,QAAQ,GAAG,MAAM,YAAY;AAAA,MACzD,MAAM,SAAS,KAAK,SAAS,SAAS,SAAS,IAAI;AAAA,MACnD,IAAI,CAAC,WAAW,QAAQ,eAAe,CAAC,KAAK,WAAW,SAAS,IAAI;AAAA,QACnE;AAAA,MAEF,MAAM,SAAS,SAAS,aAAa,SAAS;AAAA,MAC9C,IAAI,OAAO,KAAK,UAAU,QAAQ,QAAQ,IAAI;AAAA,MAG9C,IAAI,KAAK,aAAa,IAAI,IAAI,GAAG;AAAA,QAC/B,IAAI,CAAC,QAAQ;AAAA,UACX,QAAO,KAAK,kBAAkB,sCAAsC;AAAA,UACpE;AAAA,QACF;AAAA,QACA,OAAO,KAAK,YAAY,IAAI;AAAA,MAC9B;AAAA,MAEA,KAAK,aAAa,IAAI,MAAM,KAAK;AAAA,MAEjC,IAAI,CAAC,KAAK,aAAa,IAAI,KAAK;AAAA,QAAG,KAAK,aAAa,IAAI,OAAO,IAAI;AAAA,MACpE,IAAI;AAAA,QAAQ,KAAK,SAAS,eAAe,MAAM,KAAK;AAAA,MAEpD,QAAO,MAAM,8BAA8B,MAAM;AAAA,MACjD,WAAW,KAAK,EAAE,MAAM,MAAM,CAAC;AAAA,KAChC;AAAA,IAED,OAAO;AAAA;AAAA,EAGT,UAAU,CAAC,SAA6B,MAAyB;AAAA,IAC/D,OACE,CAAC,KAAK,eAAe,GAAG,OAAO,KAC/B,KAAK,eAAe,OAAO,SAAS,IAAI;AAAA;AAAA,EAI5C,iBAAiB,CACf,SACA,SACS;AAAA,IACT,IAAI,KAAK,SAAS,SAAS,OAAO;AAAA,MAAG,OAAO;AAAA,IAC5C,OACE,CAAC,CAAC,QAAQ,eAAe,KAAK,eAAe,GAAG,OAAO,GAAG,WAAW;AAAA;AAAA,EAUzE,kBAAkB,CAAC,MAAqC;AAAA,IACtD,MAAM,SAAgC,CAAC;AAAA,IAEvC,MAAM,OAAO,CAAC,OAAgB,SAAmB;AAAA,MAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,QACxB,MAAM,QAAQ,CAAC,MAAM,UAAU,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,KAAK,CAAC,CAAC,CAAC;AAAA,QACnE;AAAA,MACF;AAAA,MAEA,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAAA,QAC/C,YAAY,KAAK,WAAW,OAAO,QAAQ,KAAK;AAAA,UAC9C,KAAK,QAAQ,CAAC,GAAG,MAAM,GAAG,CAAC;AAAA,QAC7B;AAAA,MACF;AAAA,MAKA,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU;AAAA,QAAU;AAAA,MAE5D,MAAM,YAAY,OAAO,KAAK;AAAA,MAC9B,IAAI,CAAC,UAAU,KAAK;AAAA,QAAG;AAAA,MAEvB,OAAO,KAAK,EAAE,MAAM,OAAO,UAAU,CAAC;AAAA;AAAA,IAGxC,KAAK,MAAM,CAAC,CAAC;AAAA,IAKb,OAAO,OAAO,KAAK,CAAC,GAAG,MAAM,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC;AAAA;AAAA,EAS3D,WAAW,CAAC,MAAsB;AAAA,IAChC,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE;AAAA,IAC7B,IAAI,QAAQ;AAAA,IACZ,OAAO,KAAK,aAAa,IAAI,GAAG,QAAQ,QAAQ;AAAA,MAAG;AAAA,IACnD,OAAO,GAAG,QAAQ;AAAA;AAAA,EAYpB,SAAS,CAAC,QAAgB,QAAgB,MAAwB;AAAA,IAChE,OAAO,GAAG,SAAS,CAAC,QAAQ,GAAG,IAAI,EAAE,IAAI,WAAW,EAAE,KAAK,eAAc;AAAA;AAAA,EAqB3E,IAAI,CAAC,OAAwB,OAA8C;AAAA,IACzE,IAAI,CAAC,KAAK,aAAa;AAAA,MAAM,OAAO;AAAA,IAEpC,eAAe,OAAO,CAAC,OAAO,SAAS;AAAA,MACrC,IAAI,UAAU,cAAc,qBAAqB,IAAI,IAAI;AAAA,QACvD;AAAA,MAEF,MAAM,OAAO,KAAK,aAAa,IAAI,KAAK;AAAA,MACxC,IAAI,MAAM;AAAA,QACR,QAAO,MACL,8BAA8B,WAAW,cAAc,OACzD;AAAA,QACA,OAAO;AAAA,MACT;AAAA,MAEA,IAAI,UAAU;AAAA,QAAY;AAAA,MAE1B,OAAO,KAAK,YAAY,OAAO,IAAI;AAAA,KACpC;AAAA,IAED,OAAO,KAAK,YAAY,KAAK;AAAA;AAAA,EAU/B,WAAiB,CAAC,OAAmB;AAAA,IACnC,OAAO,KAAK,SAAS,KAAK,KAAK;AAAA;AAAA,EAmBjC,WAAW,CAAC,OAAe,MAAkC;AAAA,IAC3D,MAAM,SAAS,KAAK,gBAAgB,OAAO,IAAI;AAAA,IAC/C,OAAO,WAAW,QAAQ,YAAY;AAAA;AAAA,EAWxC,eAAe,CAAC,OAAe,MAAsB;AAAA,IACnD,OAAO,MAAM,QACX,oBACA,CAAC,OAAO,SAAiB,QAAiB,WAAoB;AAAA,MAC5D,MAAM,YAAY,GAAG,UAAU,WAAW,YAAY,MAAM;AAAA,MAC5D,MAAM,QAAQ,UAAU,UAAU;AAAA,MAElC,IAAI,MAAM,OAAO,kBAAkB,KAAK;AAAA,QACtC,OAAO,GAAG,YAAY,KAAK,gBAAgB,OAAO,IAAI,IAAI;AAAA,MAE5D,MAAM,OAAO,KAAK,aAAa,IAAI,KAAK;AAAA,MACxC,IAAI,CAAC;AAAA,QAAM,OAAO;AAAA,MAElB,QAAO,MACL,qCAAqC,WAAW,cAAc,OAChE;AAAA,MAEA,OAAO,GAAG,YAAY,OAAO;AAAA,KAEjC;AAAA;AAAA,EAiBF,MAAM,CAAC,OAAwB,OAA8C;AAAA,IAC3E,IAAI,CAAC,KAAK,aAAa;AAAA,MAAM,OAAO;AAAA,IAEpC,eAAe,OAAO,CAAC,MAAM,SAAS;AAAA,MACpC,MAAM,QAAQ,KAAK,aAAa,IAAI,IAAI;AAAA,MACxC,IAAI,UAAU,WAAW;AAAA,QACvB,QAAO,MACL,gCAAgC,WAAW,aAAa,QAC1D;AAAA,QACA,OAAO;AAAA,MACT;AAAA,MAEA,IAAI,UAAU;AAAA,QAAY,OAAO,KAAK,cAAc,MAAM,IAAI;AAAA,KAC/D;AAAA,IAID,OAAO,KAAK,SAAS,OAAO,KAAK;AAAA;AAAA,EAenC,aAAa,CAAC,OAAe,MAAkC;AAAA,IAC7D,IAAI,WAAW;AAAA,IAEf,MAAM,SAAS,MAAM,QACnB,+BACA,CAAC,OAAO,WAAmB,SAAiB;AAAA,MAC1C,MAAM,QAAQ,KAAK,aAAa,IAAI,IAAI;AAAA,MACxC,IAAI,UAAU;AAAA,QAAW,OAAO;AAAA,MAEhC,IAAI,KAAK,eAAe,OAAO,SAAS,GAAG;AAAA,QACzC,QAAO,KACL,6BAA6B,8BAA8B,iBAAiB,kCAC9E;AAAA,QACA,OAAO;AAAA,MACT;AAAA,MAEA,QAAO,MACL,uCAAuC,WAAW,aAAa,QACjE;AAAA,MACA,WAAW;AAAA,MAEX,OAAO,GAAG,YAAY,QAAQ;AAAA,KAElC;AAAA,IAEA,OAAO,WAAW,SAAS;AAAA;AAAA,EAoB7B,cAAc,CAAC,OAAe,WAA4B;AAAA,IACxD,MAAM,QAAQ,UAAU,MAAM,EAAE;AAAA,IAChC,IAAI,MAAM,SAAS,KAAK;AAAA,MAAG,OAAO;AAAA,IAElC,MAAM,aAAa,UAAU,OAAO,UAAU,SAAS;AAAA,IACvD,OAAO,cAAc,SAAS,KAAK,KAAK;AAAA;AAI5C;AAEA,SAAS,YAAY,CAAC,GAAa,GAAqB;AAAA,EACtD,SAAS,QAAQ,EAAG,QAAQ,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS;AAAA,IACjE,OAAO,OAAO,IAAI,QAAQ,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM;AAAA,IACnD,IAAI,SAAS;AAAA,MAAO,OAAO,OAAO,QAAQ,KAAK;AAAA,EACjD;AAAA,EACA,OAAO,EAAE,SAAS,EAAE;AAAA;;;AG/ff,MAAM,OAAO;AAAA,EAClB;AAAA,EACA;AAAA,EAEA,WAAW,CAAC,QAAsB,CAAC,GAAG;AAAA,IACpC,QAAQ,aAAa,kBAAkB;AAAA,IACvC,KAAK,aAAa,IAAI,eAAe,aAAa;AAAA,IAClD,KAAK,YAAY,IAAI,cAAc,QAAQ;AAAA;AAAA,EAS7C,qBAAqB,CAAC,SAA0B;AAAA,IAC9C,OAAO,KAAK,WAAW,aAAa,OAAO;AAAA;AAAA,EAiB7C,cAAc,CACZ,QACA,SACA,SACA,SACA;AAAA,IACA,KAAK,WAAW,gBAAgB,QAAQ,SAAS,SAAS,OAAO;AAAA;AAAA,SAiB5D,mBAAmB,CAAC,OAAkC;AAAA,IAC3D,OAAO;AAAA,MACL,GAAG,IAAI,IAAI;AAAA,QACT,GAAG,eAAe,eAAe,KAAK;AAAA,QACtC,GAAG,cAAc,UAAU,KAAK;AAAA,MAClC,CAAC;AAAA,IACH;AAAA;AAAA,SAUK,gBAAgB,CAAC,OAA0B;AAAA,IAChD,OAAO,cAAc,UAAU,KAAK;AAAA;AAAA,EAUtC,SAAS,CAAC,OAA0B;AAAA,IAClC,OAAO;AAAA,MACL,GAAG,KAAK,UAAU,UAAU,KAAK;AAAA,MACjC,GAAG,KAAK,WAAW,UAAU,KAAK;AAAA,IACpC;AAAA;AAAA,EAcF,SAAS,CAAC,SAAiB,OAAiC;AAAA,IAC1D,OAAO,KAAK,UAAU,KACpB,KAAK,WAAW,UAAU,SAAS,WAAW,KAAK,CAAC,CACtD;AAAA;AAAA,EAYF,UAAU,CAAC,SAA+C;AAAA,IACxD,OAAO,KAAK,aAAa,gBAAgB,OAAO,CAAC;AAAA;AAAA,EAanD,aAAa,CAAC,MAAsB;AAAA,IAClC,OAAO,KAAK,aAAa,IAAI;AAAA;AAAA,EAQ/B,YAAkB,CAAC,OAAmB;AAAA,IACpC,OAAO,KAAK,UAAU,KAAK,KAAK,WAAW,YAAY,KAAK,CAAC;AAAA;AAAA,EAkB/D,WAAW,CAAC,SAAiB,OAAiC;AAAA,IAC5D,OAAO,KAAK,UAAU,OACpB,KAAK,WAAW,YAAY,SAAS,WAAW,KAAK,CAAC,CACxD;AAAA;AAIJ;;ACvOA,wCAA2B;;;ACApB,MAAM,2BAA2B,MAAM;AAAA,EACnC;AAAA,EAET,WAAW,CAAC,OAAiB;AAAA,IAC3B,MACE,0CAA0C,MAAM,KAAK,IAAI,QACvD,gEACJ;AAAA,IACA,KAAK,OAAO;AAAA,IACZ,KAAK,QAAQ;AAAA;AAEjB;;;ADNA,MAAQ,oBAAW,WAAU,IAAI,0CAA0C;AAE3E,IAAM,0BAA0B;AAChC,IAAM,sBAAsB;AAAA;AAsCrB,MAAM,SAAS;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAA2B,CAAC;AAAA,EAC5B,gBAAgB,IAAI;AAAA,EACpB;AAAA,EAEA,WAAW,CAAC,OAAuB;AAAA,IACjC,QAAQ,MAAM,OAAO,SAAS,mBAAmB,gBAAgB;AAAA,IACjE,KAAK,WAAW,WAAW,UAAU;AAAA,IACrC,KAAK,QAAQ;AAAA,IACb,KAAK,SAAS;AAAA,IACd,MAAM,SAAS,kBAAkB;AAAA,IACjC,KAAK,UAAU,IAAI,OAAO;AAAA,SACrB;AAAA,MACH,uBAAuB;AAAA,WAClB,KAAK,+BAA+B,MAAM;AAAA,WAC1C,YAAY;AAAA,MACjB;AAAA,MAGA,qBAAqB;AAAA,QACnB,GAAG;AAAA,QACH,GAAI,YAAY,uBAAuB,CAAC;AAAA,MAC1C;AAAA,IACF,CAAC;AAAA;AAAA,EAmBH,SAAS,CAAC,KAAuB;AAAA,IAC/B,KAAK,mBAAmB;AAAA,IAExB,QAAQ,IAAI,QAAQ,MAAM,UAAU;AAAA,IACpC,MAAM,UAAU,UAAU,QAAQ,EAAE,QAAQ,KAAK,CAAC;AAAA,IAElD,QAAO,MAAM,uBAAuB,mBAAmB,EAAE,GAAG,CAAC;AAAA,IAE7D,KAAK,cAAc,IAAI,IAAI;AAAA,MACzB;AAAA,SACI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,MAC3B;AAAA,MACA,OAAO,KAAK,QAAQ,UAAU,SAAS,KAAK;AAAA,IAC9C,CAAC;AAAA;AAAA,EAWH,YAAY,CAAC,YAAiC;AAAA,IAC5C,KAAK,mBAAmB;AAAA,IAExB,QAAQ,IAAI,YAAY;AAAA,IACxB,MAAM,UAAU,QAAQ,WAAW,OAAO;AAAA,IAE1C,MAAM,OAAO,KAAK,cAAc,IAAI,EAAE;AAAA,IACtC,IAAI,CAAC,MAAM;AAAA,MACT,QAAO,MAAM,oCAAoC,kBAAkB;AAAA,MACnE;AAAA,IACF;AAAA,IAEA,KAAK,cAAc,OAAO,EAAE;AAAA,IAE5B,QAAO,MAAM,cAAc,KAAK,mBAAmB;AAAA,IAGnD,KAAK,QAAQ,eAAe,IAAI,SAAS,UAAU,QAAQ,IAAI,GAAG;AAAA,MAChE,aAAa;AAAA,IACf,CAAC;AAAA,IAED,KAAK,OAAO,KAAK;AAAA,MACf,MAAM;AAAA,MACN;AAAA,MACA,QAAQ,EAAE,SAAS,gBAAgB,OAAO,GAAG,QAAQ;AAAA,IACvD,CAAC;AAAA;AAAA,EAaH,WAAW,CAAC,WAA+B;AAAA,IACzC,KAAK,mBAAmB;AAAA,IAGxB,IAAI,CAAC,UAAU,KAAK,KAAK;AAAA,MAAG;AAAA,IAE5B,KAAK,OAAO,KAAK;AAAA,MACf,MAAM;AAAA,MACN,WAAW,UAAU;AAAA,MACrB,MAAM,UAAU;AAAA,IAClB,CAAC;AAAA;AAAA,EAYH,MAAM,CAAC,SAA4C;AAAA,IACjD,KAAK,mBAAmB;AAAA,IAExB,IAAI,KAAK,cAAc;AAAA,MACrB,QAAO,KACL,YAAY,KAAK,cAAc,mCAAmC;AAAA,QAChE,GAAG,KAAK,cAAc,OAAO;AAAA,MAC/B,EACG,IAAI,GAAG,WAAW,IAAI,EACtB,KAAK,IAAI,GACd;AAAA,IACF,KAAK,cAAc,MAAM;AAAA,IAEzB,MAAM,YAA4B;AAAA,MAChC,IAAI,UAAU,SAAS,KAAK,KAAK;AAAA,MACjC,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,UAAU,EAAE,SAAS,iBAAiB;AAAA,MACtC,UAAU,CAAC;AAAA,MACX,OAAO,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,UAAU,IAAI,CAAC;AAAA,MACrD,SAAS;AAAA,QACP,QAAQ,QAAQ;AAAA,QAChB,SAAS,KAAK,QAAQ,cAAc,QAAQ,OAAO;AAAA,MACrD;AAAA,IACF;AAAA,IAEA,MAAM,SAAS;AAAA,MACb,UAAU,MAAM,IAAI,CAAC,SACnB,KAAK,SAAS,cACV,KAAK,OACL,CAAC,KAAK,KAAK,OAAO,KAAK,OAAO,OAAO,CAC3C;AAAA,MACA,UAAU,QAAQ;AAAA,IACpB;AAAA,IAEA,MAAM,QAAQ,KAAK,QAAQ,UAAU,MAAM;AAAA,IAC3C,IAAI,MAAM;AAAA,MAAQ,MAAM,IAAI,mBAAmB,KAAK;AAAA,IAEpD,UAAU,WAAW,OAAO,iBAAiB,MAAM;AAAA,IACnD,KAAK,aAAa;AAAA,IAElB,OAAO;AAAA;AAAA,EAaT,SAAS,CAAC,MAAsC;AAAA,IAC9C,IAAI,KAAK,SAAS,aAAa;AAAA,MAC7B,OAAO,KAAK,MAAM,MAAM,KAAK,QAAQ,cAAc,KAAK,IAAI,EAAE;AAAA,IAChE,EAAO;AAAA,MACL,OAAO;AAAA,WACF;AAAA,QACH,QAAQ;AAAA,aACH,KAAK;AAAA,UACR,SAAS,KAAK,QAAQ,WAAW,KAAK,OAAO,OAAO;AAAA,QACtD;AAAA,MACF;AAAA;AAAA;AAAA,EAcJ,8BAA8B,CAC5B,SAAiB,yBACS;AAAA,IAC1B,OAAO;AAAA,OACJ,GAAG,aAAa,CAAC;AAAA,OACjB,GAAG,UAAU,wBAAwB,CAAC,IAAI;AAAA,IAC7C;AAAA;AAAA,EAGF,kBAAkB,GAAG;AAAA,IACnB,IAAI,KAAK;AAAA,MAAY,MAAM,IAAI,MAAM,+BAA+B;AAAA;AAExE;",
18
- "debugId": "E1FEB499A312812964756E2164756E21",
22
+ "mappings": "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",
23
+ "debugId": "88B58893F83F47D164756E2164756E21",
19
24
  "names": []
20
25
  }
@@ -21,7 +21,11 @@ export declare namespace ToolCallMasker {
21
21
  value: string;
22
22
  }
23
23
  interface RegisterOptions {
24
- /** Registers only the leaves at `maskResponsePaths`, e.g. of a failed call. */
24
+ /**
25
+ * Registers only the leaves at `maskResponsePaths`, for a failed call. An
26
+ * output that is not JSON is then its error message rather than the
27
+ * payload the paths describe, so it is not reported as unread either.
28
+ */
25
29
  secretsOnly?: boolean | undefined;
26
30
  }
27
31
  interface Registered {
@@ -0,0 +1,16 @@
1
+ import type { Masker } from "./Masker.ts";
2
+ export declare const DEFAULT_ALUMNIUM_SERVER = "alumnium";
3
+ export declare const ALUMNIUM_START_TOOL = "start";
4
+ /**
5
+ * Masker props a run against Alumnium's MCP server needs on top of the host's.
6
+ * Recording and replay must mask with the same props, or a replay would not
7
+ * derive the masks its recording holds.
8
+ *
9
+ * @param props - Host's masker props.
10
+ * @param server - Name the host gave Alumnium's MCP server.
11
+ * @returns Masker props.
12
+ */
13
+ export declare function alumniumMaskerProps(
14
+ props: Masker.Props,
15
+ server?: string,
16
+ ): Masker.Props;
@@ -0,0 +1,16 @@
1
+ import type { Recording } from "../Recording.ts";
2
+ import type { ToolExecutor } from "./ToolExecutor.ts";
3
+ export declare namespace BashExecutor {
4
+ interface Props {
5
+ allow: (string | RegExp)[];
6
+ cwd?: string | undefined;
7
+ env?: Record<string, string> | undefined;
8
+ timeoutMs?: number | undefined;
9
+ }
10
+ }
11
+ export declare class BashExecutor implements ToolExecutor {
12
+ #private;
13
+ constructor(props: BashExecutor.Props);
14
+ handles(call: ToolExecutor.Call): boolean;
15
+ execute(call: ToolExecutor.Call): Promise<Recording.ToolResult>;
16
+ }
@@ -0,0 +1,37 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { Recording } from "../Recording.ts";
3
+ import type { ToolExecutor } from "./ToolExecutor.ts";
4
+ export declare namespace McpExecutor {
5
+ interface StdioServer {
6
+ type?: "stdio" | undefined;
7
+ command: string;
8
+ args?: string[] | undefined;
9
+ env?: Record<string, string> | undefined;
10
+ cwd?: string | undefined;
11
+ }
12
+ interface HttpServer {
13
+ type: "http";
14
+ url: string;
15
+ headers?: Record<string, string> | undefined;
16
+ }
17
+ interface ClientServer {
18
+ type: "client";
19
+ client: Client;
20
+ }
21
+ type Server = StdioServer | HttpServer | ClientServer;
22
+ interface Props {
23
+ servers: Record<string, Server>;
24
+ retries?: number | undefined;
25
+ retryDelayMs?: number | undefined;
26
+ }
27
+ }
28
+ export declare class McpExecutor implements ToolExecutor {
29
+ #private;
30
+ constructor(props: McpExecutor.Props);
31
+ handles(call: ToolExecutor.Call): boolean;
32
+ execute(
33
+ call: ToolExecutor.Call,
34
+ options?: ToolExecutor.ExecuteOptions,
35
+ ): Promise<Recording.ToolResult>;
36
+ close(): Promise<void>;
37
+ }
@@ -0,0 +1,53 @@
1
+ import { Masker } from "../masker/Masker.ts";
2
+ import { Recording } from "../Recording.ts";
3
+ import type { ToolExecutor } from "./ToolExecutor.ts";
4
+ export declare namespace Player {
5
+ interface Props extends Masker.Props {
6
+ executor: ToolExecutor;
7
+ /** Name of Alumnium MCP server, `alumnium` by default. */
8
+ alumniumServer?: string | undefined;
9
+ skipTools?: string[] | undefined;
10
+ checkConfirmations?: number | undefined;
11
+ observer?: Observer | undefined;
12
+ }
13
+ interface Observer {
14
+ onNarration?(step: Recording.NarrationStep): void;
15
+ onToolCall?(call: ToolExecutor.Call): void;
16
+ onToolResult?(call: ToolExecutor.Call, result: Recording.ToolResult): void;
17
+ onToolSkipped?(call: Recording.ToolCall, reason: string): void;
18
+ onCheck?(call: ToolExecutor.Call, outcome: CheckOutcome): void;
19
+ }
20
+ /**
21
+ * How a replayed `check` verdict compares with the recorded one:
22
+ *
23
+ * - `agreed` - the same verdict;
24
+ * - `improved` - recorded failing, passing now, which does not fail the
25
+ * replay: the agent may have gone on past it, or the app got fixed;
26
+ * - `unstable` - disagreed, then agreed when re-asked;
27
+ * - `disagreed` - disagreed however many times it was re-asked, which
28
+ * fails the replay.
29
+ */
30
+ type CheckOutcome = "agreed" | "improved" | "unstable" | "disagreed";
31
+ interface Replayed {
32
+ status: "replayed";
33
+ verdict: Recording.Verdict;
34
+ }
35
+ interface Diverged {
36
+ status: "diverged";
37
+ error: string;
38
+ /** Index of the step it diverged at in `Recording.steps`. */
39
+ stepIndex: number;
40
+ }
41
+ type Result = Replayed | Diverged;
42
+ }
43
+ export declare class Player {
44
+ #private;
45
+ constructor(props: Player.Props);
46
+ /**
47
+ * Replays a recording, e.g. one `TestCache.lookup` found.
48
+ *
49
+ * @param recording - Recording to replay.
50
+ * @returns Replay result.
51
+ */
52
+ play(recording: Recording.Type): Promise<Player.Result>;
53
+ }
@@ -0,0 +1,25 @@
1
+ import { Recording } from "../Recording.ts";
2
+ export declare namespace ToolExecutor {
3
+ type Call = Omit<Recording.ToolCall, "id">;
4
+ interface ExecuteOptions {
5
+ meta?: Record<string, unknown> | undefined;
6
+ }
7
+ }
8
+ export interface ToolExecutor {
9
+ handles(call: ToolExecutor.Call): boolean;
10
+ execute(
11
+ call: ToolExecutor.Call,
12
+ options?: ToolExecutor.ExecuteOptions,
13
+ ): Promise<Recording.ToolResult>;
14
+ close?(): Promise<void>;
15
+ }
16
+ /**
17
+ * Puts executors together into one, which executes a call with the first
18
+ * executor handling it.
19
+ *
20
+ * @param executors - Executors, in order of precedence.
21
+ * @returns Composed executor.
22
+ */
23
+ export declare function composeExecutors(
24
+ ...executors: ToolExecutor[]
25
+ ): ToolExecutor;