agent-control-plane-core 0.12.0 → 0.14.0

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.
@@ -36,15 +36,17 @@ export function parse(native: any, { version }?: {
36
36
  * on an enforceable deny. On a pre-v0.135 Codex (`this_call_vetoable` false) the
37
37
  * body still renders but exit stays 0 and `enforced` is false — advisory only.
38
38
  *
39
- * The two kinds use DIFFERENT native shapes, because Codex does: a pre-tool
40
- * decision rides `hookSpecificOutput.permissionDecision`, a post-tool one the
41
- * top-level `decision`/`reason` pair (`permissionDecision` is accepted but inert
42
- * on PostToolUse, so emitting it there would read as a block that never bites).
39
+ * Each native event gets its OWN shape, because Codex parses each strictly: a
40
+ * PreToolUse decision rides `hookSpecificOutput.permissionDecision`, a
41
+ * PermissionRequest one `hookSpecificOutput.decision.behavior`, and a
42
+ * post-tool one the top-level `decision`/`reason` pair.
43
43
  *
44
44
  * `soleGate` (default `false`) is the same dangerous, explicit opt-in as the
45
- * Claude adapter: when `true` AND the verdict is `allow`, the render emits the
46
- * real `permissionDecision: "allow"` instead of abstaining. It has no meaning
47
- * post-tool — there is no approval left to grant — so it is ignored there.
45
+ * Claude adapter, and acts only on PermissionRequest: there an `allow` renders
46
+ * `behavior: "allow"`, which grants the approval (except, from 0.166, when the
47
+ * hook is environment-sourced). PreToolUse has no allow that
48
+ * approves anything (Codex accepts `"allow"` only beside `updatedInput`, as a
49
+ * rewrite), and post-tool there is no approval left to grant.
48
50
  * @param {Verdict} verdict
49
51
  * @param {ToolCallEvent} event
50
52
  * @param {{ soleGate?: boolean }} [options]
@@ -103,11 +105,15 @@ export const INTEGRATION_MODE: "external_hook";
103
105
  /** @type {import("../control-plane.mjs").CoverageMap} */
104
106
  export const COVERAGE: import("../control-plane.mjs").CoverageMap;
105
107
  /**
106
- * Which {@link VERDICT_CONTENT_FIELDS} have no native channel on each kind, so
107
- * `render` drops them. Codex splits its two content channels by event:
108
- * `updatedInput` exists only on PreToolUse (the call has not run yet), and
109
- * `hookSpecificOutput.additionalContext` only on PostToolUse — Codex documents
110
- * no context field on PreToolUse, where `updatedInput` is the whole surface.
108
+ * Which {@link VERDICT_CONTENT_FIELDS} have no native channel on each kind's
109
+ * {@link NATIVE_EVENT_FOR} event, so `render` drops them. PreToolUse takes
110
+ * `updatedInput` (applied only beside `permissionDecision: "allow"`) and
111
+ * `additionalContext`; PostToolUse takes `additionalContext` alone ([X8], read
112
+ * at `rust-v0.166.0` and identical at `rust-v0.135.0`). `PermissionRequest`,
113
+ * which also parses as `pre_tool`, takes neither: its schema is
114
+ * `{hookEventName, decision: {behavior, message}}`, and a body carrying
115
+ * `updatedInput` is ignored (Codex's own approval flow decides), so
116
+ * {@link render} drops both there.
111
117
  *
112
118
  * NEITHER kind can rewrite tool output: PostToolUse "can't undo side effects
113
119
  * from a tool that already ran", and Codex rejects `updatedMCPToolOutput`, so
@@ -119,6 +125,43 @@ export const COVERAGE: import("../control-plane.mjs").CoverageMap;
119
125
  * @type {Record<string, ReadonlySet<string>|undefined>}
120
126
  */
121
127
  export const UNRENDERED_FIELDS: Record<string, ReadonlySet<string> | undefined>;
128
+ /**
129
+ * How Codex's `PreToolUse` hook files a model tool call ([X7], read from
130
+ * Codex's source at `rust-v0.166.0`). A function tool is filed under its flat
131
+ * name (namespace + name) with its parsed arguments by default; these
132
+ * handlers supply their own payload or none:
133
+ * - `exec_command` → `Bash` with `{command: <cmd>}`; arguments Codex cannot
134
+ * parse as `ExecCommandArgs` file nothing, which this table models only for
135
+ * a missing or null `cmd` or scalar arguments. Not modelled: other type
136
+ * errors, and a positional array, which serde reads (`["ls"]` is `cmd: "ls"`).
137
+ * - `apply_patch` (a freeform tool) → `apply_patch` with its raw patch text as
138
+ * `command`.
139
+ * - `spawn_agent` in the `multi_agent_v1` namespace → `spawn_agent`.
140
+ * - `write_stdin`, code mode's `wait`, and code mode's freeform `exec` → no hook.
141
+ * These rules assume the named tool owns the name; a client-defined tool
142
+ * called `wait` with code mode off takes the default payload.
143
+ * The default payload is function-only, so a freeform extension tool files
144
+ * nothing too — the mini-language's rule for an unlisted freeform call.
145
+ * NOT MODELLED: Codex files an MCP call as `mcp__<namespace>__<tool>`, with
146
+ * `_` trimmed from the namespace's end and the tool's start (`mcp.rs`
147
+ * `hook_tool_name`), which can differ from the flat name — a rule keyed by
148
+ * one name cannot express that.
149
+ * @type {import("../control-plane.mjs").PreToolFiling}
150
+ */
151
+ export const PRE_TOOL_FILING: import("../control-plane.mjs").PreToolFiling;
152
+ /**
153
+ * Codex's `PreToolUse` output parser ([X8], `rust-v0.166.0`) deserializes the
154
+ * body into `deny_unknown_fields` structs at both levels: these seven top-level
155
+ * keys (`PreToolUseCommandOutputWire` with its flattened universal fields), and
156
+ * these five in `hookSpecificOutput`. Any other key fails the whole body: the
157
+ * run is marked failed, no block is set, and the call RUNS. A known key can
158
+ * still void the body by its value or combination (`continue: false`, any
159
+ * `stopReason`, `suppressOutput: true`, `decision: "approve"`, `updatedInput`
160
+ * without `permissionDecision: "allow"`), which this declaration does not
161
+ * describe.
162
+ * @type {import("../control-plane.mjs").PreToolResponse}
163
+ */
164
+ export const PRE_TOOL_RESPONSE: import("../control-plane.mjs").PreToolResponse;
122
165
  /** Minimum Codex version whose hook can actually veto a tool call. */
123
166
  export const MIN_ENFORCING_VERSION: readonly number[];
124
167
  /**
@@ -126,6 +126,25 @@ export const GEMINI_TOOL_ALIASES: Readonly<Record<string, string>>;
126
126
  * here, because an un-escalated ask lets Gemini run the tool.
127
127
  */
128
128
  export const NATIVE_ASK_TIER: false;
129
+ /**
130
+ * No rename is modelled. Gemini CLI hands `BeforeTool` the invocation's tool
131
+ * name beside an `originalRequestName`, so an aliased call may be filed under
132
+ * a name the model did not use; that is unverified, so not published.
133
+ * @type {import("../control-plane.mjs").PreToolFiling}
134
+ */
135
+ export const PRE_TOOL_FILING: import("../control-plane.mjs").PreToolFiling;
136
+ /**
137
+ * Gemini CLI keeps `hookSpecificOutput` as a `Record<string, unknown>` and its
138
+ * `BeforeTool` path reads only `tool_input` from it, by `in` lookup, so any
139
+ * other key is ignored (`packages/core/src/hooks/types.ts`
140
+ * `BeforeToolHookOutput`; both firing paths, `core/coreToolHookTriggers.ts`
141
+ * and `scheduler/hook-utils.ts` `evaluateBeforeToolHook`, read at gemini-cli
142
+ * `62bab5b2`). The top level is the `HookOutput` interface's seven keys, read
143
+ * from an unchecked `JSON.parse` cast (`hooks/hookRunner.ts`), so an unknown
144
+ * top-level key is ignored too.
145
+ * @type {import("../control-plane.mjs").PreToolResponse}
146
+ */
147
+ export const PRE_TOOL_RESPONSE: import("../control-plane.mjs").PreToolResponse;
129
148
  /** @type {import("../control-plane.mjs").Adapter} */
130
149
  export const geminiAdapter: import("../control-plane.mjs").Adapter;
131
150
  export type ToolCallEvent = import("../control-plane.mjs").ToolCallEvent;
@@ -132,13 +132,18 @@ export function assertAliasedInputsCanonical(fixturesList: any[], assert: any):
132
132
  * decide where an `ask` must be escalated to a `deny`, so a wrong one
133
133
  * either lets a call through or denies every asked call on the hosts that
134
134
  * do ask. OBSERVE_ONLY renders are exempt, as in rule ⑧.
135
+ * 12. pre-tool key honesty: the adapter declares a well-formed
136
+ * `PRE_TOOL_FILING` and `PRE_TOOL_RESPONSE`, and every render on the host's
137
+ * native pre-tool event — fixture, rule ⑧ and rule ⑩ probes alike — puts
138
+ * in `hookSpecificOutput` only keys `PRE_TOOL_RESPONSE` says the host
139
+ * reads. At least one such render must be checked.
135
140
  *
136
141
  * `assert` is injected (node:assert/strict) so the harness stays test-framework
137
142
  * neutral; it throws on the first mismatch. Returns a summary the caller can
138
143
  * assert further on.
139
144
  *
140
145
  * @param {{ adapter: import("./control-plane.mjs").Adapter, fixtures: any, assert: any }} args
141
- * @returns {{ cases: number, renders: number, decisionsSeen: Set<string>, mutationSeen: boolean, contentFieldsSeen: Set<string>, enforcedDenySeen: boolean, vetoableDenySeen: boolean, unknownKindSeen: boolean, coverageClassesChecked: Set<string>, unenforceableDenyChecks: number, askTierChecks: number }}
146
+ * @returns {{ cases: number, renders: number, decisionsSeen: Set<string>, mutationSeen: boolean, contentFieldsSeen: Set<string>, enforcedDenySeen: boolean, vetoableDenySeen: boolean, unknownKindSeen: boolean, coverageClassesChecked: Set<string>, unenforceableDenyChecks: number, askTierChecks: number, preToolKeyChecks: number }}
142
147
  */
143
148
  export function runAdapterConformance({ adapter, fixtures, assert }: {
144
149
  adapter: import("./control-plane.mjs").Adapter;
@@ -156,4 +161,5 @@ export function runAdapterConformance({ adapter, fixtures, assert }: {
156
161
  coverageClassesChecked: Set<string>;
157
162
  unenforceableDenyChecks: number;
158
163
  askTierChecks: number;
164
+ preToolKeyChecks: number;
159
165
  };
@@ -124,6 +124,40 @@ export function classifyCallClass(tool: string | null, native?: Record<string, u
124
124
  * @returns {ReadonlySet<string>}
125
125
  */
126
126
  export function readonlySet(values: Iterable<string>): ReadonlySet<string>;
127
+ /**
128
+ * The model tool name a {@link PreToolFiling} is keyed by: `name`, prefixed
129
+ * (no separator) with `namespace` unless that is absent, empty, or Codex's
130
+ * default `functions` namespace (`flat_tool_name`, `is_default_namespace`;
131
+ * [X7] in `docs/hook-coverage-matrix.md`).
132
+ * @param {string | null | undefined} namespace
133
+ * @param {string} name
134
+ * @returns {string}
135
+ */
136
+ export function flatToolName(namespace: string | null | undefined, name: string): string;
137
+ /**
138
+ * Whether `rule` is a non-null {@link PreToolFiling} rule: a plain object with
139
+ * exactly a non-empty string `tool_name` and a well-formed `input`. The one
140
+ * grammar both {@link fileModelCall} and the conformance harness hold a rule to.
141
+ * @param {unknown} rule
142
+ * @returns {boolean}
143
+ */
144
+ export function isFilingRule(rule: unknown): boolean;
145
+ /**
146
+ * The call a host's pre-tool hook files for a model call of `kind` to `name`
147
+ * with arguments value `args`, per that host's {@link PreToolFiling}; `null`
148
+ * when the hook files none. The reference reading of the mini-language
149
+ * `agents.json` publishes as `pre_tool_filing`. Throws on a rule outside that
150
+ * grammar or a kind outside {@link MODEL_CALL_KINDS}.
151
+ * @param {PreToolFiling} filing
152
+ * @param {"function" | "freeform"} kind whether the model made a function call or a freeform (custom) call
153
+ * @param {string} name the model tool name, a {@link flatToolName}
154
+ * @param {unknown} args the call's arguments value
155
+ * @returns {{ tool_name: string, tool_input: unknown } | null}
156
+ */
157
+ export function fileModelCall(filing: PreToolFiling, kind: "function" | "freeform", name: string, args: unknown): {
158
+ tool_name: string;
159
+ tool_input: unknown;
160
+ } | null;
127
161
  /**
128
162
  * The translator for one agent's protocol. `parse` maps a native event to a
129
163
  * {@link ToolCallEvent} (never throwing on unmodelled input, stamping the
@@ -137,7 +171,9 @@ export function readonlySet(values: Iterable<string>): ReadonlySet<string>;
137
171
  * @property {(verdict: Verdict, event: ToolCallEvent, options?: { soleGate?: boolean }) => NativeResponse} render
138
172
  * @property {Record<string, string|undefined>} [NATIVE_EVENT_FOR] the native event name this host uses for each {@link EventKind}, for a conformance probe of a kind no fixture produced. Optional: a kind absent here (`unknown` always, plus any kind this transport does not carry) is probed with a marker name, which takes the adapter's unrecognized-event branch. Never read at runtime — `parse` stamps the real name on `meta.native_event`.
139
173
  * @property {boolean} NATIVE_ASK_TIER whether this host's transport carries a DISTINCT "ask the human" tier that the host honours. `true` ⇒ a rendered `ask` suspends the call in front of a person (Claude Code's `permissionDecision: "ask"`, Amp's exit 1). `false` ⇒ the host has no such tier, so `render` collapses `ask` onto another signal (Gemini CLI writes its advisory `decision: "deny"`) and an un-escalated ask lets the tool run. A consumer that must not let an ask through reads this to decide where to escalate `ask` to `deny`, instead of keeping its own hand-typed list of agent ids. It is about the HOST, not the verdict: `ask` never renders as `enforced` on any adapter, and the tier it names is the PRE-TOOL gate — once a tool has run there is nothing left to suspend, so every host renders a post-tool `ask` as its block. The conformance harness probes the real `render` against it — a `true` whose ask renders as the host's abstaining allow fails, and so does a `false` whose ask differs from its own advisory deny.
140
- * @property {Record<string, ReadonlySet<string>|undefined>} UNRENDERED_FIELDS per-event-kind set of {@link VERDICT_CONTENT_FIELDS} this host has no native channel for, so `render` drops them. EVERY {@link EventKind} carries a row, including one this adapter's `parse` cannot emit — an omission would otherwise read as "every content field reaches a channel here", the reverse of the truth on a transport that carries none. The value type still admits `undefined` so a consumer handles a lookup miss rather than indexing straight into `.has(...)`; the conformance harness refuses a missing row. SCOPE — a row describes the ALLOW path only. An enforceable deny may drop more: Gemini's exit-2 System Block returns no stdout at all, so a deny there carries no `additional_context` whatever the row says. Do not read this map to infer what a deny delivers. The conformance harness fails an adapter whose renders disagree with its declaration either way, so a stale entry cannot survive.
174
+ * @property {PreToolFiling} PRE_TOOL_FILING how this host's pre-tool hook files a model tool call, for a consumer matching the model's calls against the hook's. `{}` when no rename is modelled.
175
+ * @property {PreToolResponse} PRE_TOOL_RESPONSE the top-level and `hookSpecificOutput` keys this host reads on its pre-tool event, each list with whether an unread key there runs the call. The conformance harness holds every pre-tool render to both lists.
176
+ * @property {Record<string, ReadonlySet<string>|undefined>} UNRENDERED_FIELDS per-event-kind set of {@link VERDICT_CONTENT_FIELDS} this host has no native channel for, so `render` drops them. EVERY {@link EventKind} carries a row, including one this adapter's `parse` cannot emit — an omission would otherwise read as "every content field reaches a channel here", the reverse of the truth on a transport that carries none. The value type still admits `undefined` so a consumer handles a lookup miss rather than indexing straight into `.has(...)`; the conformance harness refuses a missing row. SCOPE — a row describes the ALLOW path on the kind's `NATIVE_EVENT_FOR` event only; another native event of that kind (Codex's `PermissionRequest`) may drop more. An enforceable deny may drop more: Gemini's exit-2 System Block returns no stdout at all, so a deny there carries no `additional_context` whatever the row says. Do not read this map to infer what a deny delivers. The conformance harness fails an adapter whose renders disagree with its declaration either way, so a stale entry cannot survive.
141
177
  */
142
178
  /**
143
179
  * Build a normalized {@link ToolCallEvent}, stamping the schema version. Pure —
@@ -354,10 +390,10 @@ export function renderEscalated(adapter: Adapter, verdict: Verdict, event: ToolC
354
390
  * {@link Adapter} interface an integrator implements. A new REQUIRED adapter
355
391
  * member breaks every third-party adapter while every event on the wire stays
356
392
  * byte-identical, so it is a package-semver break and leaves
357
- * {@link CONTROL_PLANE_SCHEMA} alone. `UNRENDERED_FIELDS` is one and
358
- * `NATIVE_ASK_TIER` is the second: adding either is a one-line change per
359
- * adapter, and the conformance harness names the member and the migration
360
- * rather than throwing from inside itself.
393
+ * {@link CONTROL_PLANE_SCHEMA} alone. `UNRENDERED_FIELDS`, `NATIVE_ASK_TIER`,
394
+ * `PRE_TOOL_FILING` and `PRE_TOOL_RESPONSE` are such members: adding one is a
395
+ * one-line change per adapter, and the conformance harness names the member
396
+ * and the migration rather than throwing from inside itself.
361
397
  */
362
398
  /** Wire identifier for this schema version; bump on a breaking shape change. */
363
399
  export const CONTROL_PLANE_SCHEMA: "control-plane/v1";
@@ -544,6 +580,14 @@ export const VERDICT_CONTENT_FIELDS: readonly string[];
544
580
  * ignores the key it was written into.
545
581
  */
546
582
  export const UNRENDERED_ON_UNKNOWN: ReadonlySet<string>;
583
+ /** The {@link PreToolFiling} input reference to a call's whole arguments value. */
584
+ export const WHOLE_ARGUMENTS: "$";
585
+ /** The prefix of a {@link PreToolFiling} input reference to one top-level argument. */
586
+ export const ARGUMENT_KEY_PREFIX: "$.";
587
+ /** What a {@link PreToolFiling} rule must be, for error messages. */
588
+ export const FILING_RULE_GRAMMAR: "{tool_name: <non-empty string>, input: \"$\" or an object of \"$\"/\"$.<key>\" references (<key>: non-empty, no \".\")}";
589
+ /** The kinds of model tool call a {@link PreToolFiling} distinguishes. */
590
+ export const MODEL_CALL_KINDS: ReadonlySet<string>;
547
591
  /**
548
592
  * PROBLEM CLASS — an event marked vetoable on a path where the host can never
549
593
  * veto it, so a deny reads downstream as a block that never happened.
@@ -590,6 +634,8 @@ export const DEFAULT_DENY_REASON: "blocked by monitor";
590
634
  * exit. Every host here discards (or never gets) the stdout body on exit 2.
591
635
  * - `reason_required`: the host drops a block whose reason is empty and runs
592
636
  * the tool, so every body field that carries the reason must be non-empty.
637
+ * Unlike the other two fields, this holds for ANY block body the host parses,
638
+ * enforced or advisory: Codex voids an empty-reason deny either way.
593
639
  *
594
640
  * {@link gatedResponse} derives each adapter's enforced render from its row,
595
641
  * and the conformance harness holds every enforced render to the same row.
@@ -600,6 +646,21 @@ export const BLOCK_TRANSPORT: Readonly<Record<string, Readonly<{
600
646
  reason_stream: "stderr";
601
647
  reason_required: boolean;
602
648
  }>>>;
649
+ /**
650
+ * What a host reads from an exit-0 answer to its pre-tool event (`PreToolUse`,
651
+ * `BeforeTool`), as two sorted key lists, each empty when the host reads no
652
+ * body: `hook_specific_output_keys` inside `hookSpecificOutput`, and
653
+ * `top_level_keys` at the body's top level. Each list has a fail-open bit,
654
+ * `unread_key_fails_open` and `unread_top_level_key_fails_open`: `true` ⇒ any
655
+ * other key there voids the whole body and the call RUNS; `false` ⇒ other keys
656
+ * are ignored; `null` ⇒ not established from the host's source.
657
+ */
658
+ export type PreToolResponse = Readonly<{
659
+ hook_specific_output_keys: readonly string[];
660
+ unread_key_fails_open: boolean | null;
661
+ top_level_keys: readonly string[];
662
+ unread_top_level_key_fails_open: boolean | null;
663
+ }>;
603
664
  /**
604
665
  * The translator for one agent's protocol. `parse` maps a native event to a
605
666
  * {@link ToolCallEvent} (never throwing on unmodelled input, stamping the
@@ -626,7 +687,15 @@ export type Adapter = {
626
687
  */
627
688
  NATIVE_ASK_TIER: boolean;
628
689
  /**
629
- * per-event-kind set of {@link VERDICT_CONTENT_FIELDS} this host has no native channel for, so `render` drops them. EVERY {@link EventKind} carries a row, including one this adapter's `parse` cannot emit — an omission would otherwise read as "every content field reaches a channel here", the reverse of the truth on a transport that carries none. The value type still admits `undefined` so a consumer handles a lookup miss rather than indexing straight into `.has(...)`; the conformance harness refuses a missing row. SCOPE — a row describes the ALLOW path only. An enforceable deny may drop more: Gemini's exit-2 System Block returns no stdout at all, so a deny there carries no `additional_context` whatever the row says. Do not read this map to infer what a deny delivers. The conformance harness fails an adapter whose renders disagree with its declaration either way, so a stale entry cannot survive.
690
+ * how this host's pre-tool hook files a model tool call, for a consumer matching the model's calls against the hook's. `{}` when no rename is modelled.
691
+ */
692
+ PRE_TOOL_FILING: PreToolFiling;
693
+ /**
694
+ * the top-level and `hookSpecificOutput` keys this host reads on its pre-tool event, each list with whether an unread key there runs the call. The conformance harness holds every pre-tool render to both lists.
695
+ */
696
+ PRE_TOOL_RESPONSE: PreToolResponse;
697
+ /**
698
+ * per-event-kind set of {@link VERDICT_CONTENT_FIELDS} this host has no native channel for, so `render` drops them. EVERY {@link EventKind} carries a row, including one this adapter's `parse` cannot emit — an omission would otherwise read as "every content field reaches a channel here", the reverse of the truth on a transport that carries none. The value type still admits `undefined` so a consumer handles a lookup miss rather than indexing straight into `.has(...)`; the conformance harness refuses a missing row. SCOPE — a row describes the ALLOW path on the kind's `NATIVE_EVENT_FOR` event only; another native event of that kind (Codex's `PermissionRequest`) may drop more. An enforceable deny may drop more: Gemini's exit-2 System Block returns no stdout at all, so a deny there carries no `additional_context` whatever the row says. Do not read this map to infer what a deny delivers. The conformance harness fails an adapter whose renders disagree with its declaration either way, so a stale entry cannot survive.
630
699
  */
631
700
  UNRENDERED_FIELDS: Record<string, ReadonlySet<string> | undefined>;
632
701
  };
@@ -739,3 +808,32 @@ export type Verdict = {
739
808
  */
740
809
  reason?: string | undefined;
741
810
  };
811
+ /**
812
+ * How a host's pre-tool hook files a model's tool call, keyed by MODEL tool
813
+ * name, the {@link flatToolName} of the call's `namespace` and `name`. Each
814
+ * value is one of:
815
+ * - `null`: no pre-tool hook fires for this call.
816
+ * - `{ tool_name, input }`: the hook fires with `tool_name`, and `tool_input`
817
+ * built from `input` — either `"$"` (the call's whole arguments value), or
818
+ * an object whose every value is `"$"` or `"$.<key>"`. `<key>` is one
819
+ * literal, non-empty top-level key with no `.` in it. The hook files NOTHING
820
+ * when a `"$.<key>"` does not resolve: the arguments are not an object, lack
821
+ * the key, or hold `null` there.
822
+ * A FUNCTION call the map does not list is filed as the model asked: same
823
+ * name, whole arguments. A FREEFORM (custom) call the map does not list files
824
+ * nothing. Of the shipped hosts only Codex makes freeform calls ([X7] in
825
+ * `docs/hook-coverage-matrix.md`).
826
+ * The map describes a call the host DISPATCHES. Codex files nothing for a call
827
+ * it rejects first: a name it has no tool for, a call kind the tool does not
828
+ * take (each listed rule assumes the tool's own kind), or arguments the
829
+ * handler refuses before its hook (`apply_patch` whose patch does not parse).
830
+ * On Codex the ARGUMENTS VALUE is a function call's `arguments` parsed as JSON
831
+ * (an empty or all-whitespace string reads as `{}`, unparseable text stays the
832
+ * raw string), or a freeform call's raw `input` string.
833
+ * Plain JSON by design: `agents.json` publishes it so a consumer with no JS
834
+ * runtime applies it the way {@link fileModelCall} does.
835
+ */
836
+ export type PreToolFiling = Readonly<Record<string, null | Readonly<{
837
+ tool_name: string;
838
+ input: string | Readonly<Record<string, string>>;
839
+ }>>>;