From 2b37bc0ffb45105bb66e727700ea1a5eb0807cd1 Mon Sep 17 00:00:00 2001 From: Cameron Smick Date: Mon, 15 Jun 2026 14:01:59 -0700 Subject: [PATCH] refactor(core): update signalGraphTool to accept an Injector Refactor the AI debugging signalGraphTool to accept an Injector instance (named `injector`) rather than an HTMLElement. Requiring an Injector directly simplifies the tool's execution validation and decouples it from DOM element discovery utilities, allowing callers (including tests and DevTools) to resolve and pass the appropriate Injector directly. --- packages/core/src/debug/ai/signal_graph.ts | 32 ++++++++----------- .../core/test/debug/ai/signal_graph_spec.ts | 27 ++++++++++------ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/packages/core/src/debug/ai/signal_graph.ts b/packages/core/src/debug/ai/signal_graph.ts index a033549c3289..0c2ea82c9f61 100644 --- a/packages/core/src/debug/ai/signal_graph.ts +++ b/packages/core/src/debug/ai/signal_graph.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Injector} from '../../di/injector'; import {NullInjector} from '../../di/null_injector'; -import {getInjector} from '../../render3/util/discovery_utils'; import {DebugSignalGraph, getSignalGraph} from '../../render3/util/signal_debug'; import {ToolDefinition} from './tool_definitions'; @@ -19,20 +19,19 @@ type AiSignalGraph = Omit & { /** * Tool that exposes Angular's signal dependency graph to AI agents. */ -export const signalGraphTool: ToolDefinition<{target: HTMLElement}, AiSignalGraph> = { +export const signalGraphTool: ToolDefinition<{injector: Injector}, AiSignalGraph> = { name: 'angular:signal_graph', // tslint:disable-next-line:no-toplevel-property-access description: ` -Exposes the Angular signal dependency graph for a given DOM element. +Exposes the Angular signal dependency graph for a given Injector. This tool extracts the reactive dependency graph (signals, computeds, and effects) that -are transitive dependencies of the effects of that element. It will include signals +are transitive dependencies of the effects of that injector. It will include signals authored in other components/services and depended upon by the target component, but will *not* include signals only used in descendant components effects. Params: -- \`target\`: The element to get the signal graph for. Must be the host element of an - Angular component. +- \`injector\`: The Injector to get the signal graph for. Returns: - \`nodes\`: An array of reactive nodes discovered in the context. Each node contains: @@ -53,22 +52,19 @@ Example: An edge with \`{consumer: 2, producer: 0}\` means that \`nodes[2]\` (e. inputSchema: { type: 'object', properties: { - target: { + injector: { type: 'object', - description: 'The element to get the signal graph for.', - 'x-mcp-type': 'HTMLElement', + description: 'The Injector to get the signal graph for.', + 'x-mcp-type': 'Injector', }, }, - required: ['target'], + required: ['injector'], }, - execute: async ({target}: {target: HTMLElement}) => { - if (!(target instanceof HTMLElement)) { - throw new Error('Invalid input: "target" must be an HTMLElement.'); - } - - const injector = getInjector(target); - if (injector instanceof NullInjector) { - throw new Error('Invalid input: "target" is not the host element of an Angular component.'); + execute: async ({injector}: {injector: Injector}) => { + if (!injector || injector instanceof NullInjector) { + throw new Error( + 'Invalid input: "injector" is undefined, null, or an instance of NullInjector', + ); } const graph = getSignalGraph(injector); diff --git a/packages/core/test/debug/ai/signal_graph_spec.ts b/packages/core/test/debug/ai/signal_graph_spec.ts index 4b01de3017a2..b36761d8e3e3 100644 --- a/packages/core/test/debug/ai/signal_graph_spec.ts +++ b/packages/core/test/debug/ai/signal_graph_spec.ts @@ -10,13 +10,15 @@ import {Component, computed, effect, signal} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {signalGraphTool} from '../../../src/debug/ai/signal_graph'; import {setupFrameworkInjectorProfiler} from '../../../src/render3/debug/framework_injector_profiler'; +import {getInjector} from '../../../src/render3/util/discovery_utils'; +import {Injector} from '../../../src/di/injector'; describe('signalGraphTool', () => { beforeEach(() => { setupFrameworkInjectorProfiler(); }); - it('should discover signal graph from targeted element', async () => { + it('should discover signal graph from targeted injector', async () => { @Component({ selector: 'signal-graph-test-root', template: '
Signals test: {{ double() }}
', @@ -40,8 +42,9 @@ describe('signalGraphTool', () => { await fixture.whenStable(); const rootElement = fixture.nativeElement; + const injector = getInjector(rootElement); - const result = await signalGraphTool.execute({target: rootElement}); + const result = await signalGraphTool.execute({injector}); expect(result.nodes).toEqual( jasmine.arrayWithExactContents([ @@ -66,15 +69,21 @@ describe('signalGraphTool', () => { ); }); - it('should throw an error if target is not an HTMLElement', async () => { - await expectAsync( - signalGraphTool.execute({target: {} as unknown as HTMLElement}), - ).toBeRejectedWithError(/must be an HTMLElement/); + it('should throw an error if injector is null', async () => { + await expectAsync(signalGraphTool.execute({injector: null!})).toBeRejectedWithError( + /undefined, null, or an instance of NullInjector/, + ); + }); + + it('should throw an error if injector is undefined', async () => { + await expectAsync(signalGraphTool.execute({injector: undefined!})).toBeRejectedWithError( + /undefined, null, or an instance of NullInjector/, + ); }); - it('should throw an error if target is not an Angular component', async () => { + it('should throw an error if injector is a NullInjector', async () => { await expectAsync( - signalGraphTool.execute({target: document.createElement('div')}), - ).toBeRejectedWithError(/not the host element of an Angular component/); + signalGraphTool.execute({injector: getInjector(document.createElement('div'))}), + ).toBeRejectedWithError(/undefined, null, or an instance of NullInjector/); }); });