mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
feat: add cdp command agent-cdp passthrough (#873)
* feat: add agent-cdp passthrough * docs: narrow agent-cdp memory guidance * chore: pin agent-cdp 1.6.0 * test: cover agent-cdp guidance * fix: preserve agent-cdp passthrough flags * fix: expose CDP wrapper as cdp * docs: move CDP workflow to debugging guide * docs: mention cdp in command reference
This commit is contained in:
committed by
GitHub
parent
8c49d54ff1
commit
be1e1c9931
@@ -27,6 +27,7 @@ Escalate only when relevant:
|
||||
agent-device help debugging
|
||||
agent-device help react-native
|
||||
agent-device help react-devtools
|
||||
agent-device help cdp
|
||||
agent-device help remote
|
||||
agent-device help macos
|
||||
agent-device help dogfood
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import fs from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { afterEach, test, vi } from 'vitest';
|
||||
|
||||
vi.mock('../utils/exec.ts', () => ({
|
||||
runCmdStreaming: vi.fn(),
|
||||
}));
|
||||
|
||||
import { runCmdStreaming } from '../utils/exec.ts';
|
||||
import {
|
||||
AGENT_CDP_PACKAGE,
|
||||
buildAgentCdpNpmExecArgs,
|
||||
runAgentCdpCommand,
|
||||
} from '../cli/commands/agent-cdp.ts';
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('cdp wrapper pins agent-cdp package version', () => {
|
||||
assert.equal(AGENT_CDP_PACKAGE, 'agent-cdp@1.6.0');
|
||||
assert.deepEqual(
|
||||
buildAgentCdpNpmExecArgs(['memory', 'usage', 'sample', '--label', 'baseline', '--gc']),
|
||||
[
|
||||
'exec',
|
||||
'--yes',
|
||||
'--package',
|
||||
'agent-cdp@1.6.0',
|
||||
'--',
|
||||
'agent-cdp',
|
||||
'memory',
|
||||
'usage',
|
||||
'sample',
|
||||
'--label',
|
||||
'baseline',
|
||||
'--gc',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('cdp docs hide the implementation package name', () => {
|
||||
assert.doesNotMatch(fs.readFileSync('website/docs/docs/commands.md', 'utf8'), /agent-cdp/);
|
||||
assert.doesNotMatch(
|
||||
fs.readFileSync('website/docs/docs/debugging-profiling.md', 'utf8'),
|
||||
/agent-cdp/,
|
||||
);
|
||||
});
|
||||
|
||||
test('cdp workflow docs live in debugging and profiling guide', () => {
|
||||
assert.match(
|
||||
fs.readFileSync('website/docs/docs/commands.md', 'utf8'),
|
||||
/agent-device cdp memory usage sample --label baseline --gc/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
fs.readFileSync('website/docs/docs/commands.md', 'utf8'),
|
||||
/React Native JS memory through CDP/,
|
||||
);
|
||||
assert.match(
|
||||
fs.readFileSync('website/docs/docs/debugging-profiling.md', 'utf8'),
|
||||
/React Native JS memory through CDP/,
|
||||
);
|
||||
});
|
||||
|
||||
test('cdp wrapper streams through npm exec and returns downstream exit code', async () => {
|
||||
const env = { ...process.env };
|
||||
vi.mocked(runCmdStreaming).mockResolvedValueOnce({
|
||||
exitCode: 7,
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
});
|
||||
|
||||
const exitCode = await runAgentCdpCommand(['target', 'list'], {
|
||||
cwd: '/tmp/project',
|
||||
env,
|
||||
});
|
||||
|
||||
assert.equal(exitCode, 7);
|
||||
assert.equal(vi.mocked(runCmdStreaming).mock.calls[0]?.[0], 'npm');
|
||||
assert.deepEqual(vi.mocked(runCmdStreaming).mock.calls[0]?.[1], [
|
||||
'exec',
|
||||
'--yes',
|
||||
'--package',
|
||||
'agent-cdp@1.6.0',
|
||||
'--',
|
||||
'agent-cdp',
|
||||
'target',
|
||||
'list',
|
||||
]);
|
||||
assert.equal(vi.mocked(runCmdStreaming).mock.calls[0]?.[2]?.cwd, '/tmp/project');
|
||||
assert.equal(vi.mocked(runCmdStreaming).mock.calls[0]?.[2]?.env, env);
|
||||
assert.equal(vi.mocked(runCmdStreaming).mock.calls[0]?.[2]?.allowFailure, true);
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from './client.ts';
|
||||
import { materializeRemoteConnectionForCommand } from './cli/commands/connection-runtime.ts';
|
||||
import { tryRunClientBackedCommand } from './cli/commands/router.ts';
|
||||
import { runAgentCdpCommand } from './cli/commands/agent-cdp.ts';
|
||||
import { runReactDevtoolsCommand } from './cli/commands/react-devtools.ts';
|
||||
import { runWebCommand } from './cli/commands/web.ts';
|
||||
import { readCliBatchStepsJson } from './cli/batch-steps.ts';
|
||||
@@ -196,6 +197,14 @@ export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS):
|
||||
}
|
||||
let logTailStopper: (() => void) | null = null;
|
||||
try {
|
||||
if (command === 'cdp') {
|
||||
const exitCode = await runAgentCdpCommand(positionals, {
|
||||
cwd: process.cwd(),
|
||||
env: process.env,
|
||||
});
|
||||
process.exit(exitCode);
|
||||
return;
|
||||
}
|
||||
if (command === 'react-devtools') {
|
||||
const exitCode = await runReactDevtoolsCommand(positionals, {
|
||||
flags: effectiveFlags,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { runCmdStreaming } from '../../utils/exec.ts';
|
||||
|
||||
const AGENT_CDP_VERSION = '1.6.0';
|
||||
export const AGENT_CDP_PACKAGE = `agent-cdp@${AGENT_CDP_VERSION}`;
|
||||
const AGENT_CDP_BIN = 'agent-cdp';
|
||||
|
||||
type AgentCdpCommandOptions = {
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
};
|
||||
|
||||
export function buildAgentCdpNpmExecArgs(args: string[]): string[] {
|
||||
return ['exec', '--yes', '--package', AGENT_CDP_PACKAGE, '--', AGENT_CDP_BIN, ...args];
|
||||
}
|
||||
|
||||
export async function runAgentCdpCommand(
|
||||
args: string[],
|
||||
options: AgentCdpCommandOptions = {},
|
||||
): Promise<number> {
|
||||
const result = await runCmdStreaming('npm', buildAgentCdpNpmExecArgs(args), {
|
||||
cwd: options.cwd ?? process.cwd(),
|
||||
env: options.env ?? process.env,
|
||||
allowFailure: true,
|
||||
onStdoutChunk: (chunk) => {
|
||||
process.stdout.write(chunk);
|
||||
},
|
||||
onStderrChunk: (chunk) => {
|
||||
process.stderr.write(chunk);
|
||||
},
|
||||
});
|
||||
return result.exitCode;
|
||||
}
|
||||
@@ -59,6 +59,7 @@ export const INTERNAL_COMMANDS = {
|
||||
} as const;
|
||||
|
||||
const LOCAL_CLI_COMMANDS = {
|
||||
cdp: 'cdp',
|
||||
auth: 'auth',
|
||||
connect: 'connect',
|
||||
connection: 'connection',
|
||||
@@ -87,6 +88,7 @@ export type ClientBackedCliCommandName =
|
||||
|
||||
const MCP_UNEXPOSED_CLI_COMMANDS = commandSet(
|
||||
LOCAL_CLI_COMMANDS.auth,
|
||||
LOCAL_CLI_COMMANDS.cdp,
|
||||
LOCAL_CLI_COMMANDS.connect,
|
||||
LOCAL_CLI_COMMANDS.connection,
|
||||
LOCAL_CLI_COMMANDS.disconnect,
|
||||
@@ -99,6 +101,7 @@ const MCP_UNEXPOSED_CLI_COMMANDS = commandSet(
|
||||
|
||||
const CAPABILITY_EXEMPT_CLI_COMMANDS = commandSet(
|
||||
LOCAL_CLI_COMMANDS.auth,
|
||||
LOCAL_CLI_COMMANDS.cdp,
|
||||
LOCAL_CLI_COMMANDS.connect,
|
||||
LOCAL_CLI_COMMANDS.connection,
|
||||
LOCAL_CLI_COMMANDS.debug,
|
||||
|
||||
@@ -289,6 +289,87 @@ test('parseArgs supports explicit passthrough boundary for react-devtools global
|
||||
assert.deepEqual(parsed.positionals, ['status', '--json']);
|
||||
});
|
||||
|
||||
test('parseArgs preserves cdp arguments as passthrough positionals', () => {
|
||||
const parsed = parseArgs(
|
||||
[
|
||||
'cdp',
|
||||
'memory',
|
||||
'snapshot',
|
||||
'diff',
|
||||
'--base',
|
||||
'ms_1',
|
||||
'--compare',
|
||||
'ms_2',
|
||||
'--limit=10',
|
||||
'--json',
|
||||
'--session',
|
||||
'rn',
|
||||
],
|
||||
{ strictFlags: true },
|
||||
);
|
||||
assert.equal(parsed.command, 'cdp');
|
||||
assert.equal(parsed.flags.json, false);
|
||||
assert.equal(parsed.flags.session, undefined);
|
||||
assert.deepEqual(parsed.positionals, [
|
||||
'memory',
|
||||
'snapshot',
|
||||
'diff',
|
||||
'--base',
|
||||
'ms_1',
|
||||
'--compare',
|
||||
'ms_2',
|
||||
'--limit=10',
|
||||
'--json',
|
||||
'--session',
|
||||
'rn',
|
||||
]);
|
||||
});
|
||||
|
||||
test('parseArgs preserves cdp help as a downstream flag', () => {
|
||||
const parsed = parseArgs(['cdp', '--help'], { strictFlags: true });
|
||||
assert.equal(parsed.command, 'cdp');
|
||||
assert.equal(parsed.flags.help, false);
|
||||
assert.deepEqual(parsed.positionals, ['--help']);
|
||||
});
|
||||
|
||||
test('parseArgs accepts agent-device globals before cdp passthrough args', () => {
|
||||
const parsed = parseArgs(
|
||||
[
|
||||
'--session',
|
||||
'outer-session',
|
||||
'cdp',
|
||||
'target',
|
||||
'list',
|
||||
'--target',
|
||||
'Hermes',
|
||||
'--device',
|
||||
'rn-app',
|
||||
'--json',
|
||||
],
|
||||
{ strictFlags: true },
|
||||
);
|
||||
assert.equal(parsed.command, 'cdp');
|
||||
assert.equal(parsed.flags.session, 'outer-session');
|
||||
assert.equal(parsed.flags.json, false);
|
||||
assert.deepEqual(parsed.positionals, [
|
||||
'target',
|
||||
'list',
|
||||
'--target',
|
||||
'Hermes',
|
||||
'--device',
|
||||
'rn-app',
|
||||
'--json',
|
||||
]);
|
||||
});
|
||||
|
||||
test('parseArgs supports explicit passthrough boundary for cdp global flag names', () => {
|
||||
const parsed = parseArgs(['cdp', '--', 'target', 'list', '--url', 'http://127.0.0.1:8081'], {
|
||||
strictFlags: true,
|
||||
});
|
||||
assert.equal(parsed.command, 'cdp');
|
||||
assert.deepEqual(parsed.positionals, ['target', 'list', '--url', 'http://127.0.0.1:8081']);
|
||||
});
|
||||
|
||||
test('parseArgs accepts push with payload file', () => {
|
||||
const parsed = parseArgs(['push', 'com.example.app', './payload.json'], { strictFlags: true });
|
||||
assert.equal(parsed.command, 'push');
|
||||
@@ -1560,6 +1641,18 @@ test('usageForCommand resolves react-devtools help topic', () => {
|
||||
assert.match(help, /Remote iOS apps attempt the legacy React DevTools websocket/);
|
||||
});
|
||||
|
||||
test('usageForCommand resolves cdp help topic', () => {
|
||||
const help = usageForCommand('cdp');
|
||||
if (help === null) throw new Error('Expected cdp help text');
|
||||
assert.match(help, /agent-device cdp target list --url http:\/\/127\.0\.0\.1:8081/);
|
||||
assert.match(help, /memory usage sample --label baseline --gc/);
|
||||
assert.match(help, /memory snapshot leak-triplet --baseline ms_1 --action ms_2 --cleanup ms_3/);
|
||||
assert.match(help, /memory snapshot retainers --snapshot ms_3 --id <node-id>/);
|
||||
assert.match(help, /Until cdp has a compact leak report command/);
|
||||
assert.match(help, /Avoid cdp profile cpu, trace, network, and console by default/);
|
||||
assert.match(help, /React Native\/Hermes implements a subset of browser CDP/);
|
||||
});
|
||||
|
||||
test('usageForCommand resolves react-native help topic', () => {
|
||||
const help = usageForCommand('react-native');
|
||||
if (help === null) throw new Error('Expected react-native help text');
|
||||
@@ -1578,6 +1671,8 @@ test('usageForCommand resolves react-native help topic', () => {
|
||||
assert.match(help, /One simulator cannot run two copies of the same bundle id/);
|
||||
assert.match(help, /Keep the agent-device react-devtools prefix/);
|
||||
assert.match(help, /Use help react-devtools for status\/wait/);
|
||||
assert.match(help, /Keep the agent-device cdp prefix/);
|
||||
assert.match(help, /Use help cdp for JS heap usage samples/);
|
||||
assert.match(help, /logs clear --restart/);
|
||||
assert.match(help, /network dump --include headers/);
|
||||
assert.match(help, /agent-device open "Agent Device Tester" --platform android/);
|
||||
@@ -1819,6 +1914,7 @@ test('usage renders concise commands inline with descriptions', () => {
|
||||
assert.match(help, / prepare\s{2,}Pre-warm platform helpers/);
|
||||
assert.match(help, / metro\s{2,}Prepare Metro reachability for React Native\/Expo apps/);
|
||||
assert.match(help, / perf\s{2,}Check runtime metrics, frames, memory, CPU profiles/);
|
||||
assert.match(help, / cdp\s{2,}Inspect React Native CDP targets, JS heap growth/);
|
||||
assert.match(help, / react-devtools\s{2,}Inspect React Native components, props, hooks/);
|
||||
assert.match(help, / proxy\s{2,}Expose a local daemon through cloudflared, ngrok/);
|
||||
assert.match(help, / batch --steps <json> \| --steps-file <path>\s{2,}Run multiple commands/);
|
||||
|
||||
+10
-2
@@ -59,6 +59,10 @@ export function parseRawArgs(argv: string[]): RawParsedArgs {
|
||||
else positionals.push(arg);
|
||||
continue;
|
||||
}
|
||||
if (shouldPreservePostCommandArgs(command)) {
|
||||
positionals.push(arg);
|
||||
continue;
|
||||
}
|
||||
const isLongFlag = arg.startsWith('--');
|
||||
const isShortFlag = arg.startsWith('-') && arg.length > 1;
|
||||
if (!isLongFlag && !isShortFlag) {
|
||||
@@ -72,7 +76,7 @@ export function parseRawArgs(argv: string[]): RawParsedArgs {
|
||||
continue;
|
||||
}
|
||||
const definition = resolveFlagDefinition(token, command);
|
||||
if (shouldPassThroughReactDevtoolsFlag(command, definition)) {
|
||||
if (shouldPassThroughLocalToolFlag(command, definition)) {
|
||||
positionals.push(arg);
|
||||
continue;
|
||||
}
|
||||
@@ -108,7 +112,7 @@ function isLegacyIgnoredSnapshotShortFlag(command: string | null, token: string)
|
||||
return token === '-c' && (command === 'snapshot' || command === 'diff');
|
||||
}
|
||||
|
||||
function shouldPassThroughReactDevtoolsFlag(
|
||||
function shouldPassThroughLocalToolFlag(
|
||||
command: string | null,
|
||||
definition: FlagDefinition | undefined,
|
||||
): boolean {
|
||||
@@ -117,6 +121,10 @@ function shouldPassThroughReactDevtoolsFlag(
|
||||
return !isFlagSupportedForCommand(definition.key, command);
|
||||
}
|
||||
|
||||
function shouldPreservePostCommandArgs(command: string | null): boolean {
|
||||
return command === 'cdp';
|
||||
}
|
||||
|
||||
function resolveFlagDefinition(token: string, command: string | null): FlagDefinition | undefined {
|
||||
const definitions = getFlagDefinitions().filter((definition) => definition.names.includes(token));
|
||||
if (definitions.length <= 1) return definitions[0] ?? getFlagDefinition(token);
|
||||
|
||||
@@ -7,6 +7,17 @@ import { COMMON_COMMAND_SUPPORTED_FLAG_KEYS, METRO_PREPARE_FLAGS } from './cli-f
|
||||
type SchemaOnlyCliCommandName = Exclude<LocalCliCommandName, CommandName>;
|
||||
|
||||
const SCHEMA_ONLY_CLI_COMMAND_SCHEMAS = {
|
||||
cdp: {
|
||||
usageOverride: 'cdp [...args]',
|
||||
listUsageOverride: 'cdp',
|
||||
helpDescription:
|
||||
'Run CDP commands for React Native diagnostics, JS heap usage, heap snapshots, and leak analysis',
|
||||
summary:
|
||||
'Inspect React Native CDP targets, JS heap growth, heap snapshots, retainers, and leak signals',
|
||||
positionalArgs: ['args?'],
|
||||
allowsExtraPositionals: true,
|
||||
supportedFlags: COMMON_COMMAND_SUPPORTED_FLAG_KEYS,
|
||||
},
|
||||
auth: {
|
||||
usageOverride: 'auth status|login|logout',
|
||||
listUsageOverride: 'auth',
|
||||
|
||||
@@ -20,6 +20,10 @@ const AGENT_WORKFLOWS = [
|
||||
label: 'help react-devtools',
|
||||
description: 'React Native performance, profiling, component tree, and renders',
|
||||
},
|
||||
{
|
||||
label: 'help cdp',
|
||||
description: 'React Native CDP targets, JS heap snapshots, and leak triage',
|
||||
},
|
||||
{
|
||||
label: 'help physical-device',
|
||||
description: 'Connected phone/tablet setup and iOS signing prerequisites',
|
||||
@@ -43,6 +47,7 @@ const AGENT_QUICKSTART_LINES = [
|
||||
'Anti-pattern: snapshot -i followed by snapshot -i | grep ...; prior refs stay valid until app state changes, and --force-full is the explicit full re-read.',
|
||||
'Truncated text/input preview: expand first with snapshot -s @e12, not get text.',
|
||||
'React Native apps: read help react-native for Metro, DevTools routing, and RN-specific blockers; use react-native dismiss-overlay for LogBox/RedBox overlays.',
|
||||
'React Native JS memory leaks: read help cdp; use heap usage samples for a quick signal, then snapshot diff/leak-triplet for retained object proof.',
|
||||
'Android RN/Expo Metro: direct Android localhost URL opens with a port auto-configure host reachability.',
|
||||
'Expo Go/dev clients: use the provided URL when given; on iOS use open "Expo Go" <url> --platform ios, then snapshot -i --platform ios to verify project UI. Do not use plain snapshot or snapshot --diff for this recovery check. Android URL opens infer the foreground package for logs/perf when possible.',
|
||||
'Install flows: install/install-from-source first, then open the installed id with --relaunch.',
|
||||
@@ -94,6 +99,7 @@ const EXAMPLE_LINES = [
|
||||
'agent-device open TextEdit --platform macos',
|
||||
'agent-device snapshot -i',
|
||||
'agent-device react-devtools get tree --depth 3',
|
||||
'agent-device cdp memory usage sample --gc --label baseline',
|
||||
'agent-device fill @e3 "test@example.com"',
|
||||
'agent-device replay ./session.ad',
|
||||
'agent-device test ./suite --platform android',
|
||||
@@ -457,6 +463,57 @@ Example:
|
||||
agent-device network dump --include headers
|
||||
|
||||
Use snapshot, screenshot, logs, network, and perf metrics for device/app runtime evidence. Use react-devtools only when component internals or React rendering behavior matters.`,
|
||||
},
|
||||
cdp: {
|
||||
summary: 'React Native CDP targets, JS heap snapshots, and leak triage',
|
||||
body: `agent-device help cdp
|
||||
|
||||
Use this when a React Native or Expo app exposes a CDP target through Metro and
|
||||
the task needs JavaScript heap growth checks, heap snapshot diffs, allocation
|
||||
hotspots, retained-object leak evidence, or a small runtime eval to confirm JS
|
||||
state. Do not use this as the default React Native profiler.
|
||||
|
||||
Setup:
|
||||
Start Metro and open the app first. For Android devices/emulators, make sure Metro is reachable from the app, typically with adb reverse tcp:8081 tcp:8081.
|
||||
agent-device cdp target list --url http://127.0.0.1:8081
|
||||
agent-device cdp target select <target-id>
|
||||
|
||||
Quick JS heap signal:
|
||||
agent-device cdp memory usage sample --label baseline --gc
|
||||
# perform the suspected leaking action with agent-device commands
|
||||
agent-device cdp memory usage sample --label after-action --gc
|
||||
agent-device cdp memory usage diff --base jm_1 --compare jm_2
|
||||
agent-device cdp memory usage leak-signal --since jm_1
|
||||
|
||||
Retained-object proof:
|
||||
agent-device cdp memory snapshot capture --name baseline --gc
|
||||
# perform the suspected leaking action
|
||||
agent-device cdp memory snapshot capture --name after-action --gc
|
||||
# perform cleanup/navigation that should release the objects
|
||||
agent-device cdp memory snapshot capture --name cleanup --gc
|
||||
agent-device cdp memory snapshot diff --base ms_1 --compare ms_2 --limit 10
|
||||
agent-device cdp memory snapshot leak-triplet --baseline ms_1 --action ms_2 --cleanup ms_3 --limit 10
|
||||
agent-device cdp memory snapshot retainers --snapshot ms_3 --id <node-id> --depth 8 --limit 10
|
||||
|
||||
Allocation pressure:
|
||||
Use allocation sampling to find where allocations were created, not to prove a leak:
|
||||
agent-device cdp memory allocation start --name suspected-flow --interval 32768 --stack-depth 32
|
||||
# perform the flow once
|
||||
agent-device cdp memory allocation stop
|
||||
agent-device cdp memory allocation hotspots --limit 10
|
||||
agent-device cdp memory allocation source-maps
|
||||
|
||||
Recommended subset:
|
||||
cdp dynamically runs a pinned CDP helper through npm; the first run may download the pinned package, and later runs can reuse the npm cache.
|
||||
Every argument after cdp is passed to the CDP helper. Put agent-device global flags before cdp when you need the outer CLI to consume them.
|
||||
Use cdp memory usage, memory snapshot, memory allocation, and targeted runtime eval.
|
||||
Avoid cdp profile cpu, trace, network, and console by default because agent-device already has perf cpu, trace, network, logs, and react-devtools guidance for those areas.
|
||||
|
||||
Output contract:
|
||||
Until cdp has a compact leak report command, synthesize one from memory usage diff, snapshot diff, leak-triplet, and retainers. Report heap deltas, top retained classes/shapes, leak-triplet rows that stayed high after cleanup, and the shortest useful retaining paths. Do not paste raw heap snapshots or large allocation profiles into the response; use exported artifacts only when the user asks for raw data.
|
||||
|
||||
Target caveats:
|
||||
React Native/Hermes implements a subset of browser CDP. If a command reports an unsupported method, keep the target selected and switch to heap usage samples plus heap snapshots. Prefer react-devtools for component tree/render causes; prefer perf memory sample or perf memory snapshot for native/process memory.`,
|
||||
},
|
||||
'react-native': {
|
||||
summary: 'React Native app automation hazards and routing',
|
||||
@@ -470,6 +527,7 @@ Choose the next help topic:
|
||||
Generic navigation, selectors, refs, verification, serial commands: help workflow.
|
||||
Logs, network, diagnostics, traces, permission dialogs, or runtime failures: help debugging.
|
||||
Component tree, props/state/hooks, slow renders, rerenders, or render causes: help react-devtools.
|
||||
JS heap growth, heap snapshots, allocation hotspots, or retained-object leaks: help cdp.
|
||||
Remote/cloud config, leases, and local service tunnels: help remote.
|
||||
|
||||
React Native dev loop:
|
||||
@@ -517,6 +575,11 @@ React DevTools routing:
|
||||
Use help react-devtools for status/wait, component trees, props/state/hooks, profile windows, slow renders, rerenders, and remote bridge rules.
|
||||
If React DevTools cannot connect, report status and continue with logs, network, perf metrics, screenshot, and trace evidence instead of blocking the whole flow.
|
||||
|
||||
CDP memory routing:
|
||||
Keep the agent-device cdp prefix on every CDP command.
|
||||
Use help cdp for JS heap usage samples, heap snapshots, snapshot diffs, leak-triplet analysis, allocation hotspots, and retained-object paths.
|
||||
Use perf memory sample or perf memory snapshot for native/process memory; use cdp only for JavaScript heap evidence.
|
||||
|
||||
Slow-flow investigation:
|
||||
Keep one session, open the app first, and snapshot -i before interacting.
|
||||
Start React Native slow-flow plans with this ordered scaffold:
|
||||
|
||||
@@ -430,6 +430,8 @@ const BOUNDED_PROFILE_TIMELINE =
|
||||
/react-devtools\s+profile\s+timeline\b[^\n]*--limit\s+(?:10|20)\b/i;
|
||||
const BROAD_PROFILE_SLOW_LIMIT =
|
||||
/react-devtools\s+profile\s+slow\b[^\n]*--limit\s+(?:[5-9]\d|[1-9]\d{2,})\b/i;
|
||||
const CDP_MEMORY_USAGE_SAMPLE = /cdp\s+memory\s+usage\s+sample\b/i;
|
||||
const CDP_MEMORY_SNAPSHOT_CAPTURE = /cdp\s+memory\s+snapshot\s+capture\b/i;
|
||||
const IOS_EXPO_GO_OPEN =
|
||||
/(?:^|\n)(?:agent-device\s+)?open\s+["']Expo Go["']\s+["']?exp:\/\/127\.0\.0\.1:8081["']?/i;
|
||||
const IOS_TEST_APP_DEV_BUILD_OPEN = new RegExp(
|
||||
@@ -1656,6 +1658,93 @@ const SKILL_GUIDANCE_CASES: Case[] = [
|
||||
plannedCommand('perf frames'),
|
||||
],
|
||||
}),
|
||||
makeCase({
|
||||
id: 'react-native-js-heap-leak-cdp-triplet',
|
||||
contract: [
|
||||
'App name: Agent Device Tester',
|
||||
'Platform: iOS simulator',
|
||||
'Metro is running at http://127.0.0.1:8081',
|
||||
'The app exposes a React Native CDP target through Metro',
|
||||
'Symptom: JavaScript heap grows after opening and closing the Cart screen',
|
||||
'Need proof that retained JS objects survive cleanup, plus shortest useful retaining paths',
|
||||
'This is not a native/process memory investigation',
|
||||
],
|
||||
task: 'Plan a bounded React Native JS heap leak workflow using cdp: select the Metro CDP target, sample heap usage, capture baseline/action/cleanup snapshots, diff them, run leak-triplet, and inspect retainers for a leaked node.',
|
||||
outputs: [
|
||||
plannedCommand('cdp target list'),
|
||||
/--url\s+http:\/\/127\.0\.0\.1:8081/i,
|
||||
plannedCommand('cdp target select'),
|
||||
CDP_MEMORY_SNAPSHOT_CAPTURE,
|
||||
/--name\s+baseline/i,
|
||||
/--name\s+(?:after-action|action)/i,
|
||||
/--name\s+cleanup/i,
|
||||
plannedCommand('cdp memory snapshot diff'),
|
||||
plannedCommand('cdp memory snapshot leak-triplet'),
|
||||
plannedCommand('cdp memory snapshot retainers'),
|
||||
],
|
||||
forbiddenOutputs: [
|
||||
plannedCommand('perf memory sample'),
|
||||
plannedCommand('perf memory snapshot'),
|
||||
plannedCommand('react-devtools'),
|
||||
plannedCommand('cdp profile cpu'),
|
||||
plannedCommand('cdp trace'),
|
||||
plannedCommand('cdp network'),
|
||||
plannedCommand('cdp console'),
|
||||
],
|
||||
}),
|
||||
makeCase({
|
||||
id: 'react-native-js-heap-quick-signal-cdp',
|
||||
contract: [
|
||||
'App name: Agent Device Tester',
|
||||
'Platform: Android emulator',
|
||||
'Metro is running at http://127.0.0.1:8081',
|
||||
'The app exposes a React Native CDP target through Metro',
|
||||
'Symptom: JavaScript heap may grow after filtering the product list',
|
||||
'Need only a compact first-pass JS heap growth signal before deciding whether to capture heap snapshots',
|
||||
'This is not a native/process memory investigation',
|
||||
],
|
||||
task: 'Plan the CDP commands to select the Metro target and collect compact before/after JavaScript heap usage samples with GC, then diff the usage samples.',
|
||||
outputs: [
|
||||
plannedCommand('cdp target list'),
|
||||
/--url\s+http:\/\/127\.0\.0\.1:8081/i,
|
||||
plannedCommand('cdp target select'),
|
||||
CDP_MEMORY_USAGE_SAMPLE,
|
||||
/--label\s+baseline/i,
|
||||
/--label\s+after-action/i,
|
||||
plannedCommand('cdp memory usage diff'),
|
||||
],
|
||||
forbiddenOutputs: [
|
||||
plannedCommand('perf memory sample'),
|
||||
plannedCommand('perf memory snapshot'),
|
||||
CDP_MEMORY_SNAPSHOT_CAPTURE,
|
||||
plannedCommand('react-devtools'),
|
||||
],
|
||||
}),
|
||||
makeCase({
|
||||
id: 'react-native-native-memory-uses-perf-not-cdp',
|
||||
contract: [
|
||||
'App name: Agent Device Tester',
|
||||
'Platform: Android emulator',
|
||||
'The app is already open',
|
||||
'Symptom: total process RSS/PSS grows while scrolling a native image gallery',
|
||||
'Need native/process memory evidence and an Android heap artifact if escalation is needed',
|
||||
'This is not a JavaScript heap or retained JS object investigation',
|
||||
],
|
||||
task: 'Plan the memory diagnostics commands for this native/process memory issue without using CDP heap snapshots.',
|
||||
outputs: [
|
||||
plannedCommand('perf memory sample'),
|
||||
/--json/i,
|
||||
plannedCommand('perf memory snapshot'),
|
||||
/--kind\s+android-hprof/i,
|
||||
/--out\s+\S+\.hprof/i,
|
||||
],
|
||||
forbiddenOutputs: [
|
||||
plannedCommand('cdp'),
|
||||
CDP_MEMORY_USAGE_SAMPLE,
|
||||
CDP_MEMORY_SNAPSHOT_CAPTURE,
|
||||
plannedCommand('react-devtools'),
|
||||
],
|
||||
}),
|
||||
makeCase({
|
||||
id: 'perf-apple-xctrace-profile',
|
||||
contract: [
|
||||
|
||||
@@ -47,7 +47,7 @@ The bundled [agent-device skill](https://github.com/callstack/agent-device/blob/
|
||||
Add this as a project rule, custom instruction, or skill equivalent when your agent client supports it:
|
||||
|
||||
```text
|
||||
Use agent-device only for app/device automation tasks. Before planning commands, run `agent-device --version` and read `agent-device help workflow`. For exploratory QA, read `agent-device help dogfood`. For logs, network, traces, or runtime failures, read `agent-device help debugging`. For React Native component trees, props/state/hooks, slow renders, or rerenders, read `agent-device help react-devtools`. For React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence, read `agent-device help react-native`.
|
||||
Use agent-device only for app/device automation tasks. Before planning commands, run `agent-device --version` and read `agent-device help workflow`. For exploratory QA, read `agent-device help dogfood`. For logs, network, traces, or runtime failures, read `agent-device help debugging`. For React Native component trees, props/state/hooks, slow renders, or rerenders, read `agent-device help react-devtools`. For React Native JavaScript heap growth, heap snapshots, or retained-object leaks, read `agent-device help cdp`. For React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence, read `agent-device help react-native`.
|
||||
|
||||
Use MCP tools or the CLI in the integrated terminal. If `agent-device` is not on PATH but the user installed it globally in another shell, resolve the command the same way the user would from a normal terminal session and run that absolute path instead. This may require inspecting shell startup behavior or package-manager/global bin locations; do not assume the agent process `PATH` is the user's `PATH`. Do not silently fall back to `npx -y agent-device@latest`; ask or use an exact version. MCP exposes structured tools backed by the agent-device client; it does not expose generic shell execution. Prefer `open -> snapshot -i -> act -> re-snapshot -> verify -> close`. Use current refs such as `@e3` for exploration and selectors for durable replay. Keep mutating commands against one session serial. Capture screenshots, logs, network, perf, traces, recordings, and `.ad` replay scripts only when they add evidence.
|
||||
```
|
||||
@@ -111,6 +111,7 @@ Before planning device work, run `agent-device --version` and read `agent-device
|
||||
For exploratory QA, read `agent-device help dogfood`.
|
||||
For logs, network, traces, or runtime failures, read `agent-device help debugging`.
|
||||
For React Native component trees, props/state/hooks, slow renders, or rerenders, read `agent-device help react-devtools`.
|
||||
For React Native JavaScript heap growth, heap snapshots, or retained-object leaks, read `agent-device help cdp`.
|
||||
For React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence, read `agent-device help react-native`.
|
||||
|
||||
Use the CLI in Cursor's integrated terminal.
|
||||
@@ -199,6 +200,7 @@ Before planning device work, run `agent-device --version` and read `agent-device
|
||||
For exploratory QA, read `agent-device help dogfood`.
|
||||
For logs, network, traces, or runtime failures, read `agent-device help debugging`.
|
||||
For React Native component trees, props/state/hooks, slow renders, or rerenders, read `agent-device help react-devtools`.
|
||||
For React Native JavaScript heap growth, heap snapshots, or retained-object leaks, read `agent-device help cdp`.
|
||||
For React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence, read `agent-device help react-native`.
|
||||
|
||||
Use the CLI in the integrated terminal.
|
||||
|
||||
@@ -627,6 +627,9 @@ agent-device perf frames --json
|
||||
agent-device perf memory sample --json
|
||||
agent-device perf memory snapshot --kind android-hprof --out app.hprof
|
||||
agent-device perf memory snapshot --kind memgraph --out app.memgraph
|
||||
agent-device cdp target list --url http://127.0.0.1:8081
|
||||
agent-device cdp memory usage sample --label baseline --gc
|
||||
agent-device cdp memory snapshot capture --name baseline --gc
|
||||
agent-device perf cpu profile start --kind xctrace --template "Time Profiler" --out app.trace
|
||||
agent-device perf cpu profile stop --kind xctrace --out app.trace
|
||||
agent-device perf cpu profile report --kind xctrace --out app-profile.json
|
||||
@@ -645,6 +648,7 @@ agent-device perf trace stop --kind perfetto --out app.perfetto-trace
|
||||
- Example sample shape: `{"metrics":{"memory":{"available":true,"totalPssKb":562958,"totalRssKb":570304,"topConsumers":[{"name":"Dalvik Heap","pssKb":213456}]}}}`.
|
||||
- `perf memory snapshot` writes a heap/memgraph artifact to disk and returns path, size, kind, method, and support metadata. Large artifacts are never dumped into CLI/MCP/default JSON output.
|
||||
- Example default snapshot output: `Memory artifact (android-hprof): /tmp/app.hprof (42MB)`.
|
||||
- `cdp` targets React Native JavaScript heap evidence through Metro CDP. Use it for JS heap usage samples and heap snapshots; use `perf memory sample` and `perf memory snapshot` for native/process memory. See [Debugging & Profiling](/docs/debugging-profiling) for the bounded leak workflow.
|
||||
- `perf cpu profile ... --kind xctrace` records an Apple `.trace` with the requested xctrace template and writes a compact JSON report from the most recent CPU profile trace.
|
||||
- `perf trace ... --kind xctrace` records an Apple `.trace` such as Animation Hitches for native diagnosis.
|
||||
- xctrace perf commands return artifact paths and compact metadata only; inspect `.trace` files in Instruments/Xcode instead of dumping trace contents into agent context.
|
||||
|
||||
@@ -39,6 +39,29 @@ React Native warning/error overlays belong to the app run. Treat them as finding
|
||||
|
||||
Use `alert wait`, `alert accept`, and `alert dismiss` for Android runtime permission prompts, Android native alerts, and iOS platform/app-owned modal dialogs. Do not use `settings permission` to answer a dialog already on screen. Reserve `settings permission` for setup or resetting permission state before a flow.
|
||||
|
||||
## React Native JS memory through CDP
|
||||
|
||||
Use `cdp` when a React Native or Expo app exposes a Metro CDP target and the task needs JavaScript heap usage, heap snapshots, allocation hotspots, retained-object diffs, retaining paths, or a small runtime eval to confirm JS state.
|
||||
|
||||
```bash
|
||||
agent-device cdp target list --url http://127.0.0.1:8081
|
||||
agent-device cdp target select <target-id>
|
||||
agent-device cdp memory usage sample --label baseline --gc
|
||||
agent-device cdp memory snapshot capture --name baseline --gc
|
||||
agent-device cdp memory snapshot diff --base ms_1 --compare ms_2 --limit 10
|
||||
agent-device cdp memory snapshot leak-triplet --baseline ms_1 --action ms_2 --cleanup ms_3 --limit 10
|
||||
agent-device cdp memory snapshot retainers --snapshot ms_3 --id <node-id> --depth 8 --limit 10
|
||||
```
|
||||
|
||||
- `cdp` dynamically runs a pinned CDP helper through npm; the first run may download the pinned package, and later runs can reuse the npm cache.
|
||||
- Every argument after `cdp` is passed to the CDP helper. Put `agent-device` global flags before `cdp` when you need the outer CLI to consume them.
|
||||
- Start with `memory usage sample --gc` for a quick JS heap growth signal. Use snapshot diff and `leak-triplet` for proof that objects stayed retained after cleanup.
|
||||
- Until `cdp` has a compact leak report command, synthesize one from `memory usage diff`, `memory snapshot diff`, `memory snapshot leak-triplet`, and `memory snapshot retainers`.
|
||||
- Keep raw heap snapshots and allocation exports as artifacts. Default answers should summarize heap deltas, top retained classes/shapes, leak-triplet rows that stayed high after cleanup, and shortest useful retaining paths.
|
||||
- React Native/Hermes supports only part of browser CDP. If a method is unsupported, keep the selected target and fall back to heap usage samples plus heap snapshots.
|
||||
- Avoid `cdp profile cpu`, `trace`, `network`, and `console` by default because `agent-device` already has `perf cpu`, `trace`, `network`, `logs`, and `react-devtools` guidance for those areas.
|
||||
- Use `perf memory sample` and `perf memory snapshot` for native/process memory. Use `cdp` only for JavaScript heap evidence.
|
||||
|
||||
## Fast path
|
||||
|
||||
```bash
|
||||
@@ -139,6 +162,7 @@ agent-device perf trace stop --kind perfetto --out app.perfetto-trace
|
||||
- `perf memory sample` returns a compact memory-only payload, preserving the memory metric source used by `perf metrics`. Prefer it over raw `dumpsys`/`leaks` output for first-pass agent diagnosis because it keeps arrays bounded, reports top offenders compactly, and omits unrelated startup/CPU/frame data.
|
||||
- Example sample shape: `{"metrics":{"memory":{"available":true,"totalPssKb":562958,"totalRssKb":570304,"topConsumers":[{"name":"Dalvik Heap","pssKb":213456}]}}}`.
|
||||
- `perf memory snapshot` escalates to file artifacts. Android supports Java HPROF capture for active app processes when the build/device allows heap dumping. iOS simulator and macOS app sessions support memgraph capture through host-visible process tooling; physical iOS device memgraph capture reports unavailable with a hint instead of pretending support.
|
||||
- For React Native JavaScript heap leaks, use `agent-device cdp` against the Metro CDP target instead of native/process memory samples; see the CDP section above.
|
||||
- Heap and memgraph artifacts are returned as paths plus compact metadata. Example default output: `Memory artifact (android-hprof): /tmp/app.hprof (42MB)`. They are not printed or embedded in JSON by default. heapprofd/native allocation tracing is deferred until Perfetto plumbing is available.
|
||||
- `perf cpu profile ... --kind xctrace` and `perf trace ... --kind xctrace` collect Apple native `.trace` artifacts for iOS/macOS app sessions and return only artifact paths plus compact metadata.
|
||||
- Android native profiling uses `perf cpu profile ... --kind simpleperf`; Android native trace capture uses `perf trace ... --kind perfetto`. These commands require an active Android app session and return artifact paths/summaries instead of dumping profile or trace contents.
|
||||
|
||||
@@ -21,6 +21,7 @@ Use global install for normal agent workflows. It gives agents a stable `agent-d
|
||||
agent-device help workflow
|
||||
agent-device help debugging
|
||||
agent-device help react-devtools
|
||||
agent-device help cdp
|
||||
```
|
||||
|
||||
Some agent clients run commands in an environment that differs from the user's normal install shell. If `agent-device` is missing in the agent terminal but was installed globally elsewhere, resolve the command the same way the user would from a normal terminal session, then use the absolute binary path for agent commands. This may require inspecting shell startup behavior or package-manager/global bin locations; do not assume the agent process `PATH` is the user's `PATH`.
|
||||
|
||||
@@ -49,6 +49,7 @@ Installed CLI help is the version-matched operating guide. Start there before pl
|
||||
agent-device help workflow
|
||||
agent-device help debugging
|
||||
agent-device help react-devtools
|
||||
agent-device help cdp
|
||||
agent-device help dogfood
|
||||
```
|
||||
|
||||
|
||||
@@ -24,5 +24,5 @@ features:
|
||||
- title: Session and replay
|
||||
details: Open apps, keep stateful context, and replay recorded `.ad` actions to reproduce flows without AI at runtime.
|
||||
- title: React Native internals
|
||||
details: Use agent-device react-devtools to inspect React Native component trees, props, state, hooks, and render profiles through pinned agent-react-devtools.
|
||||
details: Use agent-device react-devtools for component trees and render profiles, and agent-device cdp for React Native JS heap snapshots, diffs, and leak retainers.
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user