From ed457a9d4f8a7a45a0209729b0b75e93ec531c4b Mon Sep 17 00:00:00 2001 From: Larry Gordon Date: Sat, 25 Jul 2026 23:16:04 -0700 Subject: [PATCH] feat(extensions): let tool_call handlers revise tool input A `tool_call` handler (extension or hook) could previously only block a tool. It can now also return `input` to replace the arguments the tool executes with, so a handler can normalize or rewrite a built-in's input without reimplementing the tool. The returned object is the raw execution input passed to the tool's `execute` (the handler owns its correctness), not the normalized `event.input` view, which may carry derived gate-only fields (e.g. hashline `edit` `path`/`paths`) that are not real parameters. It is ignored when `block` is set, and not applied to `computer` tool calls whose event input is a synthetic actions view rather than the real params. When multiple handlers set `input`, the last one wins. Honored in both the extension and hook tool wrappers; documented in docs/extensions.md, docs/hooks.md, and docs/skills/authoring-hooks.md. --- docs/extensions.md | 2 +- docs/hooks.md | 4 +- docs/skills/authoring-hooks.md | 2 +- packages/coding-agent/CHANGELOG.md | 1 + .../src/extensibility/extensions/wrapper.ts | 14 ++- .../src/extensibility/hooks/tool-wrapper.ts | 14 ++- .../src/extensibility/shared-events.ts | 11 +- .../test/extensions-runner.test.ts | 106 ++++++++++++++++++ .../test/hook-tool-wrapper-input.test.ts | 95 ++++++++++++++++ 9 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 packages/coding-agent/test/hook-tool-wrapper-input.test.ts diff --git a/docs/extensions.md b/docs/extensions.md index 9fd9e3b32..ec3c5ae16 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -252,7 +252,7 @@ Cancelable pre-events: ### Tool lifecycle -- `tool_call` (pre-exec, may block) +- `tool_call` (pre-exec, may block, or revise the tool's execution `input`) - `tool_result` (post-exec, may patch content/details/isError) - `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (observability) - `tool_approval_requested` / `tool_approval_resolved` (observability; emitted by `wrapper.ts` only when a tool requires approval and an approval handler is registered) diff --git a/docs/hooks.md b/docs/hooks.md index 934b1e973..1d7d779c4 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -110,7 +110,7 @@ Hook events are strongly typed in `types.ts`. ### Tool events (pre/post model) -- `tool_call` (pre-execution) → can return `{ block?: boolean; reason?: string }` +- `tool_call` (pre-execution) → can return `{ block?: boolean; reason?: string; input?: Record }`. A non-blocking handler that returns `input` replaces the arguments the tool executes with (the raw execution input, not the normalized `event.input` view); ignored when `block` is true, and not applied to `computer` tool calls. - `tool_result` (post-execution) → can return `{ content?; details?; isError? }` This is the hook subsystem’s core pre/post interception model. @@ -197,7 +197,7 @@ Inside `HookRunner`, order is deterministic by registration sequence: Conflict behavior by event type: -- `tool_call`: last returned result wins unless a handler blocks; first block short-circuits +- `tool_call`: last returned result wins unless a handler blocks; first block short-circuits. A returned `input` (execution-argument override) follows the same last-wins rule; handlers do not observe each other's revisions - `tool_result`: last returned override wins (no short-circuit) - `context`: chained; each handler receives prior handler’s message output - `before_agent_start`: first returned message is kept; later messages ignored diff --git a/docs/skills/authoring-hooks.md b/docs/skills/authoring-hooks.md index 7054bb3aa..f735ff713 100644 --- a/docs/skills/authoring-hooks.md +++ b/docs/skills/authoring-hooks.md @@ -39,7 +39,7 @@ export default function myExtension(pi: ExtensionAPI): void { | Event | Fires | Can return | |---|---|---| -| `tool_call` | Before every tool execution | `{ block?: boolean; reason?: string }` | +| `tool_call` | Before every tool execution | `{ block?: boolean; reason?: string; input?: Record }` | | `tool_result` | After every tool execution | `{ content?; details?; isError?: boolean }` | ### Session lifecycle diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c4777f2be..f9f9771a5 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Added +- A `tool_call` handler (extension or hook) can now return `input` to revise the arguments a tool executes with, not just `block` it. The returned object is the raw execution input passed to the tool (ignored when `block` is set, and not applied to `computer` tool calls), enabling wrappers that normalize or rewrite a built-in's arguments without reimplementing the tool. - `omp usage` now surfaces auto-disabled credentials as red `✗` tombstone rows (identity, how long ago, the shortened upstream cause — e.g. `Refresh token expired` — and a re-login hint), including a provider section when no active credential remains. User-driven tombstones (`replaced by newer credential`, `deleted by user`) and API-key rows stay hidden. Requires a broker with `GET /v1/credentials/disabled`; older brokers degrade to no tombstone rows. - `omp usage` warns about Anthropic's ~30-day OAuth grant lifetime: accounts whose interactive login (`authorizedAt`) is within a week of the deadline get a yellow `⚠ re-login within