feat(tools): require cancellation signal on every invocation

This commit is contained in:
Tianyi Cui
2026-07-19 23:38:54 +08:00
parent a99750f341
commit e8b95c8754
77 changed files with 1129 additions and 446 deletions
+7 -6
View File
@@ -794,7 +794,7 @@ Emitted when any prompt provider changes. This registry notification is unfilter
A tool was registered or unregistered, or a scoped restriction changed (the available tool set changed — possibly for one scope only). An UNFILTERED registry-subject notification, deliberately not scope-filtered dispatch: a global change concerns every agent's next assembly, so a scoped listener subscribing here sees every change, not just its own scope's.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L122)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L123)
### tools/execute
@@ -811,7 +811,7 @@ A tool was registered or unregistered, or a scoped restriction changed (the avai
* @param exec - the allowed call about to dispatch (name, parsed arguments, caller agent, signal).
* @mode waterfall
*/
'tools/execute'(this: Scoped<ToolRegistry>, exec: ToolExecution, next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>
'tools/execute'(this: Scoped<ToolRegistry>, exec: ToolDispatchExecution, next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>
```
Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a normalized result; wrappers may change only `exec.signal`, while call identity remains immutable. The registry re-fuses the original caller signal before the body, so replacement cannot detach caller cancellation; wrappers must still restore their signal and reach quiescence. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
@@ -829,7 +829,8 @@ Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a nor
* Accept, replace, enrich, or block a normalized dispatch result. `next()`
* accepts it unchanged; thrown tools still reach this seam as errors. Async
* listeners must observe `exec.signal`; after they settle, caller
* cancellation replaces only a successful accepted outcome with `ABORTED`.
* cancellation replaces only a successful accepted outcome with the code
* selected by whether the tool body was invoked.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
* @param exec - the call that just ran (name, parsed arguments, caller agent).
* @param result - the dispatch outcome a listener may accept, replace, or block.
@@ -838,12 +839,12 @@ Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a nor
'tools/post-execute'(this: Scoped<ToolRegistry>, exec: ToolExecution, result: Readonly<ToolExecutionResult>, next: () => Promise<PostToolDecision>): Promise<PostToolDecision>
```
Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Async listeners must observe `exec.signal`; after they settle, caller cancellation replaces only a successful accepted outcome with `ABORTED`. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Async listeners must observe `exec.signal`; after they settle, caller cancellation replaces only a successful accepted outcome with the code selected by whether the tool body was invoked. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
- `exec` — the call that just ran (name, parsed arguments, caller agent).
- `result` — the dispatch outcome a listener may accept, replace, or block.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L104)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L105)
### tools/pre-execute
@@ -888,7 +889,7 @@ Observe the frozen, lossless-JSON final outcome. Listener failures are contained
- `exec` — the execution object that traversed the pipeline.
- `result` — a deep-frozen snapshot of the final returned result.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L112)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L113)
## workflow/*
+12 -12
View File
@@ -6,7 +6,7 @@
Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L467)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L493)
### ctx.tools.register(definition)
@@ -26,7 +26,7 @@ Register globally or in the calling agent scope. Scoped tools shadow globals; du
**Returns** the exact disposer that unregisters the tool.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L569)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L595)
### ctx.tools.restrict(filter)
@@ -47,7 +47,7 @@ Restrict global tools for the calling agent scope. Empty filters, unknown names,
**Returns** the exact disposer that lifts this restriction.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L609)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L635)
### ctx.tools.guard(guard)
@@ -71,7 +71,7 @@ Register a monotonic guard after the extensible `tools/pre-execute` waterfall. A
**Returns** the exact disposer that unregisters the guard.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L660)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L686)
### ctx.tools.get(name, scope?)
@@ -95,7 +95,7 @@ Look up a tool as one scope sees it (scoped shadows global; a restricted-away gl
**Returns** the definition the scope resolves, or undefined when none is visible.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L762)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L788)
### ctx.tools.schemas(scope?)
@@ -115,7 +115,7 @@ Project visible definitions onto the allowlisted model-facing schema fields, exc
**Returns** one deep-cloned schema per visible tool.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L772)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L798)
### ctx.tools.executionMode(exec)
@@ -136,7 +136,7 @@ Classify a pending call through the caller's visible tool definition. Only an ex
**Returns** the fail-closed scheduling mode.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L793)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L819)
### ctx.tools.execute(exec)
@@ -147,9 +147,9 @@ Classify a pending call through the caller's visible tool definition. Only an ex
* results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is
* the same lossless, frozen snapshot final observers receive. Cancellation
* arriving after entry and before final result materialization skips a
* not-yet-started body or replaces a successful pipeline outcome with
* `ABORTED`; already-started work is still drained and may retain a
* tool-owned structured error.
* not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a
* successful started outcome with `ABORTED`; already-started work is still
* drained and may retain a tool-owned structured error.
* @param exec - the typed same-process call input. The registry assigns its
* correlation token before policy begins.
* @returns the materialized final result.
@@ -157,10 +157,10 @@ Classify a pending call through the caller's visible tool definition. Only an ex
async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>
```
Execute through pre-policy, guards, around-dispatch, post-policy, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body or replaces a successful pipeline outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.
Execute through pre-policy, guards, around-dispatch, post-policy, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a successful started outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.
- `exec` — the typed same-process call input. The registry assigns its correlation token before policy begins.
**Returns** the materialized final result.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L817)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/core/tools/src/index.ts#L843)