Quellcode durchsuchen

fix(tools): add collapses() method and fix createExecution collapse logic

Chinesezjc vor 1 Monat
Ursprung
Commit
1e78513806
1 geänderte Dateien mit 23 neuen und 3 gelöschten Zeilen
  1. 23 3
      packages/core/tools/src/index.ts

+ 23 - 3
packages/core/tools/src/index.ts

@@ -1250,6 +1250,19 @@ export class ToolRegistry extends Service {
     }
   }
 
+  /**
+   * Whether the `code` mode collapse denies a model-direct call: only the
+   * reserved `run_code` transport may be named. Nested sub-dispatches (a
+   * `parent` token set) bypass the collapse. One home for the
+   * security-relevant predicate, shared by {@link resolveExecution} and
+   * {@link createExecution} so the two can never drift apart.
+   * @param name - the tool name as registered.
+   * @param nested - whether the call is a transport sub-dispatch, not a model-direct call.
+   */
+  private collapses(name: string, nested: boolean): boolean {
+    return !nested && this.defaultMode === 'code' && name !== RUN_CODE_NAME
+  }
+
   /**
    * Execute through pre-policy, guards, around-dispatch, post-policy,
    * definition-owned content finalization, and final notification. Tool and
@@ -1295,8 +1308,15 @@ export class ToolRegistry extends Service {
     const agent = exec.agent
     const parent = exec.parent
     const signal = exec.signal
-    const definition = this.get(name, agent)
-    const finalizeContent = definition?.finalizeContent?.bind(definition)
+    // Distinguish a mode-collapsed call (visible in the scope, denied only by
+    // the `code` collapse) from a genuinely unknown tool. A collapsed call is
+    // deterministically denied, so it terminates BEFORE the extensible policy
+    // pipeline: pre-execute listeners, approval `ask`, and guards must never
+    // observe — or worse, approve — a call that can only fail. An unknown tool
+    // keeps the historical dispatch-stage `UNKNOWN_TOOL` path so policy
+    // listeners still see every name that reaches the registry.
+    const visible = this.get(name, agent)
+    const collapsed = visible !== undefined && this.collapses(name, parent !== undefined)
     const concludingExecutions = this.concludingExecutions
     const base = {
       token,
@@ -1333,7 +1353,7 @@ export class ToolRegistry extends Service {
       }
       const execution: MutableToolRunContext = { ...base, arguments: deepFreeze(detached) }
       this.deferredContexts.set(execution, deferredContexts)
-      this.contentFinalizers.set(execution, finalizeContent)
+      this.contentFinalizers.set(execution, finalizerFor())
       this.cancellationStates.set(execution, {
         callerSignal: signal,
         bodyInvoked: false,