From 19a95bef189b29e17b15aef6298db15a4ee1fdb7 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 5 May 2026 08:25:22 +0200 Subject: [PATCH 1/3] Improve target discovery defaults --- packages/agent-cdp/README.md | 27 ++++- packages/agent-cdp/skills/core.md | 11 +- packages/agent-cdp/src/__tests__/cli.test.ts | 7 +- .../agent-cdp/src/__tests__/discovery.test.ts | 90 +++++++++++++- .../src/__tests__/session-manager.test.ts | 34 +++++- packages/agent-cdp/src/cli.ts | 6 +- packages/agent-cdp/src/discovery.ts | 114 +++++++++++++++--- packages/agent-cdp/src/session-manager.ts | 23 +++- 8 files changed, 270 insertions(+), 42 deletions(-) diff --git a/packages/agent-cdp/README.md b/packages/agent-cdp/README.md index a93f366..7715029 100644 --- a/packages/agent-cdp/README.md +++ b/packages/agent-cdp/README.md @@ -6,9 +6,9 @@ | Environment | Notes | |-------------|--------| -| **Chrome / Chromium** | Requires a CDP debug endpoint, typically with remote debugging enabled (for example port `9222`). You point the CLI at the `/json/list` URL for that endpoint. | -| **React Native** | Works with the Metro / dev tooling that exposes a CDP-compatible target list (often `http://127.0.0.1:8081` during development). Same flow as Chrome: `target list` with the dev server URL. | -| **Node.js** | Supports attaching to Node processes started with **`--inspect`** or **`--inspect-brk`** (or the equivalent `NODE_OPTIONS`). They expose the same CDP discovery model as Chrome; point `target list` at the inspector base URL (often `http://127.0.0.1:9229` for the default port, or your `--inspect=host:port` value). | +| **Chrome / Chromium** | Requires a CDP debug endpoint, typically with remote debugging enabled (for example port `9222`). You can point the CLI at that endpoint explicitly, or let `target list` scan the default local ports. | +| **React Native** | Works with the Metro / dev tooling that exposes a CDP-compatible target list (often `http://127.0.0.1:8081` during development). `target list` scans that port by default, or you can pass the dev server URL explicitly. | +| **Node.js** | Supports attaching to Node processes started with **`--inspect`** or **`--inspect-brk`** (or the equivalent `NODE_OPTIONS`). They expose the same CDP discovery model as Chrome; `target list` scans the default inspect port (`http://127.0.0.1:9229`) automatically, or you can pass your `--inspect=host:port` URL explicitly. | Anything that exposes the same style of CDP HTTP discovery (`/json/list`) and WebSocket debugging should work; behavior depends on what the target implements. @@ -45,11 +45,26 @@ agent-cdp status **2. List targets and select one** +By default, `target list` scans these local discovery URLs in parallel: + +- `http://127.0.0.1:9222` +- `http://127.0.0.1:9229` +- `http://127.0.0.1:8081` + +Returned target IDs embed the discovery URL, so `target select ` does not require `--url`. + +Default local scan: + +```sh +agent-cdp target list +agent-cdp target select +``` + Chrome (example port): ```sh agent-cdp target list --url http://127.0.0.1:9222 -agent-cdp target select --url http://127.0.0.1:9222 +agent-cdp target select ``` React Native (example Metro URL): @@ -62,9 +77,11 @@ Node.js (example default inspect port after starting your app with `node --inspe ```sh agent-cdp target list --url http://127.0.0.1:9229 -agent-cdp target select --url http://127.0.0.1:9229 +agent-cdp target select ``` +If you pass `--url` to `target select`, it must match the discovery URL encoded in the target ID. + Clear the current selection when needed: ```sh diff --git a/packages/agent-cdp/skills/core.md b/packages/agent-cdp/skills/core.md index e47902d..e37e156 100644 --- a/packages/agent-cdp/skills/core.md +++ b/packages/agent-cdp/skills/core.md @@ -39,16 +39,19 @@ Always start the daemon before any other commands. `start` is idempotent. A "target" is a Chrome tab or Node.js process exposing a CDP endpoint. ```bash +agent-cdp target list # scan default local CDP URLs (9222, 9229, 8081) agent-cdp target list --url http://localhost:9229 # list targets for a Node.js process agent-cdp target list --url http://localhost:9222 # list targets for Chrome agent-cdp target list --url http://localhost:8081 # list targets for React Native (Metro) -agent-cdp target select --url URL # select a specific target +agent-cdp target select # select a specific target using the URL encoded in the id +agent-cdp target select --url URL # optional URL consistency check agent-cdp target clear # deselect the current target ``` The `--url` flag is the CDP discovery URL (the `--inspect` address for Node.js, -or Chrome's remote debugging port). After `target select`, subsequent commands -use that target automatically. +or Chrome's remote debugging port). When omitted, `target list` scans the local +default URLs and encodes the discovery URL into each target id. After +`target select`, subsequent commands use that target automatically. ### React Native @@ -56,7 +59,7 @@ React Native apps expose a CDP endpoint through the Metro bundler on port 8081. ```bash agent-cdp target list --url http://localhost:8081 -agent-cdp target select --url http://localhost:8081 +agent-cdp target select ``` Requirements: diff --git a/packages/agent-cdp/src/__tests__/cli.test.ts b/packages/agent-cdp/src/__tests__/cli.test.ts index 9bcb648..828148d 100644 --- a/packages/agent-cdp/src/__tests__/cli.test.ts +++ b/packages/agent-cdp/src/__tests__/cli.test.ts @@ -9,13 +9,18 @@ describe("cli", () => { url: "http://127.0.0.1:9222", }, }); + expect(parseArgs(["target", "select", "chrome|MTI3LjAuMC4xOjkyMjI|page-1"])).toEqual({ + command: ["target", "select", "chrome|MTI3LjAuMC4xOjkyMjI|page-1"], + flags: {}, + }); }); it("prints the available daemon commands", () => { expect(usage()).toContain("start"); expect(usage()).toContain("status"); expect(usage()).toContain("stop"); - expect(usage()).toContain("target list"); + expect(usage()).toContain("target list [--url URL]"); + expect(usage()).toContain("target select [--url URL]"); expect(usage()).toContain("js-allocation start"); expect(usage()).toContain("js-allocation-timeline start"); }); diff --git a/packages/agent-cdp/src/__tests__/discovery.test.ts b/packages/agent-cdp/src/__tests__/discovery.test.ts index d3fa36d..d6d0e6d 100644 --- a/packages/agent-cdp/src/__tests__/discovery.test.ts +++ b/packages/agent-cdp/src/__tests__/discovery.test.ts @@ -1,12 +1,48 @@ -import { buildTargetId, getDiscoveryUrl, mapChromeTarget, mapReactNativeTarget } from "../discovery.js"; +import { + buildTargetId, + decodeTargetSource, + DEFAULT_DISCOVERY_URLS, + discoverTargets, + encodeTargetSource, + getDiscoveryUrl, + getDiscoveryUrls, + mapChromeTarget, + mapReactNativeTarget, + parseTargetId, +} from "../discovery.js"; describe("discovery helpers", () => { it("builds deterministic target ids", () => { - expect(buildTargetId("chrome", "page-1")).toBe("chrome:page-1"); + expect(buildTargetId("chrome", "http://127.0.0.1:9222", "page-1")).toBe( + "chrome|MTI3LjAuMC4xOjkyMjI|page-1", + ); + }); + + it("strips only http scheme when encoding the source", () => { + expect(encodeTargetSource("http://127.0.0.1:9222")).toBe("MTI3LjAuMC4xOjkyMjI"); + expect(decodeTargetSource("MTI3LjAuMC4xOjkyMjI")).toBe("http://127.0.0.1:9222"); + }); + + it("preserves non-http schemes when encoding the source", () => { + const encoded = encodeTargetSource("https://example.test:8443/devtools?foo=1"); + expect(decodeTargetSource(encoded)).toBe("https://example.test:8443/devtools?foo=1"); + }); + + it("parses target ids back to their source url", () => { + expect(parseTargetId("chrome|MTI3LjAuMC4xOjkyMjI|page-1")).toEqual({ + kind: "chrome", + encodedSource: "MTI3LjAuMC4xOjkyMjI", + rawId: "page-1", + sourceUrl: "http://127.0.0.1:9222", + }); }); it("maps the configured discovery url", () => { - expect(getDiscoveryUrl({ url: "http://127.0.0.1:9222/" })).toBe("http://127.0.0.1:9222"); + expect(getDiscoveryUrl({ url: "127.0.0.1:9222/" })).toBe("http://127.0.0.1:9222"); + }); + + it("returns default discovery urls when none are configured", () => { + expect(getDiscoveryUrls({})).toEqual([...DEFAULT_DISCOVERY_URLS]); }); it("maps chrome targets", () => { @@ -18,6 +54,7 @@ describe("discovery helpers", () => { webSocketDebuggerUrl: "ws://127.0.0.1:9222/devtools/page/1", }), ).toMatchObject({ + id: "chrome|MTI3LjAuMC4xOjkyMjI|page-1", rawId: "page-1", kind: "chrome", title: "Example", @@ -39,6 +76,7 @@ describe("discovery helpers", () => { }, }), ).toMatchObject({ + id: "react-native|MTI3LjAuMC4xOjgwODE|device-page", kind: "react-native", appId: "com.example.app", reactNative: { @@ -46,4 +84,50 @@ describe("discovery helpers", () => { }, }); }); + + it("keeps ids unique across different source urls", () => { + expect(buildTargetId("chrome", "http://127.0.0.1:9222", "page-1")).not.toBe( + buildTargetId("chrome", "http://127.0.0.1:9229", "page-1"), + ); + }); + + it("merges successful targets across default discovery urls", async () => { + const fetchMock = vi + .fn() + .mockResolvedValueOnce( + new Response( + JSON.stringify([{ id: "page-1", title: "Chrome", webSocketDebuggerUrl: "ws://127.0.0.1:9222/devtools/page/1" }]), + ), + ) + .mockRejectedValueOnce(new Error("connect ECONNREFUSED")) + .mockResolvedValueOnce( + new Response( + JSON.stringify([ + { + id: "device-page", + title: "React Native Experimental", + appId: "com.example.app", + webSocketDebuggerUrl: "ws://127.0.0.1:8081/inspector/debug?page=1", + reactNative: { logicalDeviceId: "device-1", capabilities: {} }, + }, + ]), + ), + ); + + vi.stubGlobal("fetch", fetchMock); + + await expect(discoverTargets({})).resolves.toMatchObject([ + { id: "chrome|MTI3LjAuMC4xOjkyMjI|page-1", kind: "chrome" }, + { id: "react-native|MTI3LjAuMC4xOjgwODE|device-page", kind: "react-native" }, + ]); + expect(fetchMock).toHaveBeenCalledTimes(3); + }); + + it("throws for explicit discovery url failures", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(null, { status: 500 }))); + + await expect(discoverTargets({ url: "127.0.0.1:9222" })).rejects.toThrow( + "Target discovery failed for http://127.0.0.1:9222: HTTP 500", + ); + }); }); diff --git a/packages/agent-cdp/src/__tests__/session-manager.test.ts b/packages/agent-cdp/src/__tests__/session-manager.test.ts index c8b59d0..96f5c5e 100644 --- a/packages/agent-cdp/src/__tests__/session-manager.test.ts +++ b/packages/agent-cdp/src/__tests__/session-manager.test.ts @@ -1,6 +1,9 @@ import { SessionManager } from "../session-manager.js"; import type { CdpEventMessage, CdpTransport, TargetDescriptor, TargetProvider } from "../types.js"; +const CHROME_TEST_ID = "chrome|ZXhhbXBsZS50ZXN0|page-1"; +const REACT_NATIVE_TEST_ID = "react-native|ZXhhbXBsZS50ZXN0|page-1"; + class FakeTransport implements CdpTransport { connected = false; @@ -39,7 +42,7 @@ describe("SessionManager", () => { it("lists targets from configured providers", async () => { const targets = [ { - id: "chrome:test:page-1", + id: CHROME_TEST_ID, rawId: "page-1", title: "Example", kind: "chrome" as const, @@ -55,7 +58,7 @@ describe("SessionManager", () => { it("selects and clears a target", async () => { const targets = [ { - id: "chrome:test:page-1", + id: CHROME_TEST_ID, rawId: "page-1", title: "Example", kind: "chrome" as const, @@ -65,7 +68,7 @@ describe("SessionManager", () => { }, ]; const manager = new SessionManager([new FakeProvider()], () => Promise.resolve(targets)); - await expect(manager.selectTarget("chrome:test:page-1", { url: "http://example.test" })).resolves.toMatchObject({ + await expect(manager.selectTarget(CHROME_TEST_ID, {})).resolves.toMatchObject({ title: "Example", }); expect(manager.getSessionState()).toBe("connected"); @@ -74,6 +77,25 @@ describe("SessionManager", () => { expect(manager.getSessionState()).toBe("disconnected"); }); + it("rejects mismatched explicit urls when selecting a target", async () => { + const targets = [ + { + id: CHROME_TEST_ID, + rawId: "page-1", + title: "Example", + kind: "chrome" as const, + description: "Test page", + webSocketDebuggerUrl: "ws://example.test/devtools/page/1", + sourceUrl: "http://example.test", + }, + ]; + const manager = new SessionManager([new FakeProvider()], () => Promise.resolve(targets)); + + await expect(manager.selectTarget(CHROME_TEST_ID, { url: "http://other.test" })).rejects.toThrow( + `Target id source does not match --url: ${CHROME_TEST_ID}`, + ); + }); + it("reconnects react native targets by logical device id", async () => { class FakeReactNativeProvider implements TargetProvider { readonly kind = "react-native" as const; @@ -83,7 +105,7 @@ describe("SessionManager", () => { this.attempt += 1; return [ { - id: `react-native:test:page-${this.attempt}`, + id: `react-native|ZXhhbXBsZS50ZXN0|page-${this.attempt}`, rawId: `page-${this.attempt}`, title: "React Native Experimental", kind: "react-native", @@ -113,7 +135,7 @@ describe("SessionManager", () => { attempt += 1; return Promise.resolve([ { - id: `react-native:test:page-${attempt}`, + id: `react-native|ZXhhbXBsZS50ZXN0|page-${attempt}`, rawId: `page-${attempt}`, title: "React Native Experimental", kind: "react-native" as const, @@ -133,7 +155,7 @@ describe("SessionManager", () => { })(); const manager = new SessionManager([new FakeReactNativeProvider()], discoverTargetsImpl); - await manager.selectTarget("react-native:test:page-1", { url: "http://example.test" }); + await manager.selectTarget(REACT_NATIVE_TEST_ID, {}); const session = manager.getSession(); if (!session) { throw new Error("Expected session to exist"); diff --git a/packages/agent-cdp/src/cli.ts b/packages/agent-cdp/src/cli.ts index 184944f..931ba80 100644 --- a/packages/agent-cdp/src/cli.ts +++ b/packages/agent-cdp/src/cli.ts @@ -84,8 +84,8 @@ Daemon: status Show daemon status Targets: - target list --url URL - target select --url URL + target list [--url URL] + target select [--url URL] target clear Console: @@ -308,7 +308,7 @@ export async function main(): Promise { if (cmd === "target" && command[1] === "select") { const targetId = command[2]; if (!targetId) { - throw new Error("Usage: agent-cdp target select --url URL"); + throw new Error("Usage: agent-cdp target select [--url URL]"); } await ensureDaemon(); const response = await sendCommand({ diff --git a/packages/agent-cdp/src/discovery.ts b/packages/agent-cdp/src/discovery.ts index 9fbf92e..78bddf2 100644 --- a/packages/agent-cdp/src/discovery.ts +++ b/packages/agent-cdp/src/discovery.ts @@ -1,5 +1,11 @@ import type { DiscoveryOptions, TargetDescriptor } from "./types.js"; +export const DEFAULT_DISCOVERY_URLS = [ + "http://127.0.0.1:9222", + "http://127.0.0.1:9229", + "http://127.0.0.1:8081", +] as const; + interface ChromeJsonTarget { id: string; title?: string; @@ -25,12 +31,69 @@ export function normalizeBaseUrl(url: string): string { return url.endsWith("/") ? url.slice(0, -1) : url; } -export function buildTargetId(kind: TargetDescriptor["kind"], rawId: string): string { - return `${kind}:${rawId}`; +export function normalizeDiscoveryUrl(url: string): string { + const normalized = normalizeBaseUrl(url.trim()); + return /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(normalized) ? normalized : `http://${normalized}`; +} + +export function encodeTargetSource(url: string): string { + const normalized = normalizeDiscoveryUrl(url); + const value = normalized.startsWith("http://") ? normalized.slice("http://".length) : normalized; + return Buffer.from(value, "utf8").toString("base64url"); +} + +export function decodeTargetSource(source: string): string { + const decoded = Buffer.from(source, "base64url").toString("utf8"); + return decoded.includes("://") ? decoded : `http://${decoded}`; +} + +export function buildTargetId(kind: TargetDescriptor["kind"], sourceUrl: string, rawId: string): string { + return `${kind}|${encodeTargetSource(sourceUrl)}|${rawId}`; +} + +export function parseTargetId(id: string): { + kind: TargetDescriptor["kind"]; + encodedSource: string; + rawId: string; + sourceUrl: string; +} { + const firstSeparator = id.indexOf("|"); + const secondSeparator = id.indexOf("|", firstSeparator + 1); + if (firstSeparator <= 0 || secondSeparator <= firstSeparator + 1 || secondSeparator === id.length - 1) { + throw new Error(`Invalid target id: ${id}`); + } + + const kind = id.slice(0, firstSeparator); + if (kind !== "chrome" && kind !== "react-native") { + throw new Error(`Invalid target id: ${id}`); + } + + const encodedSource = id.slice(firstSeparator + 1, secondSeparator); + const rawId = id.slice(secondSeparator + 1); + + try { + return { + kind, + encodedSource, + rawId, + sourceUrl: normalizeDiscoveryUrl(decodeTargetSource(encodedSource)), + }; + } catch { + throw new Error(`Invalid target id: ${id}`); + } } export function getDiscoveryUrl(options: DiscoveryOptions): string | null { - return options.url ? normalizeBaseUrl(options.url) : null; + return options.url ? normalizeDiscoveryUrl(options.url) : null; +} + +export function getDiscoveryUrls(options: DiscoveryOptions): string[] { + const explicitUrl = getDiscoveryUrl(options); + if (explicitUrl) { + return [explicitUrl]; + } + + return [...DEFAULT_DISCOVERY_URLS]; } export function mapChromeTarget(sourceUrl: string, target: ChromeJsonTarget): TargetDescriptor | null { @@ -39,7 +102,7 @@ export function mapChromeTarget(sourceUrl: string, target: ChromeJsonTarget): Ta } return { - id: buildTargetId("chrome", target.id), + id: buildTargetId("chrome", sourceUrl, target.id), rawId: target.id, title: target.title || target.id, kind: "chrome", @@ -56,7 +119,7 @@ export function mapReactNativeTarget(sourceUrl: string, target: ReactNativeJsonT } return { - id: buildTargetId("react-native", target.id), + id: buildTargetId("react-native", sourceUrl, target.id), rawId: target.id, title: target.title || target.id, kind: "react-native", @@ -84,19 +147,36 @@ export async function fetchJsonTargets(baseUrl: string): Promise { } export async function discoverTargets(options: DiscoveryOptions): Promise { - const url = getDiscoveryUrl(options); - if (!url) { - return []; + const urls = getDiscoveryUrls(options); + if (options.url) { + const targets = await fetchJsonTargets(urls[0]); + return targets + .map((target) => { + if (target.reactNative) { + return mapReactNativeTarget(urls[0], target); + } + + return mapChromeTarget(urls[0], target); + }) + .filter((target): target is TargetDescriptor => target !== null); } - const targets = await fetchJsonTargets(url); - return targets - .map((target) => { - if (target.reactNative) { - return mapReactNativeTarget(url, target); - } + const results = await Promise.allSettled(urls.map((url) => fetchJsonTargets(url))); + + return results.flatMap((result, index) => { + if (result.status !== "fulfilled") { + return []; + } + + const url = urls[index]; + return result.value + .map((target) => { + if (target.reactNative) { + return mapReactNativeTarget(url, target); + } - return mapChromeTarget(url, target); - }) - .filter((target): target is TargetDescriptor => target !== null); + return mapChromeTarget(url, target); + }) + .filter((target): target is TargetDescriptor => target !== null); + }); } diff --git a/packages/agent-cdp/src/session-manager.ts b/packages/agent-cdp/src/session-manager.ts index fb911e2..f3b6654 100644 --- a/packages/agent-cdp/src/session-manager.ts +++ b/packages/agent-cdp/src/session-manager.ts @@ -6,7 +6,7 @@ import type { TargetDescriptor, TargetProvider, } from "./types.js"; -import { discoverTargets } from "./discovery.js"; +import { discoverTargets, normalizeDiscoveryUrl, parseTargetId } from "./discovery.js"; export class PersistentRuntimeSession implements RuntimeSession { constructor( @@ -38,7 +38,8 @@ export class SessionManager { } async selectTarget(targetId: string, options: DiscoveryOptions): Promise { - const targets = await this.listTargets(options); + const resolvedOptions = this.resolveSelectionOptions(targetId, options); + const targets = await this.listTargets(resolvedOptions); const target = targets.find((candidate) => candidate.id === targetId); if (!target) { throw new Error(`Target not found: ${targetId}`); @@ -58,7 +59,7 @@ export class SessionManager { await session.ensureConnected(); this.session = session; this.sessionState = "connected"; - this.selectedOptions = options; + this.selectedOptions = resolvedOptions; return target; } catch (error) { this.sessionState = "disconnected"; @@ -149,4 +150,20 @@ export class SessionManager { }) || null ); } + + private resolveSelectionOptions(targetId: string, options: DiscoveryOptions): DiscoveryOptions { + const parsedTarget = parseTargetId(targetId); + const resolvedUrl = parsedTarget.sourceUrl; + + if (!options.url) { + return { url: resolvedUrl }; + } + + const normalizedOptionUrl = normalizeDiscoveryUrl(options.url); + if (normalizedOptionUrl !== resolvedUrl) { + throw new Error(`Target id source does not match --url: ${targetId}`); + } + + return { url: normalizedOptionUrl }; + } } From 2c493d8e8f050257d37200a98a3efb8d708fe9f2 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 5 May 2026 08:59:03 +0200 Subject: [PATCH 2/3] fix target ids with shell-safe separator --- packages/agent-cdp/src/__tests__/cli.test.ts | 4 ++-- packages/agent-cdp/src/__tests__/discovery.test.ts | 12 ++++++------ .../agent-cdp/src/__tests__/session-manager.test.ts | 8 ++++---- packages/agent-cdp/src/discovery.ts | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/agent-cdp/src/__tests__/cli.test.ts b/packages/agent-cdp/src/__tests__/cli.test.ts index 828148d..8accb18 100644 --- a/packages/agent-cdp/src/__tests__/cli.test.ts +++ b/packages/agent-cdp/src/__tests__/cli.test.ts @@ -9,8 +9,8 @@ describe("cli", () => { url: "http://127.0.0.1:9222", }, }); - expect(parseArgs(["target", "select", "chrome|MTI3LjAuMC4xOjkyMjI|page-1"])).toEqual({ - command: ["target", "select", "chrome|MTI3LjAuMC4xOjkyMjI|page-1"], + expect(parseArgs(["target", "select", "chrome:MTI3LjAuMC4xOjkyMjI:page-1"])).toEqual({ + command: ["target", "select", "chrome:MTI3LjAuMC4xOjkyMjI:page-1"], flags: {}, }); }); diff --git a/packages/agent-cdp/src/__tests__/discovery.test.ts b/packages/agent-cdp/src/__tests__/discovery.test.ts index d6d0e6d..81919a2 100644 --- a/packages/agent-cdp/src/__tests__/discovery.test.ts +++ b/packages/agent-cdp/src/__tests__/discovery.test.ts @@ -14,7 +14,7 @@ import { describe("discovery helpers", () => { it("builds deterministic target ids", () => { expect(buildTargetId("chrome", "http://127.0.0.1:9222", "page-1")).toBe( - "chrome|MTI3LjAuMC4xOjkyMjI|page-1", + "chrome:MTI3LjAuMC4xOjkyMjI:page-1", ); }); @@ -29,7 +29,7 @@ describe("discovery helpers", () => { }); it("parses target ids back to their source url", () => { - expect(parseTargetId("chrome|MTI3LjAuMC4xOjkyMjI|page-1")).toEqual({ + expect(parseTargetId("chrome:MTI3LjAuMC4xOjkyMjI:page-1")).toEqual({ kind: "chrome", encodedSource: "MTI3LjAuMC4xOjkyMjI", rawId: "page-1", @@ -54,7 +54,7 @@ describe("discovery helpers", () => { webSocketDebuggerUrl: "ws://127.0.0.1:9222/devtools/page/1", }), ).toMatchObject({ - id: "chrome|MTI3LjAuMC4xOjkyMjI|page-1", + id: "chrome:MTI3LjAuMC4xOjkyMjI:page-1", rawId: "page-1", kind: "chrome", title: "Example", @@ -76,7 +76,7 @@ describe("discovery helpers", () => { }, }), ).toMatchObject({ - id: "react-native|MTI3LjAuMC4xOjgwODE|device-page", + id: "react-native:MTI3LjAuMC4xOjgwODE:device-page", kind: "react-native", appId: "com.example.app", reactNative: { @@ -117,8 +117,8 @@ describe("discovery helpers", () => { vi.stubGlobal("fetch", fetchMock); await expect(discoverTargets({})).resolves.toMatchObject([ - { id: "chrome|MTI3LjAuMC4xOjkyMjI|page-1", kind: "chrome" }, - { id: "react-native|MTI3LjAuMC4xOjgwODE|device-page", kind: "react-native" }, + { id: "chrome:MTI3LjAuMC4xOjkyMjI:page-1", kind: "chrome" }, + { id: "react-native:MTI3LjAuMC4xOjgwODE:device-page", kind: "react-native" }, ]); expect(fetchMock).toHaveBeenCalledTimes(3); }); diff --git a/packages/agent-cdp/src/__tests__/session-manager.test.ts b/packages/agent-cdp/src/__tests__/session-manager.test.ts index 96f5c5e..2b35310 100644 --- a/packages/agent-cdp/src/__tests__/session-manager.test.ts +++ b/packages/agent-cdp/src/__tests__/session-manager.test.ts @@ -1,8 +1,8 @@ import { SessionManager } from "../session-manager.js"; import type { CdpEventMessage, CdpTransport, TargetDescriptor, TargetProvider } from "../types.js"; -const CHROME_TEST_ID = "chrome|ZXhhbXBsZS50ZXN0|page-1"; -const REACT_NATIVE_TEST_ID = "react-native|ZXhhbXBsZS50ZXN0|page-1"; +const CHROME_TEST_ID = "chrome:ZXhhbXBsZS50ZXN0:page-1"; +const REACT_NATIVE_TEST_ID = "react-native:ZXhhbXBsZS50ZXN0:page-1"; class FakeTransport implements CdpTransport { connected = false; @@ -105,7 +105,7 @@ describe("SessionManager", () => { this.attempt += 1; return [ { - id: `react-native|ZXhhbXBsZS50ZXN0|page-${this.attempt}`, + id: `react-native:ZXhhbXBsZS50ZXN0:page-${this.attempt}`, rawId: `page-${this.attempt}`, title: "React Native Experimental", kind: "react-native", @@ -135,7 +135,7 @@ describe("SessionManager", () => { attempt += 1; return Promise.resolve([ { - id: `react-native|ZXhhbXBsZS50ZXN0|page-${attempt}`, + id: `react-native:ZXhhbXBsZS50ZXN0:page-${attempt}`, rawId: `page-${attempt}`, title: "React Native Experimental", kind: "react-native" as const, diff --git a/packages/agent-cdp/src/discovery.ts b/packages/agent-cdp/src/discovery.ts index 78bddf2..87d44bd 100644 --- a/packages/agent-cdp/src/discovery.ts +++ b/packages/agent-cdp/src/discovery.ts @@ -48,7 +48,7 @@ export function decodeTargetSource(source: string): string { } export function buildTargetId(kind: TargetDescriptor["kind"], sourceUrl: string, rawId: string): string { - return `${kind}|${encodeTargetSource(sourceUrl)}|${rawId}`; + return `${kind}:${encodeTargetSource(sourceUrl)}:${rawId}`; } export function parseTargetId(id: string): { @@ -57,8 +57,8 @@ export function parseTargetId(id: string): { rawId: string; sourceUrl: string; } { - const firstSeparator = id.indexOf("|"); - const secondSeparator = id.indexOf("|", firstSeparator + 1); + const firstSeparator = id.indexOf(":"); + const secondSeparator = id.indexOf(":", firstSeparator + 1); if (firstSeparator <= 0 || secondSeparator <= firstSeparator + 1 || secondSeparator === id.length - 1) { throw new Error(`Invalid target id: ${id}`); } From cf15978a262460ba230bdb065ee60e5d93732e3d Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 5 May 2026 09:03:17 +0200 Subject: [PATCH 3/3] chore: remove package manager pin --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 53b3e35..38f7ee1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "agent-cdp-root", "private": true, "license": "MIT", - "packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a", "workspaces": [ "packages/*" ],