@adcp/sdk 13.0.4 → 13.1.1
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.
- package/bin/adcp-grade.js +4 -3
- package/bin/adcp.js +18 -0
- package/dist/lib/conformance/invariants/uniformError.d.ts.map +1 -1
- package/dist/lib/conformance/invariants/uniformError.js +21 -0
- package/dist/lib/conformance/invariants/uniformError.js.map +1 -1
- package/dist/lib/conformance/invariants/uniformError.mjs +22 -1
- package/dist/lib/conformance/invariants/uniformError.mjs.map +1 -1
- package/dist/lib/core/AgentClient.d.mts +1 -1
- package/dist/lib/core/AgentClient.d.ts +1 -1
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/AgentClient.js +1 -1
- package/dist/lib/core/AgentClient.js.map +1 -1
- package/dist/lib/core/AgentClient.mjs +1 -1
- package/dist/lib/core/AgentClient.mjs.map +1 -1
- package/dist/lib/core/SingleAgentClient.d.mts +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.js +88 -48
- package/dist/lib/core/SingleAgentClient.js.map +1 -1
- package/dist/lib/core/SingleAgentClient.mjs +88 -48
- package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
- package/dist/lib/core/TaskExecutor.d.ts.map +1 -1
- package/dist/lib/core/TaskExecutor.js +8 -4
- package/dist/lib/core/TaskExecutor.js.map +1 -1
- package/dist/lib/core/TaskExecutor.mjs +8 -4
- package/dist/lib/core/TaskExecutor.mjs.map +1 -1
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/net/agent-transport-fetch.d.mts +15 -0
- package/dist/lib/net/agent-transport-fetch.d.ts +15 -0
- package/dist/lib/net/agent-transport-fetch.d.ts.map +1 -1
- package/dist/lib/net/agent-transport-fetch.js +37 -2
- package/dist/lib/net/agent-transport-fetch.js.map +1 -1
- package/dist/lib/net/agent-transport-fetch.mjs +37 -2
- package/dist/lib/net/agent-transport-fetch.mjs.map +1 -1
- package/dist/lib/net/credential-headers.d.mts +1 -0
- package/dist/lib/net/credential-headers.d.ts +2 -0
- package/dist/lib/net/credential-headers.d.ts.map +1 -0
- package/dist/lib/net/credential-headers.js +34 -0
- package/dist/lib/net/credential-headers.js.map +1 -0
- package/dist/lib/net/credential-headers.mjs +10 -0
- package/dist/lib/net/credential-headers.mjs.map +1 -0
- package/dist/lib/protocols/a2a-native-v1.d.mts +18 -0
- package/dist/lib/protocols/a2a-native-v1.d.ts +19 -0
- package/dist/lib/protocols/a2a-native-v1.d.ts.map +1 -0
- package/dist/lib/protocols/a2a-native-v1.js +183 -0
- package/dist/lib/protocols/a2a-native-v1.js.map +1 -0
- package/dist/lib/protocols/a2a-native-v1.mjs +167 -0
- package/dist/lib/protocols/a2a-native-v1.mjs.map +1 -0
- package/dist/lib/protocols/a2a-push-notification.d.mts +13 -0
- package/dist/lib/protocols/a2a-push-notification.d.ts +14 -0
- package/dist/lib/protocols/a2a-push-notification.d.ts.map +1 -0
- package/dist/lib/protocols/a2a-push-notification.js +42 -0
- package/dist/lib/protocols/a2a-push-notification.js.map +1 -0
- package/dist/lib/protocols/a2a-push-notification.mjs +23 -0
- package/dist/lib/protocols/a2a-push-notification.mjs.map +1 -0
- package/dist/lib/protocols/a2a.d.mts +15 -5
- package/dist/lib/protocols/a2a.d.ts +15 -5
- package/dist/lib/protocols/a2a.d.ts.map +1 -1
- package/dist/lib/protocols/a2a.js +102 -13
- package/dist/lib/protocols/a2a.js.map +1 -1
- package/dist/lib/protocols/a2a.mjs +90 -12
- package/dist/lib/protocols/a2a.mjs.map +1 -1
- package/dist/lib/protocols/index.d.mts +3 -0
- package/dist/lib/protocols/index.d.ts +3 -0
- package/dist/lib/protocols/index.d.ts.map +1 -1
- package/dist/lib/protocols/index.js +18 -6
- package/dist/lib/protocols/index.js.map +1 -1
- package/dist/lib/protocols/index.mjs +18 -6
- package/dist/lib/protocols/index.mjs.map +1 -1
- package/dist/lib/protocols/mcp-modern.d.ts.map +1 -1
- package/dist/lib/protocols/mcp-modern.js +3 -1
- package/dist/lib/protocols/mcp-modern.js.map +1 -1
- package/dist/lib/protocols/mcp-modern.mjs +3 -1
- package/dist/lib/protocols/mcp-modern.mjs.map +1 -1
- package/dist/lib/protocols/mcp-tasks.d.ts.map +1 -1
- package/dist/lib/protocols/mcp-tasks.js +2 -1
- package/dist/lib/protocols/mcp-tasks.js.map +1 -1
- package/dist/lib/protocols/mcp-tasks.mjs +2 -1
- package/dist/lib/protocols/mcp-tasks.mjs.map +1 -1
- package/dist/lib/protocols/mcp.d.mts +8 -5
- package/dist/lib/protocols/mcp.d.ts +8 -5
- package/dist/lib/protocols/mcp.d.ts.map +1 -1
- package/dist/lib/protocols/mcp.js +12 -4
- package/dist/lib/protocols/mcp.js.map +1 -1
- package/dist/lib/protocols/mcp.mjs +12 -4
- package/dist/lib/protocols/mcp.mjs.map +1 -1
- package/dist/lib/protocols/rawResponseCapture.d.mts +9 -1
- package/dist/lib/protocols/rawResponseCapture.d.ts +9 -1
- package/dist/lib/protocols/rawResponseCapture.d.ts.map +1 -1
- package/dist/lib/protocols/rawResponseCapture.js +153 -10
- package/dist/lib/protocols/rawResponseCapture.js.map +1 -1
- package/dist/lib/protocols/rawResponseCapture.mjs +153 -10
- package/dist/lib/protocols/rawResponseCapture.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/agent-fetch.d.mts +3 -1
- package/dist/lib/signing/agent-fetch.d.ts +3 -1
- package/dist/lib/signing/agent-fetch.d.ts.map +1 -1
- package/dist/lib/signing/agent-fetch.js +4 -2
- package/dist/lib/signing/agent-fetch.js.map +1 -1
- package/dist/lib/signing/agent-fetch.mjs +4 -2
- package/dist/lib/signing/agent-fetch.mjs.map +1 -1
- package/dist/lib/signing/protocol-methods.d.mts +24 -0
- package/dist/lib/signing/protocol-methods.d.ts +25 -0
- package/dist/lib/signing/protocol-methods.d.ts.map +1 -0
- package/dist/lib/signing/protocol-methods.js +51 -0
- package/dist/lib/signing/protocol-methods.js.map +1 -0
- package/dist/lib/signing/protocol-methods.mjs +25 -0
- package/dist/lib/signing/protocol-methods.mjs.map +1 -0
- package/dist/lib/signing/types.d.mts +2 -0
- package/dist/lib/signing/types.d.ts +2 -0
- package/dist/lib/signing/types.d.ts.map +1 -1
- package/dist/lib/signing/types.js.map +1 -1
- package/dist/lib/signing/types.mjs.map +1 -1
- package/dist/lib/signing/verifier.d.ts.map +1 -1
- package/dist/lib/signing/verifier.js +6 -2
- package/dist/lib/signing/verifier.js.map +1 -1
- package/dist/lib/signing/verifier.mjs +6 -2
- package/dist/lib/signing/verifier.mjs.map +1 -1
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +4 -1
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/client.mjs +4 -1
- package/dist/lib/testing/client.mjs.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +3 -0
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/comply.mjs +3 -0
- package/dist/lib/testing/compliance/comply.mjs.map +1 -1
- package/dist/lib/testing/storyboard/agent-routing.d.mts +3 -1
- package/dist/lib/testing/storyboard/agent-routing.d.ts +3 -1
- package/dist/lib/testing/storyboard/agent-routing.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/agent-routing.js +5 -2
- package/dist/lib/testing/storyboard/agent-routing.js.map +1 -1
- package/dist/lib/testing/storyboard/agent-routing.mjs +4 -2
- package/dist/lib/testing/storyboard/agent-routing.mjs.map +1 -1
- package/dist/lib/testing/storyboard/compliance.d.mts +1 -1
- package/dist/lib/testing/storyboard/compliance.d.ts +1 -1
- package/dist/lib/testing/storyboard/compliance.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/compliance.js +4 -1
- package/dist/lib/testing/storyboard/compliance.js.map +1 -1
- package/dist/lib/testing/storyboard/compliance.mjs +4 -1
- package/dist/lib/testing/storyboard/compliance.mjs.map +1 -1
- package/dist/lib/testing/storyboard/native-a2a-compliance.d.mts +7 -0
- package/dist/lib/testing/storyboard/native-a2a-compliance.d.ts +8 -0
- package/dist/lib/testing/storyboard/native-a2a-compliance.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/native-a2a-compliance.js +45 -0
- package/dist/lib/testing/storyboard/native-a2a-compliance.js.map +1 -0
- package/dist/lib/testing/storyboard/native-a2a-compliance.mjs +21 -0
- package/dist/lib/testing/storyboard/native-a2a-compliance.mjs.map +1 -0
- package/dist/lib/testing/storyboard/probes.d.mts +2 -0
- package/dist/lib/testing/storyboard/probes.d.ts +2 -0
- package/dist/lib/testing/storyboard/probes.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/probes.js +3 -2
- package/dist/lib/testing/storyboard/probes.js.map +1 -1
- package/dist/lib/testing/storyboard/probes.mjs +3 -2
- package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +31 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +32 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +279 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js.map +1 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +244 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs.map +1 -0
- package/dist/lib/testing/storyboard/request-signing/builder.d.mts +4 -1
- package/dist/lib/testing/storyboard/request-signing/builder.d.ts +4 -1
- package/dist/lib/testing/storyboard/request-signing/builder.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +19 -0
- package/dist/lib/testing/storyboard/request-signing/builder.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +19 -0
- package/dist/lib/testing/storyboard/request-signing/builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/grader.d.mts +5 -6
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts +5 -6
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +82 -13
- package/dist/lib/testing/storyboard/request-signing/grader.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +86 -13
- package/dist/lib/testing/storyboard/request-signing/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/index.d.mts +1 -0
- package/dist/lib/testing/storyboard/request-signing/index.d.ts +1 -0
- package/dist/lib/testing/storyboard/request-signing/index.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/index.js +7 -0
- package/dist/lib/testing/storyboard/request-signing/index.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/index.mjs +8 -0
- package/dist/lib/testing/storyboard/request-signing/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.mts +2 -2
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.ts +2 -2
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.js +6 -5
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +6 -5
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/types.d.mts +1 -0
- package/dist/lib/testing/storyboard/request-signing/types.d.ts +1 -0
- package/dist/lib/testing/storyboard/request-signing/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/types.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +1 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +1 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.mts +28 -1
- package/dist/lib/testing/storyboard/runner.d.ts +28 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +251 -55
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +255 -60
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/types.d.mts +4 -2
- package/dist/lib/testing/storyboard/types.d.ts +4 -2
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js +1 -0
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs +1 -0
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.js +97 -22
- package/dist/lib/testing/storyboard/validations.js.map +1 -1
- package/dist/lib/testing/storyboard/validations.mjs +98 -22
- package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/version.mjs.map +1 -1
- package/docs/guides/BUILD-AN-AGENT.md +1 -1
- package/docs/llms.txt +5 -3
- package/package.json +2 -1
|
@@ -43,7 +43,9 @@
|
|
|
43
43
|
import type { TestClient } from '../client.mjs';
|
|
44
44
|
import type { AgentProfile } from '../types.mjs';
|
|
45
45
|
import { type AdcpProtocol } from '../../utils/capabilities.mjs';
|
|
46
|
-
import type { Storyboard, StoryboardRunOptions, StoryboardStep } from './types.mjs';
|
|
46
|
+
import type { AgentEntry, Storyboard, StoryboardRunOptions, StoryboardStep } from './types.mjs';
|
|
47
|
+
/** Per-agent options view: per-entry overrides shadow run-level defaults. */
|
|
48
|
+
export declare function buildAgentOptions(entry: AgentEntry, options: StoryboardRunOptions): StoryboardRunOptions;
|
|
47
49
|
export interface AgentRoutingContext {
|
|
48
50
|
/** Per-agent transport client. Lifecycle owned by the runner. */
|
|
49
51
|
clients: Map<string, TestClient>;
|
|
@@ -43,7 +43,9 @@
|
|
|
43
43
|
import type { TestClient } from '../client';
|
|
44
44
|
import type { AgentProfile } from '../types';
|
|
45
45
|
import { type AdcpProtocol } from '../../utils/capabilities';
|
|
46
|
-
import type { Storyboard, StoryboardRunOptions, StoryboardStep } from './types';
|
|
46
|
+
import type { AgentEntry, Storyboard, StoryboardRunOptions, StoryboardStep } from './types';
|
|
47
|
+
/** Per-agent options view: per-entry overrides shadow run-level defaults. */
|
|
48
|
+
export declare function buildAgentOptions(entry: AgentEntry, options: StoryboardRunOptions): StoryboardRunOptions;
|
|
47
49
|
export interface AgentRoutingContext {
|
|
48
50
|
/** Per-agent transport client. Lifecycle owned by the runner. */
|
|
49
51
|
clients: Map<string, TestClient>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-routing.d.ts","sourceRoot":"","sources":["../../../../src/lib/testing/storyboard/agent-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"agent-routing.d.ts","sourceRoot":"","sources":["../../../../src/lib/testing/storyboard/agent-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAoD5F,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAkBxG;AAED,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACjC,qEAAqE;IACrE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACpC,+CAA+C;IAC/C,aAAa,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;;;OAOG;IACH,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;CACvC;AAED,qBAAa,YAAa,SAAQ,KAAK;aAGnB,IAAI,EAAE,MAAM;aACZ,IAAI,EAAE,MAAM;gBAF5B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;CAK/B;AAED,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,QAAQ,EAAE,MAAM;aAChB,GAAG,EAAE,MAAM;aACX,UAAU,EAAE,MAAM;gBAHlC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM;CAKrC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAmE9B;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAClC,mBAAmB,CAUrB;AAwFD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,mBAAmB,GACvB,MAAM,CAkER"}
|
|
@@ -20,6 +20,7 @@ var agent_routing_exports = {};
|
|
|
20
20
|
__export(agent_routing_exports, {
|
|
21
21
|
DiscoveryFailure: () => DiscoveryFailure,
|
|
22
22
|
RoutingError: () => RoutingError,
|
|
23
|
+
buildAgentOptions: () => buildAgentOptions,
|
|
23
24
|
buildRoutingContext: () => buildRoutingContext,
|
|
24
25
|
buildRoutingContextFromProfiles: () => buildRoutingContextFromProfiles,
|
|
25
26
|
resolveAgentForStep: () => resolveAgentForStep
|
|
@@ -27,6 +28,7 @@ __export(agent_routing_exports, {
|
|
|
27
28
|
module.exports = __toCommonJS(agent_routing_exports);
|
|
28
29
|
var import_client = require('../client.js');
|
|
29
30
|
var import_capabilities = require('../../utils/capabilities.js');
|
|
31
|
+
var import_native_a2a_compliance = require('./native-a2a-compliance.js');
|
|
30
32
|
const KNOWN_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
31
33
|
"media_buy",
|
|
32
34
|
"signals",
|
|
@@ -41,7 +43,7 @@ function scrubAuthSecrets(text) {
|
|
|
41
43
|
return text.replace(/(authorization\s*:\s*bearer\s+)[A-Za-z0-9._~+/=-]+/gi, "$1[REDACTED]").replace(/\bbearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/([?&]token=)[A-Za-z0-9._~+/=-]+/gi, "$1[REDACTED]");
|
|
42
44
|
}
|
|
43
45
|
function buildAgentOptions(entry, options) {
|
|
44
|
-
return {
|
|
46
|
+
return (0, import_native_a2a_compliance.applyNativeA2AComplianceTransportOptions)({
|
|
45
47
|
...options,
|
|
46
48
|
auth: entry.auth ?? options.auth,
|
|
47
49
|
protocol: entry.transport ?? options.protocol,
|
|
@@ -57,7 +59,7 @@ function buildAgentOptions(entry, options) {
|
|
|
57
59
|
_profile: void 0,
|
|
58
60
|
agents: void 0,
|
|
59
61
|
agentTools: void 0
|
|
60
|
-
};
|
|
62
|
+
});
|
|
61
63
|
}
|
|
62
64
|
class RoutingError extends Error {
|
|
63
65
|
constructor(message, task, hint) {
|
|
@@ -241,6 +243,7 @@ function resolveAgentForStep(step, options, ctx) {
|
|
|
241
243
|
0 && (module.exports = {
|
|
242
244
|
DiscoveryFailure,
|
|
243
245
|
RoutingError,
|
|
246
|
+
buildAgentOptions,
|
|
244
247
|
buildRoutingContext,
|
|
245
248
|
buildRoutingContextFromProfiles,
|
|
246
249
|
resolveAgentForStep
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/testing/storyboard/agent-routing.ts"],"sourcesContent":["/**\n * Per-specialism agent routing for the storyboard runner (#1066).\n *\n * Resolves each step's `task` to one of N agents in\n * `StoryboardRunOptions.agents` so storyboards that span specialisms\n * (e.g., a `signal_marketplace/governance_denied` flow that calls\n * `sync_governance` then `activate_signal`) can hit the tenant that\n * actually owns each tool.\n *\n * Resolution order per step:\n * 1. Explicit `step.agent` (escape hatch for cross-domain tools)\n * 2. `TASK_FEATURE_MAP` → first protocol → unique claimant in the agents map\n * 3. `options.default_agent`\n * 4. Throw `RoutingError`\n *\n * Multi-claim conflicts (two agents claim the same protocol AND a step\n * relying on that protocol lacks an explicit `agent:` override) fail-fast\n * at routing-context build time, BEFORE any non-discovery network calls.\n *\n * ## Webhook receiver topology in routed mode\n *\n * When `StoryboardRunOptions.webhook_receiver` is set alongside `agents`,\n * the receiver is **shared** across all tenants — one HTTP server, one base\n * URL. The runner does not create per-tenant receivers and does not need to:\n * delivery correlation is by **step-keyed URL** (`/step/<step_id>/<op_id>`),\n * not by source agent. If both a governance tenant and a signals tenant\n * emit webhooks during the same run, the assertion harness matches each\n * delivery to its `expect_webhook*` step by URL path, regardless of which\n * tenant posted it. This means:\n *\n * - `{{runner.webhook_base}}` substitutes the same URL for every step\n * across all agents — no per-agent variable needed.\n * - `{{runner.webhook_url:<step_id>}}` is always sufficient to pin a\n * delivery to the step that requested it, even when multiple tenants\n * emit concurrently.\n * - Per-tenant receivers are not supported; if a future storyboard needs\n * delivery-source disambiguation, use distinct step IDs (they already\n * produce distinct URL paths).\n *\n * See `StoryboardRunOptions.webhook_receiver` JSDoc and `webhook-receiver.ts`\n * for implementation details.\n */\nimport type { TestClient } from '../client';\nimport { getOrCreateClient, getOrDiscoverProfile } from '../client';\nimport type { AgentProfile } from '../types';\nimport { TASK_FEATURE_MAP, type AdcpProtocol } from '../../utils/capabilities';\nimport type { AgentEntry, Storyboard, StoryboardRunOptions, StoryboardStep } from './types';\n\n// `compliance_testing` is on the wire as a top-level capability block, NOT\n// in `supported_protocols`. `parseCapabilitiesResponse`\n// (`src/lib/utils/capabilities.ts`) normalizes the block into the\n// `protocols[]` list before `getOrDiscoverProfile` writes it onto the\n// profile, so by the time routing reads `profile.supported_protocols`\n// the value is present. Any future refactor that strips that\n// normalization needs to either preserve it or drop `compliance_testing`\n// from this set.\nconst KNOWN_PROTOCOLS: ReadonlySet<AdcpProtocol> = new Set([\n 'media_buy',\n 'signals',\n 'governance',\n 'creative',\n 'sponsored_intelligence',\n 'trusted_match',\n 'compliance_testing',\n 'brand',\n]);\n\n/**\n * Redact common auth-token patterns before propagating an upstream error\n * message into runner output / CI logs / `StoryboardResult.error`. The\n * concrete leak path: a misconfigured agent that echoes its received\n * `Authorization: Bearer <token>` header in a 401 body would put the\n * caller's own bearer for that tenant into CI artifacts. The bearer is\n * already in the caller's CI environment, so this is parity with single-\n * agent mode rather than a fix for a new leak — but defense-in-depth is\n * cheap and the regex is bounded.\n *\n * Patterns covered:\n * - `Authorization: Bearer <token>` (any case, common in 401 echoes)\n * - `Bearer <token>` standalone\n * - `?token=<value>` / `&token=<value>` query-string tokens\n *\n * Best-effort. Anything unusual (custom-header schemes, base64-encoded\n * blobs in JSON bodies) is not caught — operators should still treat\n * upstream-error CI logs as sensitive.\n */\nfunction scrubAuthSecrets(text: string): string {\n // The character class `[A-Za-z0-9._~+/=-]+` looks like high-entropy\n // content to entropy-based secret scanners (GitGuardian/gitleaks). It\n // is the redaction pattern itself — no secret is encoded here.\n // ggignore\n return text\n .replace(/(authorization\\s*:\\s*bearer\\s+)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]') // ggignore\n .replace(/\\bbearer\\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]') // ggignore\n .replace(/([?&]token=)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]'); // ggignore\n}\n\n/** Per-agent options view: per-entry overrides shadow run-level defaults. */\nfunction buildAgentOptions(entry: AgentEntry, options: StoryboardRunOptions): StoryboardRunOptions {\n return {\n ...options,\n auth: entry.auth ?? options.auth,\n protocol: entry.transport ?? options.protocol,\n // _client, _profile, and agents are run-scoped — never inherit into\n // per-agent views. `_profile` is the load-bearing one: when comply()\n // sets it on the run-level options, `getOrDiscoverProfile` (`client.ts`)\n // short-circuits to that cached profile and skips the actual probe.\n // Without this clear, every per-agent discovery would return\n // comply()'s primary-tenant profile instead of probing the real\n // tenant — silently breaking the protocol-claim index that routing\n // depends on.\n _client: undefined,\n _profile: undefined,\n agents: undefined,\n agentTools: undefined,\n };\n}\n\nexport interface AgentRoutingContext {\n /** Per-agent transport client. Lifecycle owned by the runner. */\n clients: Map<string, TestClient>;\n /** Per-agent discovered profile (`get_adcp_capabilities` result). */\n profiles: Map<string, AgentProfile>;\n /** Protocol → keys of agents that claim it. */\n protocolIndex: Map<AdcpProtocol, string[]>;\n /** Resolved (key → URL) for echo on the storyboard result. */\n agentMap: Record<string, string>;\n /**\n * Agents whose discovery failed when `discovery_resilient: true` was set.\n * Empty in the default (hard-failure) mode — if any agent failed and\n * resilient mode is off, `buildRoutingContext` throws `DiscoveryFailure`\n * instead of returning. The runner echoes this list onto\n * `StoryboardResult.discovery_failures` so operators see the topology\n * breakage even when the storyboard otherwise completes.\n */\n discoveryFailures: DiscoveryFailure[];\n}\n\nexport class RoutingError extends Error {\n constructor(\n message: string,\n public readonly task: string,\n public readonly hint: string\n ) {\n super(message);\n this.name = 'RoutingError';\n }\n}\n\nexport class DiscoveryFailure extends Error {\n constructor(\n message: string,\n public readonly agentKey: string,\n public readonly url: string,\n public readonly underlying: string\n ) {\n super(message);\n this.name = 'DiscoveryFailure';\n }\n}\n\n/**\n * Discover every agent in the map in parallel and build the routing index.\n *\n * Failure modes:\n * - default (`options.discovery_resilient !== true`): any agent's\n * discovery failure → throw `DiscoveryFailure` (caller surfaces as a\n * hard storyboard failure, never a per-step skip). Correct for\n * production multi-tenant flows where every tenant is load-bearing.\n * - resilient (`options.discovery_resilient === true`, #1367): failed\n * agents are excluded from `profiles` + `protocolIndex` and reported\n * on `AgentRoutingContext.discoveryFailures`. Per-step routing then\n * surfaces failures only for storyboards that actually need the\n * broken tenant — unrelated storyboards complete normally.\n * - protocol claimed by 2+ agents AND ≥1 storyboard step that needs it\n * lacks `step.agent` → `RoutingError` (conflict). Independent of\n * resilient mode.\n */\nexport async function buildRoutingContext(\n storyboard: Storyboard,\n options: StoryboardRunOptions\n): Promise<AgentRoutingContext> {\n const agents = options.agents!;\n const entries = Object.entries(agents);\n const resilient = options.discovery_resilient === true;\n\n const clients = new Map<string, TestClient>();\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of entries) {\n const perAgentOptions = buildAgentOptions(entry, options);\n clients.set(key, getOrCreateClient(entry.url, perAgentOptions));\n agentMap[key] = entry.url;\n }\n\n // Parallel discovery — one tenant's slowness does not block another.\n const profiles = new Map<string, AgentProfile>();\n const discoveryFailures: DiscoveryFailure[] = [];\n const discoveryResults = await Promise.all(\n entries.map(async ([key, entry]) => {\n const perAgentOptions = buildAgentOptions(entry, options);\n try {\n const { profile, step } = await getOrDiscoverProfile(clients.get(key)!, perAgentOptions);\n return { key, profile, step };\n } catch (err) {\n return {\n key,\n profile: undefined,\n step: {\n step: 'Discover agent capabilities',\n passed: false,\n error: scrubAuthSecrets((err as Error)?.message || String(err)),\n },\n };\n }\n })\n );\n for (const r of discoveryResults) {\n if (!r.profile || r.step.passed === false) {\n // Bound the attacker-influenced upstream string. A malicious agent\n // could return an arbitrarily large error body during the discovery\n // probe; the unbounded string would flow into\n // `StoryboardResult.discovery_failures[].error` where downstream\n // dashboards / LLM contexts ingest it. 512 chars is enough for a\n // human to read the failure and grep logs for more.\n const rawDetail = scrubAuthSecrets(r.step.error ?? 'Discovery returned no profile.');\n const detail = rawDetail.length > 512 ? `${rawDetail.slice(0, 512)} …(truncated)` : rawDetail;\n // Default mode appends a signposted escape hatch so CI operators\n // discover the resilient flag without grep'ing the JSDoc. Suppressed\n // in resilient mode (the operator already opted in) so the hint\n // doesn't appear in every per-agent failure attached to the result.\n const baseMessage = `Discovery failed for agent \"${r.key}\" (${agents[r.key]!.url}): ${detail}`;\n const message = resilient\n ? baseMessage\n : `${baseMessage}\\n\\nIf this is hello-cluster / exploratory CI and you want unrelated storyboards to complete despite this failure, ` +\n `set \\`StoryboardRunOptions.discovery_resilient: true\\`. Do NOT set it in production — a topology with a broken tenant ` +\n `is a hard misconfiguration there.`;\n const failure = new DiscoveryFailure(message, r.key, agents[r.key]!.url, detail);\n if (!resilient) throw failure;\n discoveryFailures.push(failure);\n continue;\n }\n profiles.set(r.key, r.profile);\n }\n\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n\n return { clients, profiles, protocolIndex, agentMap, discoveryFailures };\n}\n\n/**\n * Test seam: build a routing context from pre-baked profiles instead of\n * running discovery. Lets unit tests exercise conflict detection and\n * per-step resolution without spinning up live agents. Production code\n * uses `buildRoutingContext`; this entry point is exported for the\n * `agent-routing.test.js` harness.\n */\nexport function buildRoutingContextFromProfiles(\n storyboard: Storyboard,\n options: StoryboardRunOptions,\n profiles: Map<string, AgentProfile>\n): AgentRoutingContext {\n const agents = options.agents!;\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of Object.entries(agents)) {\n agentMap[key] = entry.url;\n }\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n // Empty client map — callers that only test routing shouldn't dispatch.\n return { clients: new Map(), profiles, protocolIndex, agentMap, discoveryFailures: [] };\n}\n\n/**\n * Reverse-index `AgentProfile.supported_protocols` so routing can pick an\n * agent by tool→protocol→agent in O(1).\n *\n * Sorts the agent-key list per protocol for deterministic conflict-error\n * output and reproducible tests.\n */\nfunction buildProtocolIndex(profiles: Map<string, AgentProfile>): Map<AdcpProtocol, string[]> {\n const index = new Map<AdcpProtocol, string[]>();\n for (const [key, profile] of profiles) {\n const protocols = profile.supported_protocols ?? [];\n for (const p of protocols) {\n if (!KNOWN_PROTOCOLS.has(p as AdcpProtocol)) continue;\n const proto = p as AdcpProtocol;\n const list = index.get(proto) ?? [];\n list.push(key);\n index.set(proto, list);\n }\n }\n for (const list of index.values()) list.sort();\n return index;\n}\n\n/**\n * Walk the storyboard once and surface any tool→protocol resolution that\n * has 2+ candidate agents AND no explicit `step.agent` override.\n *\n * We could defer this to per-step routing time, but a multi-claim is\n * almost always a topology bug (two tenants accidentally claiming the\n * same protocol) — surfacing it before the first non-discovery network\n * call gives a much better error than a half-run storyboard.\n */\nfunction detectMultiClaimConflicts(storyboard: Storyboard, protocolIndex: Map<AdcpProtocol, string[]>): void {\n const conflicts: Array<{ task: string; protocol: AdcpProtocol; agents: string[]; stepIds: string[] }> = [];\n for (const phase of storyboard.phases ?? []) {\n for (const step of phase.steps ?? []) {\n if (step.agent !== undefined) continue;\n const protocol = primaryProtocolFor(step.task);\n if (!protocol) continue;\n const candidates = protocolIndex.get(protocol);\n if (!candidates || candidates.length < 2) continue;\n const existing = conflicts.find(c => c.task === step.task && c.protocol === protocol);\n if (existing) {\n existing.stepIds.push(step.id);\n } else {\n conflicts.push({\n task: step.task,\n protocol,\n agents: candidates,\n stepIds: [step.id],\n });\n }\n }\n }\n if (conflicts.length === 0) return;\n\n const lines = conflicts.map(\n c =>\n ` - tool \"${c.task}\" (protocol ${c.protocol}) is claimed by [${c.agents.join(', ')}] ` +\n `but step(s) [${c.stepIds.join(', ')}] do not declare \\`agent:\\` override.`\n );\n throw new RoutingError(\n `Routing conflict: multiple agents claim the same protocol and the storyboard ` +\n `does not disambiguate.\\n${lines.join('\\n')}\\n` +\n `Fix: either remove one of the conflicting agents from the map, or add ` +\n `\\`agent: <key>\\` to each affected step.`,\n conflicts[0]!.task,\n `Conflicting agents: ${conflicts[0]!.agents.join(', ')}`\n );\n}\n\n/**\n * First protocol-typed feature in `TASK_FEATURE_MAP[task]`, or `undefined`\n * for tools intentionally unmapped (e.g. `sync_creatives`, which serves\n * both media-buy and creative domains and requires explicit per-step\n * routing in multi-agent mode).\n */\nfunction primaryProtocolFor(task: string): AdcpProtocol | undefined {\n const features = TASK_FEATURE_MAP[task];\n if (!features) return undefined;\n for (const f of features) {\n if (KNOWN_PROTOCOLS.has(f as AdcpProtocol)) return f as AdcpProtocol;\n }\n return undefined;\n}\n\n/**\n * Resolve a step to its target agent key. Throws `RoutingError` when no\n * route can be determined; callers convert to a synthetic step result\n * (see Stage 4 in the runner).\n */\nexport function resolveAgentForStep(\n step: StoryboardStep,\n options: StoryboardRunOptions,\n ctx: AgentRoutingContext\n): string {\n if (step.agent !== undefined) {\n // If the override targets an agent whose discovery failed under\n // resilient mode, surface that explicitly rather than handing\n // dispatch a transport client to a broken tenant (which would\n // otherwise fail at the wire layer with a misleading transport\n // error). Only meaningful when `discoveryFailures` is non-empty;\n // under default (hard-failure) mode `buildRoutingContext` throws\n // before we reach this branch.\n const failed = ctx.discoveryFailures.find(f => f.agentKey === step.agent);\n if (failed) {\n throw new RoutingError(\n `Step \"${step.id}\" targets agent \"${step.agent}\" via \\`step.agent\\` override, ` +\n `but that agent's discovery failed: ${failed.underlying} ` +\n `(${failed.url}). Either remove the override, fix the agent, or ` +\n `route the step to a healthy agent.`,\n step.task,\n `agent \"${step.agent}\" failed discovery`\n );\n }\n return step.agent;\n }\n const protocol = primaryProtocolFor(step.task);\n if (protocol) {\n const candidates = ctx.protocolIndex.get(protocol) ?? [];\n if (candidates.length === 1) return candidates[0]!;\n if (candidates.length > 1) {\n // Conflict already detected at build time, but `step.agent` is\n // unset here. This branch is unreachable in well-formed runs; keep\n // it as a fail-loud sanity check.\n throw new RoutingError(\n `Internal: step \"${step.id}\" task \"${step.task}\" maps to protocol ` +\n `\"${protocol}\" claimed by [${candidates.join(', ')}], no step.agent override. ` +\n `This should have been caught at routing-context build time.`,\n step.task,\n `agents: ${candidates.join(', ')}`\n );\n }\n // Zero candidates — no agent in the map claims this protocol.\n if (options.default_agent) return options.default_agent;\n // Under `discovery_resilient`, an agent that was supposed to claim\n // this protocol may have been excluded by a failed probe. Suffix the\n // error so the operator sees the connection without correlating logs.\n const failedHint =\n ctx.discoveryFailures.length > 0\n ? ` Discovery failed for: ${ctx.discoveryFailures.map(f => `${f.agentKey} (${f.url})`).join(', ')}.`\n : '';\n throw new RoutingError(\n `No agent in the map claims protocol \"${protocol}\" required by tool ` +\n `\"${step.task}\" (step \"${step.id}\"). Available agents: ` +\n `${[...ctx.profiles.keys()].join(', ') || '(none — every agent failed discovery)'}.${failedHint} ` +\n `Add an agent that supports ${protocol}, or set \\`default_agent\\` to fall back.`,\n step.task,\n `protocol ${protocol} unclaimed`\n );\n }\n // Tool not in TASK_FEATURE_MAP (e.g., sync_creatives, comply_test_controller,\n // get_adcp_capabilities post-discovery, future tasks).\n if (options.default_agent) return options.default_agent;\n throw new RoutingError(\n `Tool \"${step.task}\" (step \"${step.id}\") has no specialism mapping ` +\n `(it serves multiple domains or is unrecognized). Set \\`agent: <key>\\` ` +\n `on the step, or set \\`default_agent\\` on the run options.`,\n step.task,\n 'no protocol mapping'\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/testing/storyboard/agent-routing.ts"],"sourcesContent":["/**\n * Per-specialism agent routing for the storyboard runner (#1066).\n *\n * Resolves each step's `task` to one of N agents in\n * `StoryboardRunOptions.agents` so storyboards that span specialisms\n * (e.g., a `signal_marketplace/governance_denied` flow that calls\n * `sync_governance` then `activate_signal`) can hit the tenant that\n * actually owns each tool.\n *\n * Resolution order per step:\n * 1. Explicit `step.agent` (escape hatch for cross-domain tools)\n * 2. `TASK_FEATURE_MAP` → first protocol → unique claimant in the agents map\n * 3. `options.default_agent`\n * 4. Throw `RoutingError`\n *\n * Multi-claim conflicts (two agents claim the same protocol AND a step\n * relying on that protocol lacks an explicit `agent:` override) fail-fast\n * at routing-context build time, BEFORE any non-discovery network calls.\n *\n * ## Webhook receiver topology in routed mode\n *\n * When `StoryboardRunOptions.webhook_receiver` is set alongside `agents`,\n * the receiver is **shared** across all tenants — one HTTP server, one base\n * URL. The runner does not create per-tenant receivers and does not need to:\n * delivery correlation is by **step-keyed URL** (`/step/<step_id>/<op_id>`),\n * not by source agent. If both a governance tenant and a signals tenant\n * emit webhooks during the same run, the assertion harness matches each\n * delivery to its `expect_webhook*` step by URL path, regardless of which\n * tenant posted it. This means:\n *\n * - `{{runner.webhook_base}}` substitutes the same URL for every step\n * across all agents — no per-agent variable needed.\n * - `{{runner.webhook_url:<step_id>}}` is always sufficient to pin a\n * delivery to the step that requested it, even when multiple tenants\n * emit concurrently.\n * - Per-tenant receivers are not supported; if a future storyboard needs\n * delivery-source disambiguation, use distinct step IDs (they already\n * produce distinct URL paths).\n *\n * See `StoryboardRunOptions.webhook_receiver` JSDoc and `webhook-receiver.ts`\n * for implementation details.\n */\nimport type { TestClient } from '../client';\nimport { getOrCreateClient, getOrDiscoverProfile } from '../client';\nimport type { AgentProfile } from '../types';\nimport { TASK_FEATURE_MAP, type AdcpProtocol } from '../../utils/capabilities';\nimport type { AgentEntry, Storyboard, StoryboardRunOptions, StoryboardStep } from './types';\nimport { applyNativeA2AComplianceTransportOptions } from './native-a2a-compliance';\n\n// `compliance_testing` is on the wire as a top-level capability block, NOT\n// in `supported_protocols`. `parseCapabilitiesResponse`\n// (`src/lib/utils/capabilities.ts`) normalizes the block into the\n// `protocols[]` list before `getOrDiscoverProfile` writes it onto the\n// profile, so by the time routing reads `profile.supported_protocols`\n// the value is present. Any future refactor that strips that\n// normalization needs to either preserve it or drop `compliance_testing`\n// from this set.\nconst KNOWN_PROTOCOLS: ReadonlySet<AdcpProtocol> = new Set([\n 'media_buy',\n 'signals',\n 'governance',\n 'creative',\n 'sponsored_intelligence',\n 'trusted_match',\n 'compliance_testing',\n 'brand',\n]);\n\n/**\n * Redact common auth-token patterns before propagating an upstream error\n * message into runner output / CI logs / `StoryboardResult.error`. The\n * concrete leak path: a misconfigured agent that echoes its received\n * `Authorization: Bearer <token>` header in a 401 body would put the\n * caller's own bearer for that tenant into CI artifacts. The bearer is\n * already in the caller's CI environment, so this is parity with single-\n * agent mode rather than a fix for a new leak — but defense-in-depth is\n * cheap and the regex is bounded.\n *\n * Patterns covered:\n * - `Authorization: Bearer <token>` (any case, common in 401 echoes)\n * - `Bearer <token>` standalone\n * - `?token=<value>` / `&token=<value>` query-string tokens\n *\n * Best-effort. Anything unusual (custom-header schemes, base64-encoded\n * blobs in JSON bodies) is not caught — operators should still treat\n * upstream-error CI logs as sensitive.\n */\nfunction scrubAuthSecrets(text: string): string {\n // The character class `[A-Za-z0-9._~+/=-]+` looks like high-entropy\n // content to entropy-based secret scanners (GitGuardian/gitleaks). It\n // is the redaction pattern itself — no secret is encoded here.\n // ggignore\n return text\n .replace(/(authorization\\s*:\\s*bearer\\s+)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]') // ggignore\n .replace(/\\bbearer\\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]') // ggignore\n .replace(/([?&]token=)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]'); // ggignore\n}\n\n/** Per-agent options view: per-entry overrides shadow run-level defaults. */\nexport function buildAgentOptions(entry: AgentEntry, options: StoryboardRunOptions): StoryboardRunOptions {\n return applyNativeA2AComplianceTransportOptions({\n ...options,\n auth: entry.auth ?? options.auth,\n protocol: entry.transport ?? options.protocol,\n // _client, _profile, and agents are run-scoped — never inherit into\n // per-agent views. `_profile` is the load-bearing one: when comply()\n // sets it on the run-level options, `getOrDiscoverProfile` (`client.ts`)\n // short-circuits to that cached profile and skips the actual probe.\n // Without this clear, every per-agent discovery would return\n // comply()'s primary-tenant profile instead of probing the real\n // tenant — silently breaking the protocol-claim index that routing\n // depends on.\n _client: undefined,\n _profile: undefined,\n agents: undefined,\n agentTools: undefined,\n });\n}\n\nexport interface AgentRoutingContext {\n /** Per-agent transport client. Lifecycle owned by the runner. */\n clients: Map<string, TestClient>;\n /** Per-agent discovered profile (`get_adcp_capabilities` result). */\n profiles: Map<string, AgentProfile>;\n /** Protocol → keys of agents that claim it. */\n protocolIndex: Map<AdcpProtocol, string[]>;\n /** Resolved (key → URL) for echo on the storyboard result. */\n agentMap: Record<string, string>;\n /**\n * Agents whose discovery failed when `discovery_resilient: true` was set.\n * Empty in the default (hard-failure) mode — if any agent failed and\n * resilient mode is off, `buildRoutingContext` throws `DiscoveryFailure`\n * instead of returning. The runner echoes this list onto\n * `StoryboardResult.discovery_failures` so operators see the topology\n * breakage even when the storyboard otherwise completes.\n */\n discoveryFailures: DiscoveryFailure[];\n}\n\nexport class RoutingError extends Error {\n constructor(\n message: string,\n public readonly task: string,\n public readonly hint: string\n ) {\n super(message);\n this.name = 'RoutingError';\n }\n}\n\nexport class DiscoveryFailure extends Error {\n constructor(\n message: string,\n public readonly agentKey: string,\n public readonly url: string,\n public readonly underlying: string\n ) {\n super(message);\n this.name = 'DiscoveryFailure';\n }\n}\n\n/**\n * Discover every agent in the map in parallel and build the routing index.\n *\n * Failure modes:\n * - default (`options.discovery_resilient !== true`): any agent's\n * discovery failure → throw `DiscoveryFailure` (caller surfaces as a\n * hard storyboard failure, never a per-step skip). Correct for\n * production multi-tenant flows where every tenant is load-bearing.\n * - resilient (`options.discovery_resilient === true`, #1367): failed\n * agents are excluded from `profiles` + `protocolIndex` and reported\n * on `AgentRoutingContext.discoveryFailures`. Per-step routing then\n * surfaces failures only for storyboards that actually need the\n * broken tenant — unrelated storyboards complete normally.\n * - protocol claimed by 2+ agents AND ≥1 storyboard step that needs it\n * lacks `step.agent` → `RoutingError` (conflict). Independent of\n * resilient mode.\n */\nexport async function buildRoutingContext(\n storyboard: Storyboard,\n options: StoryboardRunOptions\n): Promise<AgentRoutingContext> {\n const agents = options.agents!;\n const entries = Object.entries(agents);\n const resilient = options.discovery_resilient === true;\n\n const clients = new Map<string, TestClient>();\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of entries) {\n const perAgentOptions = buildAgentOptions(entry, options);\n clients.set(key, getOrCreateClient(entry.url, perAgentOptions));\n agentMap[key] = entry.url;\n }\n\n // Parallel discovery — one tenant's slowness does not block another.\n const profiles = new Map<string, AgentProfile>();\n const discoveryFailures: DiscoveryFailure[] = [];\n const discoveryResults = await Promise.all(\n entries.map(async ([key, entry]) => {\n const perAgentOptions = buildAgentOptions(entry, options);\n try {\n const { profile, step } = await getOrDiscoverProfile(clients.get(key)!, perAgentOptions);\n return { key, profile, step };\n } catch (err) {\n return {\n key,\n profile: undefined,\n step: {\n step: 'Discover agent capabilities',\n passed: false,\n error: scrubAuthSecrets((err as Error)?.message || String(err)),\n },\n };\n }\n })\n );\n for (const r of discoveryResults) {\n if (!r.profile || r.step.passed === false) {\n // Bound the attacker-influenced upstream string. A malicious agent\n // could return an arbitrarily large error body during the discovery\n // probe; the unbounded string would flow into\n // `StoryboardResult.discovery_failures[].error` where downstream\n // dashboards / LLM contexts ingest it. 512 chars is enough for a\n // human to read the failure and grep logs for more.\n const rawDetail = scrubAuthSecrets(r.step.error ?? 'Discovery returned no profile.');\n const detail = rawDetail.length > 512 ? `${rawDetail.slice(0, 512)} …(truncated)` : rawDetail;\n // Default mode appends a signposted escape hatch so CI operators\n // discover the resilient flag without grep'ing the JSDoc. Suppressed\n // in resilient mode (the operator already opted in) so the hint\n // doesn't appear in every per-agent failure attached to the result.\n const baseMessage = `Discovery failed for agent \"${r.key}\" (${agents[r.key]!.url}): ${detail}`;\n const message = resilient\n ? baseMessage\n : `${baseMessage}\\n\\nIf this is hello-cluster / exploratory CI and you want unrelated storyboards to complete despite this failure, ` +\n `set \\`StoryboardRunOptions.discovery_resilient: true\\`. Do NOT set it in production — a topology with a broken tenant ` +\n `is a hard misconfiguration there.`;\n const failure = new DiscoveryFailure(message, r.key, agents[r.key]!.url, detail);\n if (!resilient) throw failure;\n discoveryFailures.push(failure);\n continue;\n }\n profiles.set(r.key, r.profile);\n }\n\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n\n return { clients, profiles, protocolIndex, agentMap, discoveryFailures };\n}\n\n/**\n * Test seam: build a routing context from pre-baked profiles instead of\n * running discovery. Lets unit tests exercise conflict detection and\n * per-step resolution without spinning up live agents. Production code\n * uses `buildRoutingContext`; this entry point is exported for the\n * `agent-routing.test.js` harness.\n */\nexport function buildRoutingContextFromProfiles(\n storyboard: Storyboard,\n options: StoryboardRunOptions,\n profiles: Map<string, AgentProfile>\n): AgentRoutingContext {\n const agents = options.agents!;\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of Object.entries(agents)) {\n agentMap[key] = entry.url;\n }\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n // Empty client map — callers that only test routing shouldn't dispatch.\n return { clients: new Map(), profiles, protocolIndex, agentMap, discoveryFailures: [] };\n}\n\n/**\n * Reverse-index `AgentProfile.supported_protocols` so routing can pick an\n * agent by tool→protocol→agent in O(1).\n *\n * Sorts the agent-key list per protocol for deterministic conflict-error\n * output and reproducible tests.\n */\nfunction buildProtocolIndex(profiles: Map<string, AgentProfile>): Map<AdcpProtocol, string[]> {\n const index = new Map<AdcpProtocol, string[]>();\n for (const [key, profile] of profiles) {\n const protocols = profile.supported_protocols ?? [];\n for (const p of protocols) {\n if (!KNOWN_PROTOCOLS.has(p as AdcpProtocol)) continue;\n const proto = p as AdcpProtocol;\n const list = index.get(proto) ?? [];\n list.push(key);\n index.set(proto, list);\n }\n }\n for (const list of index.values()) list.sort();\n return index;\n}\n\n/**\n * Walk the storyboard once and surface any tool→protocol resolution that\n * has 2+ candidate agents AND no explicit `step.agent` override.\n *\n * We could defer this to per-step routing time, but a multi-claim is\n * almost always a topology bug (two tenants accidentally claiming the\n * same protocol) — surfacing it before the first non-discovery network\n * call gives a much better error than a half-run storyboard.\n */\nfunction detectMultiClaimConflicts(storyboard: Storyboard, protocolIndex: Map<AdcpProtocol, string[]>): void {\n const conflicts: Array<{ task: string; protocol: AdcpProtocol; agents: string[]; stepIds: string[] }> = [];\n for (const phase of storyboard.phases ?? []) {\n for (const step of phase.steps ?? []) {\n if (step.agent !== undefined) continue;\n const protocol = primaryProtocolFor(step.task);\n if (!protocol) continue;\n const candidates = protocolIndex.get(protocol);\n if (!candidates || candidates.length < 2) continue;\n const existing = conflicts.find(c => c.task === step.task && c.protocol === protocol);\n if (existing) {\n existing.stepIds.push(step.id);\n } else {\n conflicts.push({\n task: step.task,\n protocol,\n agents: candidates,\n stepIds: [step.id],\n });\n }\n }\n }\n if (conflicts.length === 0) return;\n\n const lines = conflicts.map(\n c =>\n ` - tool \"${c.task}\" (protocol ${c.protocol}) is claimed by [${c.agents.join(', ')}] ` +\n `but step(s) [${c.stepIds.join(', ')}] do not declare \\`agent:\\` override.`\n );\n throw new RoutingError(\n `Routing conflict: multiple agents claim the same protocol and the storyboard ` +\n `does not disambiguate.\\n${lines.join('\\n')}\\n` +\n `Fix: either remove one of the conflicting agents from the map, or add ` +\n `\\`agent: <key>\\` to each affected step.`,\n conflicts[0]!.task,\n `Conflicting agents: ${conflicts[0]!.agents.join(', ')}`\n );\n}\n\n/**\n * First protocol-typed feature in `TASK_FEATURE_MAP[task]`, or `undefined`\n * for tools intentionally unmapped (e.g. `sync_creatives`, which serves\n * both media-buy and creative domains and requires explicit per-step\n * routing in multi-agent mode).\n */\nfunction primaryProtocolFor(task: string): AdcpProtocol | undefined {\n const features = TASK_FEATURE_MAP[task];\n if (!features) return undefined;\n for (const f of features) {\n if (KNOWN_PROTOCOLS.has(f as AdcpProtocol)) return f as AdcpProtocol;\n }\n return undefined;\n}\n\n/**\n * Resolve a step to its target agent key. Throws `RoutingError` when no\n * route can be determined; callers convert to a synthetic step result\n * (see Stage 4 in the runner).\n */\nexport function resolveAgentForStep(\n step: StoryboardStep,\n options: StoryboardRunOptions,\n ctx: AgentRoutingContext\n): string {\n if (step.agent !== undefined) {\n // If the override targets an agent whose discovery failed under\n // resilient mode, surface that explicitly rather than handing\n // dispatch a transport client to a broken tenant (which would\n // otherwise fail at the wire layer with a misleading transport\n // error). Only meaningful when `discoveryFailures` is non-empty;\n // under default (hard-failure) mode `buildRoutingContext` throws\n // before we reach this branch.\n const failed = ctx.discoveryFailures.find(f => f.agentKey === step.agent);\n if (failed) {\n throw new RoutingError(\n `Step \"${step.id}\" targets agent \"${step.agent}\" via \\`step.agent\\` override, ` +\n `but that agent's discovery failed: ${failed.underlying} ` +\n `(${failed.url}). Either remove the override, fix the agent, or ` +\n `route the step to a healthy agent.`,\n step.task,\n `agent \"${step.agent}\" failed discovery`\n );\n }\n return step.agent;\n }\n const protocol = primaryProtocolFor(step.task);\n if (protocol) {\n const candidates = ctx.protocolIndex.get(protocol) ?? [];\n if (candidates.length === 1) return candidates[0]!;\n if (candidates.length > 1) {\n // Conflict already detected at build time, but `step.agent` is\n // unset here. This branch is unreachable in well-formed runs; keep\n // it as a fail-loud sanity check.\n throw new RoutingError(\n `Internal: step \"${step.id}\" task \"${step.task}\" maps to protocol ` +\n `\"${protocol}\" claimed by [${candidates.join(', ')}], no step.agent override. ` +\n `This should have been caught at routing-context build time.`,\n step.task,\n `agents: ${candidates.join(', ')}`\n );\n }\n // Zero candidates — no agent in the map claims this protocol.\n if (options.default_agent) return options.default_agent;\n // Under `discovery_resilient`, an agent that was supposed to claim\n // this protocol may have been excluded by a failed probe. Suffix the\n // error so the operator sees the connection without correlating logs.\n const failedHint =\n ctx.discoveryFailures.length > 0\n ? ` Discovery failed for: ${ctx.discoveryFailures.map(f => `${f.agentKey} (${f.url})`).join(', ')}.`\n : '';\n throw new RoutingError(\n `No agent in the map claims protocol \"${protocol}\" required by tool ` +\n `\"${step.task}\" (step \"${step.id}\"). Available agents: ` +\n `${[...ctx.profiles.keys()].join(', ') || '(none — every agent failed discovery)'}.${failedHint} ` +\n `Add an agent that supports ${protocol}, or set \\`default_agent\\` to fall back.`,\n step.task,\n `protocol ${protocol} unclaimed`\n );\n }\n // Tool not in TASK_FEATURE_MAP (e.g., sync_creatives, comply_test_controller,\n // get_adcp_capabilities post-discovery, future tasks).\n if (options.default_agent) return options.default_agent;\n throw new RoutingError(\n `Tool \"${step.task}\" (step \"${step.id}\") has no specialism mapping ` +\n `(it serves multiple domains or is unrecognized). Set \\`agent: <key>\\` ` +\n `on the step, or set \\`default_agent\\` on the run options.`,\n step.task,\n 'no protocol mapping'\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getOrCreateClient, getOrDiscoverProfile } from "../client.mjs";
|
|
2
2
|
import { TASK_FEATURE_MAP } from "../../utils/capabilities.mjs";
|
|
3
|
+
import { applyNativeA2AComplianceTransportOptions } from "./native-a2a-compliance.mjs";
|
|
3
4
|
const KNOWN_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
4
5
|
"media_buy",
|
|
5
6
|
"signals",
|
|
@@ -14,7 +15,7 @@ function scrubAuthSecrets(text) {
|
|
|
14
15
|
return text.replace(/(authorization\s*:\s*bearer\s+)[A-Za-z0-9._~+/=-]+/gi, "$1[REDACTED]").replace(/\bbearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/([?&]token=)[A-Za-z0-9._~+/=-]+/gi, "$1[REDACTED]");
|
|
15
16
|
}
|
|
16
17
|
function buildAgentOptions(entry, options) {
|
|
17
|
-
return {
|
|
18
|
+
return applyNativeA2AComplianceTransportOptions({
|
|
18
19
|
...options,
|
|
19
20
|
auth: entry.auth ?? options.auth,
|
|
20
21
|
protocol: entry.transport ?? options.protocol,
|
|
@@ -30,7 +31,7 @@ function buildAgentOptions(entry, options) {
|
|
|
30
31
|
_profile: void 0,
|
|
31
32
|
agents: void 0,
|
|
32
33
|
agentTools: void 0
|
|
33
|
-
};
|
|
34
|
+
});
|
|
34
35
|
}
|
|
35
36
|
class RoutingError extends Error {
|
|
36
37
|
constructor(message, task, hint) {
|
|
@@ -213,6 +214,7 @@ function resolveAgentForStep(step, options, ctx) {
|
|
|
213
214
|
export {
|
|
214
215
|
DiscoveryFailure,
|
|
215
216
|
RoutingError,
|
|
217
|
+
buildAgentOptions,
|
|
216
218
|
buildRoutingContext,
|
|
217
219
|
buildRoutingContextFromProfiles,
|
|
218
220
|
resolveAgentForStep
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/testing/storyboard/agent-routing.ts"],"sourcesContent":["/**\n * Per-specialism agent routing for the storyboard runner (#1066).\n *\n * Resolves each step's `task` to one of N agents in\n * `StoryboardRunOptions.agents` so storyboards that span specialisms\n * (e.g., a `signal_marketplace/governance_denied` flow that calls\n * `sync_governance` then `activate_signal`) can hit the tenant that\n * actually owns each tool.\n *\n * Resolution order per step:\n * 1. Explicit `step.agent` (escape hatch for cross-domain tools)\n * 2. `TASK_FEATURE_MAP` → first protocol → unique claimant in the agents map\n * 3. `options.default_agent`\n * 4. Throw `RoutingError`\n *\n * Multi-claim conflicts (two agents claim the same protocol AND a step\n * relying on that protocol lacks an explicit `agent:` override) fail-fast\n * at routing-context build time, BEFORE any non-discovery network calls.\n *\n * ## Webhook receiver topology in routed mode\n *\n * When `StoryboardRunOptions.webhook_receiver` is set alongside `agents`,\n * the receiver is **shared** across all tenants — one HTTP server, one base\n * URL. The runner does not create per-tenant receivers and does not need to:\n * delivery correlation is by **step-keyed URL** (`/step/<step_id>/<op_id>`),\n * not by source agent. If both a governance tenant and a signals tenant\n * emit webhooks during the same run, the assertion harness matches each\n * delivery to its `expect_webhook*` step by URL path, regardless of which\n * tenant posted it. This means:\n *\n * - `{{runner.webhook_base}}` substitutes the same URL for every step\n * across all agents — no per-agent variable needed.\n * - `{{runner.webhook_url:<step_id>}}` is always sufficient to pin a\n * delivery to the step that requested it, even when multiple tenants\n * emit concurrently.\n * - Per-tenant receivers are not supported; if a future storyboard needs\n * delivery-source disambiguation, use distinct step IDs (they already\n * produce distinct URL paths).\n *\n * See `StoryboardRunOptions.webhook_receiver` JSDoc and `webhook-receiver.ts`\n * for implementation details.\n */\nimport type { TestClient } from '../client';\nimport { getOrCreateClient, getOrDiscoverProfile } from '../client';\nimport type { AgentProfile } from '../types';\nimport { TASK_FEATURE_MAP, type AdcpProtocol } from '../../utils/capabilities';\nimport type { AgentEntry, Storyboard, StoryboardRunOptions, StoryboardStep } from './types';\n\n// `compliance_testing` is on the wire as a top-level capability block, NOT\n// in `supported_protocols`. `parseCapabilitiesResponse`\n// (`src/lib/utils/capabilities.ts`) normalizes the block into the\n// `protocols[]` list before `getOrDiscoverProfile` writes it onto the\n// profile, so by the time routing reads `profile.supported_protocols`\n// the value is present. Any future refactor that strips that\n// normalization needs to either preserve it or drop `compliance_testing`\n// from this set.\nconst KNOWN_PROTOCOLS: ReadonlySet<AdcpProtocol> = new Set([\n 'media_buy',\n 'signals',\n 'governance',\n 'creative',\n 'sponsored_intelligence',\n 'trusted_match',\n 'compliance_testing',\n 'brand',\n]);\n\n/**\n * Redact common auth-token patterns before propagating an upstream error\n * message into runner output / CI logs / `StoryboardResult.error`. The\n * concrete leak path: a misconfigured agent that echoes its received\n * `Authorization: Bearer <token>` header in a 401 body would put the\n * caller's own bearer for that tenant into CI artifacts. The bearer is\n * already in the caller's CI environment, so this is parity with single-\n * agent mode rather than a fix for a new leak — but defense-in-depth is\n * cheap and the regex is bounded.\n *\n * Patterns covered:\n * - `Authorization: Bearer <token>` (any case, common in 401 echoes)\n * - `Bearer <token>` standalone\n * - `?token=<value>` / `&token=<value>` query-string tokens\n *\n * Best-effort. Anything unusual (custom-header schemes, base64-encoded\n * blobs in JSON bodies) is not caught — operators should still treat\n * upstream-error CI logs as sensitive.\n */\nfunction scrubAuthSecrets(text: string): string {\n // The character class `[A-Za-z0-9._~+/=-]+` looks like high-entropy\n // content to entropy-based secret scanners (GitGuardian/gitleaks). It\n // is the redaction pattern itself — no secret is encoded here.\n // ggignore\n return text\n .replace(/(authorization\\s*:\\s*bearer\\s+)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]') // ggignore\n .replace(/\\bbearer\\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]') // ggignore\n .replace(/([?&]token=)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]'); // ggignore\n}\n\n/** Per-agent options view: per-entry overrides shadow run-level defaults. */\nfunction buildAgentOptions(entry: AgentEntry, options: StoryboardRunOptions): StoryboardRunOptions {\n return {\n ...options,\n auth: entry.auth ?? options.auth,\n protocol: entry.transport ?? options.protocol,\n // _client, _profile, and agents are run-scoped — never inherit into\n // per-agent views. `_profile` is the load-bearing one: when comply()\n // sets it on the run-level options, `getOrDiscoverProfile` (`client.ts`)\n // short-circuits to that cached profile and skips the actual probe.\n // Without this clear, every per-agent discovery would return\n // comply()'s primary-tenant profile instead of probing the real\n // tenant — silently breaking the protocol-claim index that routing\n // depends on.\n _client: undefined,\n _profile: undefined,\n agents: undefined,\n agentTools: undefined,\n };\n}\n\nexport interface AgentRoutingContext {\n /** Per-agent transport client. Lifecycle owned by the runner. */\n clients: Map<string, TestClient>;\n /** Per-agent discovered profile (`get_adcp_capabilities` result). */\n profiles: Map<string, AgentProfile>;\n /** Protocol → keys of agents that claim it. */\n protocolIndex: Map<AdcpProtocol, string[]>;\n /** Resolved (key → URL) for echo on the storyboard result. */\n agentMap: Record<string, string>;\n /**\n * Agents whose discovery failed when `discovery_resilient: true` was set.\n * Empty in the default (hard-failure) mode — if any agent failed and\n * resilient mode is off, `buildRoutingContext` throws `DiscoveryFailure`\n * instead of returning. The runner echoes this list onto\n * `StoryboardResult.discovery_failures` so operators see the topology\n * breakage even when the storyboard otherwise completes.\n */\n discoveryFailures: DiscoveryFailure[];\n}\n\nexport class RoutingError extends Error {\n constructor(\n message: string,\n public readonly task: string,\n public readonly hint: string\n ) {\n super(message);\n this.name = 'RoutingError';\n }\n}\n\nexport class DiscoveryFailure extends Error {\n constructor(\n message: string,\n public readonly agentKey: string,\n public readonly url: string,\n public readonly underlying: string\n ) {\n super(message);\n this.name = 'DiscoveryFailure';\n }\n}\n\n/**\n * Discover every agent in the map in parallel and build the routing index.\n *\n * Failure modes:\n * - default (`options.discovery_resilient !== true`): any agent's\n * discovery failure → throw `DiscoveryFailure` (caller surfaces as a\n * hard storyboard failure, never a per-step skip). Correct for\n * production multi-tenant flows where every tenant is load-bearing.\n * - resilient (`options.discovery_resilient === true`, #1367): failed\n * agents are excluded from `profiles` + `protocolIndex` and reported\n * on `AgentRoutingContext.discoveryFailures`. Per-step routing then\n * surfaces failures only for storyboards that actually need the\n * broken tenant — unrelated storyboards complete normally.\n * - protocol claimed by 2+ agents AND ≥1 storyboard step that needs it\n * lacks `step.agent` → `RoutingError` (conflict). Independent of\n * resilient mode.\n */\nexport async function buildRoutingContext(\n storyboard: Storyboard,\n options: StoryboardRunOptions\n): Promise<AgentRoutingContext> {\n const agents = options.agents!;\n const entries = Object.entries(agents);\n const resilient = options.discovery_resilient === true;\n\n const clients = new Map<string, TestClient>();\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of entries) {\n const perAgentOptions = buildAgentOptions(entry, options);\n clients.set(key, getOrCreateClient(entry.url, perAgentOptions));\n agentMap[key] = entry.url;\n }\n\n // Parallel discovery — one tenant's slowness does not block another.\n const profiles = new Map<string, AgentProfile>();\n const discoveryFailures: DiscoveryFailure[] = [];\n const discoveryResults = await Promise.all(\n entries.map(async ([key, entry]) => {\n const perAgentOptions = buildAgentOptions(entry, options);\n try {\n const { profile, step } = await getOrDiscoverProfile(clients.get(key)!, perAgentOptions);\n return { key, profile, step };\n } catch (err) {\n return {\n key,\n profile: undefined,\n step: {\n step: 'Discover agent capabilities',\n passed: false,\n error: scrubAuthSecrets((err as Error)?.message || String(err)),\n },\n };\n }\n })\n );\n for (const r of discoveryResults) {\n if (!r.profile || r.step.passed === false) {\n // Bound the attacker-influenced upstream string. A malicious agent\n // could return an arbitrarily large error body during the discovery\n // probe; the unbounded string would flow into\n // `StoryboardResult.discovery_failures[].error` where downstream\n // dashboards / LLM contexts ingest it. 512 chars is enough for a\n // human to read the failure and grep logs for more.\n const rawDetail = scrubAuthSecrets(r.step.error ?? 'Discovery returned no profile.');\n const detail = rawDetail.length > 512 ? `${rawDetail.slice(0, 512)} …(truncated)` : rawDetail;\n // Default mode appends a signposted escape hatch so CI operators\n // discover the resilient flag without grep'ing the JSDoc. Suppressed\n // in resilient mode (the operator already opted in) so the hint\n // doesn't appear in every per-agent failure attached to the result.\n const baseMessage = `Discovery failed for agent \"${r.key}\" (${agents[r.key]!.url}): ${detail}`;\n const message = resilient\n ? baseMessage\n : `${baseMessage}\\n\\nIf this is hello-cluster / exploratory CI and you want unrelated storyboards to complete despite this failure, ` +\n `set \\`StoryboardRunOptions.discovery_resilient: true\\`. Do NOT set it in production — a topology with a broken tenant ` +\n `is a hard misconfiguration there.`;\n const failure = new DiscoveryFailure(message, r.key, agents[r.key]!.url, detail);\n if (!resilient) throw failure;\n discoveryFailures.push(failure);\n continue;\n }\n profiles.set(r.key, r.profile);\n }\n\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n\n return { clients, profiles, protocolIndex, agentMap, discoveryFailures };\n}\n\n/**\n * Test seam: build a routing context from pre-baked profiles instead of\n * running discovery. Lets unit tests exercise conflict detection and\n * per-step resolution without spinning up live agents. Production code\n * uses `buildRoutingContext`; this entry point is exported for the\n * `agent-routing.test.js` harness.\n */\nexport function buildRoutingContextFromProfiles(\n storyboard: Storyboard,\n options: StoryboardRunOptions,\n profiles: Map<string, AgentProfile>\n): AgentRoutingContext {\n const agents = options.agents!;\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of Object.entries(agents)) {\n agentMap[key] = entry.url;\n }\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n // Empty client map — callers that only test routing shouldn't dispatch.\n return { clients: new Map(), profiles, protocolIndex, agentMap, discoveryFailures: [] };\n}\n\n/**\n * Reverse-index `AgentProfile.supported_protocols` so routing can pick an\n * agent by tool→protocol→agent in O(1).\n *\n * Sorts the agent-key list per protocol for deterministic conflict-error\n * output and reproducible tests.\n */\nfunction buildProtocolIndex(profiles: Map<string, AgentProfile>): Map<AdcpProtocol, string[]> {\n const index = new Map<AdcpProtocol, string[]>();\n for (const [key, profile] of profiles) {\n const protocols = profile.supported_protocols ?? [];\n for (const p of protocols) {\n if (!KNOWN_PROTOCOLS.has(p as AdcpProtocol)) continue;\n const proto = p as AdcpProtocol;\n const list = index.get(proto) ?? [];\n list.push(key);\n index.set(proto, list);\n }\n }\n for (const list of index.values()) list.sort();\n return index;\n}\n\n/**\n * Walk the storyboard once and surface any tool→protocol resolution that\n * has 2+ candidate agents AND no explicit `step.agent` override.\n *\n * We could defer this to per-step routing time, but a multi-claim is\n * almost always a topology bug (two tenants accidentally claiming the\n * same protocol) — surfacing it before the first non-discovery network\n * call gives a much better error than a half-run storyboard.\n */\nfunction detectMultiClaimConflicts(storyboard: Storyboard, protocolIndex: Map<AdcpProtocol, string[]>): void {\n const conflicts: Array<{ task: string; protocol: AdcpProtocol; agents: string[]; stepIds: string[] }> = [];\n for (const phase of storyboard.phases ?? []) {\n for (const step of phase.steps ?? []) {\n if (step.agent !== undefined) continue;\n const protocol = primaryProtocolFor(step.task);\n if (!protocol) continue;\n const candidates = protocolIndex.get(protocol);\n if (!candidates || candidates.length < 2) continue;\n const existing = conflicts.find(c => c.task === step.task && c.protocol === protocol);\n if (existing) {\n existing.stepIds.push(step.id);\n } else {\n conflicts.push({\n task: step.task,\n protocol,\n agents: candidates,\n stepIds: [step.id],\n });\n }\n }\n }\n if (conflicts.length === 0) return;\n\n const lines = conflicts.map(\n c =>\n ` - tool \"${c.task}\" (protocol ${c.protocol}) is claimed by [${c.agents.join(', ')}] ` +\n `but step(s) [${c.stepIds.join(', ')}] do not declare \\`agent:\\` override.`\n );\n throw new RoutingError(\n `Routing conflict: multiple agents claim the same protocol and the storyboard ` +\n `does not disambiguate.\\n${lines.join('\\n')}\\n` +\n `Fix: either remove one of the conflicting agents from the map, or add ` +\n `\\`agent: <key>\\` to each affected step.`,\n conflicts[0]!.task,\n `Conflicting agents: ${conflicts[0]!.agents.join(', ')}`\n );\n}\n\n/**\n * First protocol-typed feature in `TASK_FEATURE_MAP[task]`, or `undefined`\n * for tools intentionally unmapped (e.g. `sync_creatives`, which serves\n * both media-buy and creative domains and requires explicit per-step\n * routing in multi-agent mode).\n */\nfunction primaryProtocolFor(task: string): AdcpProtocol | undefined {\n const features = TASK_FEATURE_MAP[task];\n if (!features) return undefined;\n for (const f of features) {\n if (KNOWN_PROTOCOLS.has(f as AdcpProtocol)) return f as AdcpProtocol;\n }\n return undefined;\n}\n\n/**\n * Resolve a step to its target agent key. Throws `RoutingError` when no\n * route can be determined; callers convert to a synthetic step result\n * (see Stage 4 in the runner).\n */\nexport function resolveAgentForStep(\n step: StoryboardStep,\n options: StoryboardRunOptions,\n ctx: AgentRoutingContext\n): string {\n if (step.agent !== undefined) {\n // If the override targets an agent whose discovery failed under\n // resilient mode, surface that explicitly rather than handing\n // dispatch a transport client to a broken tenant (which would\n // otherwise fail at the wire layer with a misleading transport\n // error). Only meaningful when `discoveryFailures` is non-empty;\n // under default (hard-failure) mode `buildRoutingContext` throws\n // before we reach this branch.\n const failed = ctx.discoveryFailures.find(f => f.agentKey === step.agent);\n if (failed) {\n throw new RoutingError(\n `Step \"${step.id}\" targets agent \"${step.agent}\" via \\`step.agent\\` override, ` +\n `but that agent's discovery failed: ${failed.underlying} ` +\n `(${failed.url}). Either remove the override, fix the agent, or ` +\n `route the step to a healthy agent.`,\n step.task,\n `agent \"${step.agent}\" failed discovery`\n );\n }\n return step.agent;\n }\n const protocol = primaryProtocolFor(step.task);\n if (protocol) {\n const candidates = ctx.protocolIndex.get(protocol) ?? [];\n if (candidates.length === 1) return candidates[0]!;\n if (candidates.length > 1) {\n // Conflict already detected at build time, but `step.agent` is\n // unset here. This branch is unreachable in well-formed runs; keep\n // it as a fail-loud sanity check.\n throw new RoutingError(\n `Internal: step \"${step.id}\" task \"${step.task}\" maps to protocol ` +\n `\"${protocol}\" claimed by [${candidates.join(', ')}], no step.agent override. ` +\n `This should have been caught at routing-context build time.`,\n step.task,\n `agents: ${candidates.join(', ')}`\n );\n }\n // Zero candidates — no agent in the map claims this protocol.\n if (options.default_agent) return options.default_agent;\n // Under `discovery_resilient`, an agent that was supposed to claim\n // this protocol may have been excluded by a failed probe. Suffix the\n // error so the operator sees the connection without correlating logs.\n const failedHint =\n ctx.discoveryFailures.length > 0\n ? ` Discovery failed for: ${ctx.discoveryFailures.map(f => `${f.agentKey} (${f.url})`).join(', ')}.`\n : '';\n throw new RoutingError(\n `No agent in the map claims protocol \"${protocol}\" required by tool ` +\n `\"${step.task}\" (step \"${step.id}\"). Available agents: ` +\n `${[...ctx.profiles.keys()].join(', ') || '(none — every agent failed discovery)'}.${failedHint} ` +\n `Add an agent that supports ${protocol}, or set \\`default_agent\\` to fall back.`,\n step.task,\n `protocol ${protocol} unclaimed`\n );\n }\n // Tool not in TASK_FEATURE_MAP (e.g., sync_creatives, comply_test_controller,\n // get_adcp_capabilities post-discovery, future tasks).\n if (options.default_agent) return options.default_agent;\n throw new RoutingError(\n `Tool \"${step.task}\" (step \"${step.id}\") has no specialism mapping ` +\n `(it serves multiple domains or is unrecognized). Set \\`agent: <key>\\` ` +\n `on the step, or set \\`default_agent\\` on the run options.`,\n step.task,\n 'no protocol mapping'\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/testing/storyboard/agent-routing.ts"],"sourcesContent":["/**\n * Per-specialism agent routing for the storyboard runner (#1066).\n *\n * Resolves each step's `task` to one of N agents in\n * `StoryboardRunOptions.agents` so storyboards that span specialisms\n * (e.g., a `signal_marketplace/governance_denied` flow that calls\n * `sync_governance` then `activate_signal`) can hit the tenant that\n * actually owns each tool.\n *\n * Resolution order per step:\n * 1. Explicit `step.agent` (escape hatch for cross-domain tools)\n * 2. `TASK_FEATURE_MAP` → first protocol → unique claimant in the agents map\n * 3. `options.default_agent`\n * 4. Throw `RoutingError`\n *\n * Multi-claim conflicts (two agents claim the same protocol AND a step\n * relying on that protocol lacks an explicit `agent:` override) fail-fast\n * at routing-context build time, BEFORE any non-discovery network calls.\n *\n * ## Webhook receiver topology in routed mode\n *\n * When `StoryboardRunOptions.webhook_receiver` is set alongside `agents`,\n * the receiver is **shared** across all tenants — one HTTP server, one base\n * URL. The runner does not create per-tenant receivers and does not need to:\n * delivery correlation is by **step-keyed URL** (`/step/<step_id>/<op_id>`),\n * not by source agent. If both a governance tenant and a signals tenant\n * emit webhooks during the same run, the assertion harness matches each\n * delivery to its `expect_webhook*` step by URL path, regardless of which\n * tenant posted it. This means:\n *\n * - `{{runner.webhook_base}}` substitutes the same URL for every step\n * across all agents — no per-agent variable needed.\n * - `{{runner.webhook_url:<step_id>}}` is always sufficient to pin a\n * delivery to the step that requested it, even when multiple tenants\n * emit concurrently.\n * - Per-tenant receivers are not supported; if a future storyboard needs\n * delivery-source disambiguation, use distinct step IDs (they already\n * produce distinct URL paths).\n *\n * See `StoryboardRunOptions.webhook_receiver` JSDoc and `webhook-receiver.ts`\n * for implementation details.\n */\nimport type { TestClient } from '../client';\nimport { getOrCreateClient, getOrDiscoverProfile } from '../client';\nimport type { AgentProfile } from '../types';\nimport { TASK_FEATURE_MAP, type AdcpProtocol } from '../../utils/capabilities';\nimport type { AgentEntry, Storyboard, StoryboardRunOptions, StoryboardStep } from './types';\nimport { applyNativeA2AComplianceTransportOptions } from './native-a2a-compliance';\n\n// `compliance_testing` is on the wire as a top-level capability block, NOT\n// in `supported_protocols`. `parseCapabilitiesResponse`\n// (`src/lib/utils/capabilities.ts`) normalizes the block into the\n// `protocols[]` list before `getOrDiscoverProfile` writes it onto the\n// profile, so by the time routing reads `profile.supported_protocols`\n// the value is present. Any future refactor that strips that\n// normalization needs to either preserve it or drop `compliance_testing`\n// from this set.\nconst KNOWN_PROTOCOLS: ReadonlySet<AdcpProtocol> = new Set([\n 'media_buy',\n 'signals',\n 'governance',\n 'creative',\n 'sponsored_intelligence',\n 'trusted_match',\n 'compliance_testing',\n 'brand',\n]);\n\n/**\n * Redact common auth-token patterns before propagating an upstream error\n * message into runner output / CI logs / `StoryboardResult.error`. The\n * concrete leak path: a misconfigured agent that echoes its received\n * `Authorization: Bearer <token>` header in a 401 body would put the\n * caller's own bearer for that tenant into CI artifacts. The bearer is\n * already in the caller's CI environment, so this is parity with single-\n * agent mode rather than a fix for a new leak — but defense-in-depth is\n * cheap and the regex is bounded.\n *\n * Patterns covered:\n * - `Authorization: Bearer <token>` (any case, common in 401 echoes)\n * - `Bearer <token>` standalone\n * - `?token=<value>` / `&token=<value>` query-string tokens\n *\n * Best-effort. Anything unusual (custom-header schemes, base64-encoded\n * blobs in JSON bodies) is not caught — operators should still treat\n * upstream-error CI logs as sensitive.\n */\nfunction scrubAuthSecrets(text: string): string {\n // The character class `[A-Za-z0-9._~+/=-]+` looks like high-entropy\n // content to entropy-based secret scanners (GitGuardian/gitleaks). It\n // is the redaction pattern itself — no secret is encoded here.\n // ggignore\n return text\n .replace(/(authorization\\s*:\\s*bearer\\s+)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]') // ggignore\n .replace(/\\bbearer\\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [REDACTED]') // ggignore\n .replace(/([?&]token=)[A-Za-z0-9._~+/=-]+/gi, '$1[REDACTED]'); // ggignore\n}\n\n/** Per-agent options view: per-entry overrides shadow run-level defaults. */\nexport function buildAgentOptions(entry: AgentEntry, options: StoryboardRunOptions): StoryboardRunOptions {\n return applyNativeA2AComplianceTransportOptions({\n ...options,\n auth: entry.auth ?? options.auth,\n protocol: entry.transport ?? options.protocol,\n // _client, _profile, and agents are run-scoped — never inherit into\n // per-agent views. `_profile` is the load-bearing one: when comply()\n // sets it on the run-level options, `getOrDiscoverProfile` (`client.ts`)\n // short-circuits to that cached profile and skips the actual probe.\n // Without this clear, every per-agent discovery would return\n // comply()'s primary-tenant profile instead of probing the real\n // tenant — silently breaking the protocol-claim index that routing\n // depends on.\n _client: undefined,\n _profile: undefined,\n agents: undefined,\n agentTools: undefined,\n });\n}\n\nexport interface AgentRoutingContext {\n /** Per-agent transport client. Lifecycle owned by the runner. */\n clients: Map<string, TestClient>;\n /** Per-agent discovered profile (`get_adcp_capabilities` result). */\n profiles: Map<string, AgentProfile>;\n /** Protocol → keys of agents that claim it. */\n protocolIndex: Map<AdcpProtocol, string[]>;\n /** Resolved (key → URL) for echo on the storyboard result. */\n agentMap: Record<string, string>;\n /**\n * Agents whose discovery failed when `discovery_resilient: true` was set.\n * Empty in the default (hard-failure) mode — if any agent failed and\n * resilient mode is off, `buildRoutingContext` throws `DiscoveryFailure`\n * instead of returning. The runner echoes this list onto\n * `StoryboardResult.discovery_failures` so operators see the topology\n * breakage even when the storyboard otherwise completes.\n */\n discoveryFailures: DiscoveryFailure[];\n}\n\nexport class RoutingError extends Error {\n constructor(\n message: string,\n public readonly task: string,\n public readonly hint: string\n ) {\n super(message);\n this.name = 'RoutingError';\n }\n}\n\nexport class DiscoveryFailure extends Error {\n constructor(\n message: string,\n public readonly agentKey: string,\n public readonly url: string,\n public readonly underlying: string\n ) {\n super(message);\n this.name = 'DiscoveryFailure';\n }\n}\n\n/**\n * Discover every agent in the map in parallel and build the routing index.\n *\n * Failure modes:\n * - default (`options.discovery_resilient !== true`): any agent's\n * discovery failure → throw `DiscoveryFailure` (caller surfaces as a\n * hard storyboard failure, never a per-step skip). Correct for\n * production multi-tenant flows where every tenant is load-bearing.\n * - resilient (`options.discovery_resilient === true`, #1367): failed\n * agents are excluded from `profiles` + `protocolIndex` and reported\n * on `AgentRoutingContext.discoveryFailures`. Per-step routing then\n * surfaces failures only for storyboards that actually need the\n * broken tenant — unrelated storyboards complete normally.\n * - protocol claimed by 2+ agents AND ≥1 storyboard step that needs it\n * lacks `step.agent` → `RoutingError` (conflict). Independent of\n * resilient mode.\n */\nexport async function buildRoutingContext(\n storyboard: Storyboard,\n options: StoryboardRunOptions\n): Promise<AgentRoutingContext> {\n const agents = options.agents!;\n const entries = Object.entries(agents);\n const resilient = options.discovery_resilient === true;\n\n const clients = new Map<string, TestClient>();\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of entries) {\n const perAgentOptions = buildAgentOptions(entry, options);\n clients.set(key, getOrCreateClient(entry.url, perAgentOptions));\n agentMap[key] = entry.url;\n }\n\n // Parallel discovery — one tenant's slowness does not block another.\n const profiles = new Map<string, AgentProfile>();\n const discoveryFailures: DiscoveryFailure[] = [];\n const discoveryResults = await Promise.all(\n entries.map(async ([key, entry]) => {\n const perAgentOptions = buildAgentOptions(entry, options);\n try {\n const { profile, step } = await getOrDiscoverProfile(clients.get(key)!, perAgentOptions);\n return { key, profile, step };\n } catch (err) {\n return {\n key,\n profile: undefined,\n step: {\n step: 'Discover agent capabilities',\n passed: false,\n error: scrubAuthSecrets((err as Error)?.message || String(err)),\n },\n };\n }\n })\n );\n for (const r of discoveryResults) {\n if (!r.profile || r.step.passed === false) {\n // Bound the attacker-influenced upstream string. A malicious agent\n // could return an arbitrarily large error body during the discovery\n // probe; the unbounded string would flow into\n // `StoryboardResult.discovery_failures[].error` where downstream\n // dashboards / LLM contexts ingest it. 512 chars is enough for a\n // human to read the failure and grep logs for more.\n const rawDetail = scrubAuthSecrets(r.step.error ?? 'Discovery returned no profile.');\n const detail = rawDetail.length > 512 ? `${rawDetail.slice(0, 512)} …(truncated)` : rawDetail;\n // Default mode appends a signposted escape hatch so CI operators\n // discover the resilient flag without grep'ing the JSDoc. Suppressed\n // in resilient mode (the operator already opted in) so the hint\n // doesn't appear in every per-agent failure attached to the result.\n const baseMessage = `Discovery failed for agent \"${r.key}\" (${agents[r.key]!.url}): ${detail}`;\n const message = resilient\n ? baseMessage\n : `${baseMessage}\\n\\nIf this is hello-cluster / exploratory CI and you want unrelated storyboards to complete despite this failure, ` +\n `set \\`StoryboardRunOptions.discovery_resilient: true\\`. Do NOT set it in production — a topology with a broken tenant ` +\n `is a hard misconfiguration there.`;\n const failure = new DiscoveryFailure(message, r.key, agents[r.key]!.url, detail);\n if (!resilient) throw failure;\n discoveryFailures.push(failure);\n continue;\n }\n profiles.set(r.key, r.profile);\n }\n\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n\n return { clients, profiles, protocolIndex, agentMap, discoveryFailures };\n}\n\n/**\n * Test seam: build a routing context from pre-baked profiles instead of\n * running discovery. Lets unit tests exercise conflict detection and\n * per-step resolution without spinning up live agents. Production code\n * uses `buildRoutingContext`; this entry point is exported for the\n * `agent-routing.test.js` harness.\n */\nexport function buildRoutingContextFromProfiles(\n storyboard: Storyboard,\n options: StoryboardRunOptions,\n profiles: Map<string, AgentProfile>\n): AgentRoutingContext {\n const agents = options.agents!;\n const agentMap: Record<string, string> = {};\n for (const [key, entry] of Object.entries(agents)) {\n agentMap[key] = entry.url;\n }\n const protocolIndex = buildProtocolIndex(profiles);\n detectMultiClaimConflicts(storyboard, protocolIndex);\n // Empty client map — callers that only test routing shouldn't dispatch.\n return { clients: new Map(), profiles, protocolIndex, agentMap, discoveryFailures: [] };\n}\n\n/**\n * Reverse-index `AgentProfile.supported_protocols` so routing can pick an\n * agent by tool→protocol→agent in O(1).\n *\n * Sorts the agent-key list per protocol for deterministic conflict-error\n * output and reproducible tests.\n */\nfunction buildProtocolIndex(profiles: Map<string, AgentProfile>): Map<AdcpProtocol, string[]> {\n const index = new Map<AdcpProtocol, string[]>();\n for (const [key, profile] of profiles) {\n const protocols = profile.supported_protocols ?? [];\n for (const p of protocols) {\n if (!KNOWN_PROTOCOLS.has(p as AdcpProtocol)) continue;\n const proto = p as AdcpProtocol;\n const list = index.get(proto) ?? [];\n list.push(key);\n index.set(proto, list);\n }\n }\n for (const list of index.values()) list.sort();\n return index;\n}\n\n/**\n * Walk the storyboard once and surface any tool→protocol resolution that\n * has 2+ candidate agents AND no explicit `step.agent` override.\n *\n * We could defer this to per-step routing time, but a multi-claim is\n * almost always a topology bug (two tenants accidentally claiming the\n * same protocol) — surfacing it before the first non-discovery network\n * call gives a much better error than a half-run storyboard.\n */\nfunction detectMultiClaimConflicts(storyboard: Storyboard, protocolIndex: Map<AdcpProtocol, string[]>): void {\n const conflicts: Array<{ task: string; protocol: AdcpProtocol; agents: string[]; stepIds: string[] }> = [];\n for (const phase of storyboard.phases ?? []) {\n for (const step of phase.steps ?? []) {\n if (step.agent !== undefined) continue;\n const protocol = primaryProtocolFor(step.task);\n if (!protocol) continue;\n const candidates = protocolIndex.get(protocol);\n if (!candidates || candidates.length < 2) continue;\n const existing = conflicts.find(c => c.task === step.task && c.protocol === protocol);\n if (existing) {\n existing.stepIds.push(step.id);\n } else {\n conflicts.push({\n task: step.task,\n protocol,\n agents: candidates,\n stepIds: [step.id],\n });\n }\n }\n }\n if (conflicts.length === 0) return;\n\n const lines = conflicts.map(\n c =>\n ` - tool \"${c.task}\" (protocol ${c.protocol}) is claimed by [${c.agents.join(', ')}] ` +\n `but step(s) [${c.stepIds.join(', ')}] do not declare \\`agent:\\` override.`\n );\n throw new RoutingError(\n `Routing conflict: multiple agents claim the same protocol and the storyboard ` +\n `does not disambiguate.\\n${lines.join('\\n')}\\n` +\n `Fix: either remove one of the conflicting agents from the map, or add ` +\n `\\`agent: <key>\\` to each affected step.`,\n conflicts[0]!.task,\n `Conflicting agents: ${conflicts[0]!.agents.join(', ')}`\n );\n}\n\n/**\n * First protocol-typed feature in `TASK_FEATURE_MAP[task]`, or `undefined`\n * for tools intentionally unmapped (e.g. `sync_creatives`, which serves\n * both media-buy and creative domains and requires explicit per-step\n * routing in multi-agent mode).\n */\nfunction primaryProtocolFor(task: string): AdcpProtocol | undefined {\n const features = TASK_FEATURE_MAP[task];\n if (!features) return undefined;\n for (const f of features) {\n if (KNOWN_PROTOCOLS.has(f as AdcpProtocol)) return f as AdcpProtocol;\n }\n return undefined;\n}\n\n/**\n * Resolve a step to its target agent key. Throws `RoutingError` when no\n * route can be determined; callers convert to a synthetic step result\n * (see Stage 4 in the runner).\n */\nexport function resolveAgentForStep(\n step: StoryboardStep,\n options: StoryboardRunOptions,\n ctx: AgentRoutingContext\n): string {\n if (step.agent !== undefined) {\n // If the override targets an agent whose discovery failed under\n // resilient mode, surface that explicitly rather than handing\n // dispatch a transport client to a broken tenant (which would\n // otherwise fail at the wire layer with a misleading transport\n // error). Only meaningful when `discoveryFailures` is non-empty;\n // under default (hard-failure) mode `buildRoutingContext` throws\n // before we reach this branch.\n const failed = ctx.discoveryFailures.find(f => f.agentKey === step.agent);\n if (failed) {\n throw new RoutingError(\n `Step \"${step.id}\" targets agent \"${step.agent}\" via \\`step.agent\\` override, ` +\n `but that agent's discovery failed: ${failed.underlying} ` +\n `(${failed.url}). Either remove the override, fix the agent, or ` +\n `route the step to a healthy agent.`,\n step.task,\n `agent \"${step.agent}\" failed discovery`\n );\n }\n return step.agent;\n }\n const protocol = primaryProtocolFor(step.task);\n if (protocol) {\n const candidates = ctx.protocolIndex.get(protocol) ?? [];\n if (candidates.length === 1) return candidates[0]!;\n if (candidates.length > 1) {\n // Conflict already detected at build time, but `step.agent` is\n // unset here. This branch is unreachable in well-formed runs; keep\n // it as a fail-loud sanity check.\n throw new RoutingError(\n `Internal: step \"${step.id}\" task \"${step.task}\" maps to protocol ` +\n `\"${protocol}\" claimed by [${candidates.join(', ')}], no step.agent override. ` +\n `This should have been caught at routing-context build time.`,\n step.task,\n `agents: ${candidates.join(', ')}`\n );\n }\n // Zero candidates — no agent in the map claims this protocol.\n if (options.default_agent) return options.default_agent;\n // Under `discovery_resilient`, an agent that was supposed to claim\n // this protocol may have been excluded by a failed probe. Suffix the\n // error so the operator sees the connection without correlating logs.\n const failedHint =\n ctx.discoveryFailures.length > 0\n ? ` Discovery failed for: ${ctx.discoveryFailures.map(f => `${f.agentKey} (${f.url})`).join(', ')}.`\n : '';\n throw new RoutingError(\n `No agent in the map claims protocol \"${protocol}\" required by tool ` +\n `\"${step.task}\" (step \"${step.id}\"). Available agents: ` +\n `${[...ctx.profiles.keys()].join(', ') || '(none — every agent failed discovery)'}.${failedHint} ` +\n `Add an agent that supports ${protocol}, or set \\`default_agent\\` to fall back.`,\n step.task,\n `protocol ${protocol} unclaimed`\n );\n }\n // Tool not in TASK_FEATURE_MAP (e.g., sync_creatives, comply_test_controller,\n // get_adcp_capabilities post-discovery, future tasks).\n if (options.default_agent) return options.default_agent;\n throw new RoutingError(\n `Tool \"${step.task}\" (step \"${step.id}\") has no specialism mapping ` +\n `(it serves multiple domains or is unrecognized). Set \\`agent: <key>\\` ` +\n `on the step, or set \\`default_agent\\` on the run options.`,\n step.task,\n 'no protocol mapping'\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -164,7 +164,7 @@ export interface ResolvedStoryboards {
|
|
|
164
164
|
* Priority:
|
|
165
165
|
* 1. `options.complianceDir` (explicit override, used by tests)
|
|
166
166
|
* 2. `ADCP_COMPLIANCE_DIR` env var (full path including version dir, for packaged consumers)
|
|
167
|
-
* 3. `{package-root}/compliance/cache/{version}` (
|
|
167
|
+
* 3. `{package-root}/compliance/cache/{version}` (development checkout cache)
|
|
168
168
|
*/
|
|
169
169
|
export declare function getComplianceCacheDir(options?: ResolveOptions): string;
|
|
170
170
|
/** Load and parse `index.json` from the compliance cache. */
|
|
@@ -164,7 +164,7 @@ export interface ResolvedStoryboards {
|
|
|
164
164
|
* Priority:
|
|
165
165
|
* 1. `options.complianceDir` (explicit override, used by tests)
|
|
166
166
|
* 2. `ADCP_COMPLIANCE_DIR` env var (full path including version dir, for packaged consumers)
|
|
167
|
-
* 3. `{package-root}/compliance/cache/{version}` (
|
|
167
|
+
* 3. `{package-root}/compliance/cache/{version}` (development checkout cache)
|
|
168
168
|
*/
|
|
169
169
|
export declare function getComplianceCacheDir(options?: ResolveOptions): string;
|
|
170
170
|
/** Load and parse `index.json` from the compliance cache. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../../../src/lib/testing/storyboard/compliance.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../../../src/lib/testing/storyboard/compliance.ts"],"names":[],"mappings":"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"}
|
|
@@ -89,7 +89,10 @@ function readAdcpVersion() {
|
|
|
89
89
|
return import_version.ADCP_VERSION;
|
|
90
90
|
}
|
|
91
91
|
function complianceMissingMessage(what, path) {
|
|
92
|
-
|
|
92
|
+
const cacheRoot = (0, import_path.join)(getRepoRoot(), "compliance", "cache");
|
|
93
|
+
const bundledVersions = (0, import_fs.existsSync)(cacheRoot) ? (0, import_fs.readdirSync)(cacheRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (0, import_fs.existsSync)((0, import_path.join)(cacheRoot, entry.name, "index.json"))).map((entry) => entry.name).sort() : [];
|
|
94
|
+
const bundledText = bundledVersions.length > 0 ? bundledVersions.join(", ") : "none detected";
|
|
95
|
+
return `${what} not found at ${path}. This @adcp/sdk package bundles compliance caches for ${bundledText}, but not every historical patch. For an exact version that is not bundled, pass \`--compliance-dir /path/to/adcp-X.Y.Z/compliance\` and \`--schema-root /path/to/adcp-X.Y.Z/schemas\` from the same protocol release. If developing locally, run \`npm run sync-schemas\` to populate the checkout cache.`;
|
|
93
96
|
}
|
|
94
97
|
function loadComplianceIndex(options = {}) {
|
|
95
98
|
const dir = getComplianceCacheDir(options);
|