Revert "feat: enhance React profiler session handling with device ID support"

This reverts commit 1efa4c001e.
This commit is contained in:
pFornagiel
2026-04-13 20:29:03 +02:00
parent badf127411
commit 2bf25cb950
13 changed files with 90 additions and 164 deletions
@@ -108,13 +108,10 @@ export const jsRuntimeDebuggerBlueprint: ServiceBlueprint<JsRuntimeDebuggerApi,
},
async factory(_deps, payload, options?) {
// payload is either "port" or "port:deviceId"
const colonIdx = payload.indexOf(":");
const port = colonIdx >= 0 ? parseInt(payload.slice(0, colonIdx), 10) : parseInt(payload, 10);
const deviceId = colonIdx >= 0 ? payload.slice(colonIdx + 1) : undefined;
const port = parseInt(payload, 10);
const metro = await discoverMetro(port);
const selected = selectTarget(metro.targets, port, { ...options, ...(deviceId && { deviceId }) });
const selected = selectTarget(metro.targets, port, options);
const cdp = new CDPClient(selected.webSocketUrl);
await cdp.connect();
@@ -38,15 +38,10 @@ export interface ProfilerSessionPaths {
anyCompilerOptimized: boolean | null;
hotCommitIndices: number[] | null;
totalReactCommits: number | null;
deviceId: string | null;
deviceName: string | null;
appName: string | null;
projectRoot: string | null;
}
export interface ReactProfilerSessionApi {
port: number;
deviceId: string | null;
cdp: CDPClient;
projectRoot: string;
hermesVersion: string;
@@ -64,17 +59,12 @@ export interface ReactProfilerSessionApi {
export const reactProfilerSessionBlueprint: ServiceBlueprint<ReactProfilerSessionApi, string> = {
namespace: REACT_PROFILER_SESSION_NAMESPACE,
// payload is either "port" or "port:deviceId"
getURN(payload: string) {
return `${REACT_PROFILER_SESSION_NAMESPACE}:${payload}`;
getURN(port: string) {
return `${REACT_PROFILER_SESSION_NAMESPACE}:${port}`;
},
getDependencies(payload: string) {
const colonIdx = payload.indexOf(":");
const portStr = colonIdx >= 0 ? payload.slice(0, colonIdx) : payload;
const deviceId = colonIdx >= 0 ? payload.slice(colonIdx + 1) : undefined;
const depPayload = deviceId ? `${portStr}:${deviceId}` : portStr;
return { debugger: `JsRuntimeDebugger:${depPayload}` };
getDependencies(port: string) {
return { debugger: `JsRuntimeDebugger:${port}` };
},
async factory(deps, _payload) {
@@ -91,7 +81,6 @@ export const reactProfilerSessionBlueprint: ServiceBlueprint<ReactProfilerSessio
const state: ReactProfilerSessionApi = {
port: debuggerApi.port,
deviceId: debuggerApi.logicalDeviceId ?? null,
cdp,
projectRoot: debuggerApi.projectRoot,
hermesVersion: "unknown",
@@ -167,11 +156,6 @@ export const reactProfilerSessionBlueprint: ServiceBlueprint<ReactProfilerSessio
}
cdp.events.on("disconnected", (error) => {
// Only clear cache if profiling was in progress — preserves data from a completed session
// that survived app restart, while preventing stale in-flight data from being returned.
if (state.profilingActive) {
clearCachedProfilerPaths(state.port, state.deviceId ?? undefined);
}
events.emit("terminated", error ?? new Error("CDP disconnected"));
});
@@ -186,27 +170,16 @@ export const reactProfilerSessionBlueprint: ServiceBlueprint<ReactProfilerSessio
},
};
const profilerPathsCache = new Map<string, ProfilerSessionPaths>();
const profilerPathsCache = new Map<number, ProfilerSessionPaths>();
function cacheKey(port: number, deviceId?: string): string {
return deviceId ? `${port}:${deviceId}` : `${port}`;
export function cacheProfilerPaths(port: number, paths: ProfilerSessionPaths): void {
profilerPathsCache.set(port, paths);
}
export function cacheProfilerPaths(
port: number,
paths: ProfilerSessionPaths,
deviceId?: string
): void {
profilerPathsCache.set(cacheKey(port, deviceId), paths);
export function getCachedProfilerPaths(port: number): ProfilerSessionPaths | undefined {
return profilerPathsCache.get(port);
}
export function getCachedProfilerPaths(
port: number,
deviceId?: string
): ProfilerSessionPaths | undefined {
return profilerPathsCache.get(cacheKey(port, deviceId));
}
export function clearCachedProfilerPaths(port: number, deviceId?: string): void {
profilerPathsCache.delete(cacheKey(port, deviceId));
export function clearCachedProfilerPaths(port: number): void {
profilerPathsCache.delete(port);
}
@@ -1,6 +1,10 @@
import { z } from "zod";
import type { ToolDefinition } from "@argent/registry";
import { getCachedProfilerPaths } from "../../../blueprints/react-profiler-session";
import {
REACT_PROFILER_SESSION_NAMESPACE,
type ReactProfilerSessionApi,
getCachedProfilerPaths,
} from "../../../blueprints/react-profiler-session";
import {
IOS_PROFILER_SESSION_NAMESPACE,
type IosProfilerSessionApi,
@@ -44,9 +48,11 @@ Returns a markdown report correlating hangs with React commits, memory leaks, an
Fails if either react-profiler-analyze or ios-profiler-analyze has not been called first.`,
zodSchema,
services: (params) => ({
reactSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
iosSession: `${IOS_PROFILER_SESSION_NAMESPACE}:${params.device_id}`,
}),
async execute(services, params) {
async execute(services) {
const reactApi = services.reactSession as ReactProfilerSessionApi;
const iosApi = services.iosSession as IosProfilerSessionApi;
// Validate prerequisites
@@ -54,8 +60,7 @@ Fails if either react-profiler-analyze or ios-profiler-analyze has not been call
throw new Error("No iOS Instruments data. Run ios-profiler-analyze first.");
}
// Read-only: resolve react paths from cache only — no live CDP connection needed.
const sessionPaths = getCachedProfilerPaths(params.port, params.device_id);
const sessionPaths = reactApi.sessionPaths ?? getCachedProfilerPaths(reactApi.port);
if (!sessionPaths?.commitsPath) {
throw new Error("No React commit data. Run react-profiler-analyze first.");
}
@@ -71,7 +76,7 @@ Fails if either react-profiler-analyze or ios-profiler-analyze has not been call
cpuProfile = await readCpuProfile(sessionPaths.cpuProfilePath);
}
const reactWallStart = onDisk.meta?.profileStartWallMs ?? null;
const reactWallStart = reactApi.profileStartWallMs ?? onDisk.meta?.profileStartWallMs ?? null;
const iosWallStart = iosApi.wallClockStartMs;
if (!reactWallStart && !iosWallStart) {
@@ -98,7 +103,7 @@ Fails if either react-profiler-analyze or ios-profiler-analyze has not been call
// Build hot commit summaries from raw data
const preprocessed = preprocess(commitTree.commits);
const hotIndices = sessionPaths.hotCommitIndices ?? [];
const hotIndices = sessionPaths.hotCommitIndices ?? reactApi.hotCommitIndices ?? [];
const hotCommits = buildHotCommitSummaries(preprocessed, hotIndices);
const nonMarginCommits = hotCommits.filter((c) => !c.isMargin);
@@ -1,6 +1,10 @@
import { z } from "zod";
import type { ToolDefinition } from "@argent/registry";
import { getCachedProfilerPaths } from "../../../blueprints/react-profiler-session";
import {
REACT_PROFILER_SESSION_NAMESPACE,
type ReactProfilerSessionApi,
getCachedProfilerPaths,
} from "../../../blueprints/react-profiler-session";
import type {
DevToolsFiberCommit,
DevToolsCommitTree,
@@ -15,12 +19,6 @@ const timeRangeSchema = z.object({
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Must match the value passed to react-profiler-start/stop."
),
mode: z
.enum(["by_component", "by_time_range", "by_index", "cascade_tree"])
.describe(
@@ -42,11 +40,8 @@ const zodSchema = z.object({
.describe("Max results to return (default 20)"),
});
async function getCommitTree(
port: number,
deviceId: string | undefined
): Promise<DevToolsCommitTree> {
const sessionPaths = getCachedProfilerPaths(port, deviceId);
async function getCommitTree(api: ReactProfilerSessionApi): Promise<DevToolsCommitTree> {
const sessionPaths = api.sessionPaths ?? getCachedProfilerPaths(api.port);
if (!sessionPaths?.commitsPath) {
throw new Error(
"No commit data stored. Run react-profiler-start → exercise app → react-profiler-stop first."
@@ -331,9 +326,12 @@ Use when drilling into specific components or time windows after react-profiler-
Returns a markdown table or tree of commit data matching the requested mode.
Fails if react-profiler-stop has not been called or no commit data is stored.`,
zodSchema,
services: () => ({}),
async execute(_services, params) {
const commitTree = await getCommitTree(params.port, params.device_id);
services: (params) => ({
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
}),
async execute(services, params) {
const api = services.profilerSession as ReactProfilerSessionApi;
const commitTree = await getCommitTree(api);
switch (params.mode) {
case "by_component": {
@@ -1,6 +1,8 @@
import { z } from "zod";
import type { ToolDefinition } from "@argent/registry";
import {
REACT_PROFILER_SESSION_NAMESPACE,
type ReactProfilerSessionApi,
type ProfilerSessionPaths,
getCachedProfilerPaths,
} from "../../../blueprints/react-profiler-session";
@@ -22,12 +24,6 @@ const timeWindowSchema = z.object({
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Must match the value passed to react-profiler-start/stop."
),
mode: z
.enum(["top_functions", "time_window", "call_tree", "component_cpu"])
.describe(
@@ -51,7 +47,7 @@ const zodSchema = z.object({
.describe("For call_tree mode: also show callers of the function"),
});
async function getIndex(sessionPaths: ProfilerSessionPaths): Promise<{
async function getIndex(api: ReactProfilerSessionApi): Promise<{
index: CpuSampleIndex;
commitTree: {
commits: {
@@ -62,6 +58,9 @@ async function getIndex(sessionPaths: ProfilerSessionPaths): Promise<{
}[];
} | null;
}> {
const sessionPaths: ProfilerSessionPaths | undefined =
api.sessionPaths ?? getCachedProfilerPaths(api.port) ?? undefined;
if (!sessionPaths?.cpuProfilePath) {
throw new Error(
"No CPU profile stored. Run react-profiler-start → exercise the app → react-profiler-stop → react-profiler-analyze first."
@@ -349,15 +348,12 @@ Use when investigating JS CPU hotspots or correlating CPU cost with specific com
Returns a markdown table of CPU hotspots, call tree, or per-component CPU breakdown.
Fails if no CPU profile is stored — run react-profiler-stop first.`,
zodSchema,
services: () => ({}),
async execute(_services, params) {
const sessionPaths = getCachedProfilerPaths(params.port, params.device_id);
if (!sessionPaths) {
throw new Error(
"No profiling data stored. Run react-profiler-start → exercise the app → react-profiler-stop → react-profiler-analyze first."
);
}
const { index, commitTree } = await getIndex(sessionPaths);
services: (params) => ({
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
}),
async execute(services, params) {
const api = services.profilerSession as ReactProfilerSessionApi;
const { index, commitTree } = await getIndex(api);
switch (params.mode) {
case "top_functions":
@@ -38,10 +38,7 @@ const zodSchema = z.object({
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Required for load_instruments. " +
"For load_react: caches the session under this device ID so query tools can look it up by port+device."
),
.describe("iOS Simulator UDID — required for load_instruments to populate the iOS session"),
});
async function listSessions(debugDir: string): Promise<string> {
@@ -122,8 +119,7 @@ async function listSessions(debugDir: string): Promise<string> {
async function loadReactSession(
debugDir: string,
sessionId: string,
port: number,
deviceId?: string
port: number
): Promise<string> {
const cpuPath = path.join(debugDir, `react-profiler-${sessionId}_cpu.json`);
const commitsPath = path.join(debugDir, `react-profiler-${sessionId}_commits.json`);
@@ -203,13 +199,9 @@ async function loadReactSession(
anyCompilerOptimized,
hotCommitIndices,
totalReactCommits,
deviceId: deviceId ?? null,
deviceName: null,
appName: null,
projectRoot: null,
};
cacheProfilerPaths(port, sessionPaths, deviceId);
cacheProfilerPaths(port, sessionPaths);
const lines: string[] = [
`Loaded React profiler session \`${sessionId}\` into port ${port}.`,
@@ -319,7 +311,7 @@ Fails if the session_id is not found or required XML files are missing from disk
"load_react mode requires the session_id parameter. Use list mode first."
);
}
return loadReactSession(debugDir, params.session_id, params.port, params.device_id);
return loadReactSession(debugDir, params.session_id, params.port);
}
case "load_instruments": {
@@ -2,6 +2,8 @@ import { z } from "zod";
import { promises as fsPromises } from "fs";
import type { ToolDefinition } from "@argent/registry";
import {
REACT_PROFILER_SESSION_NAMESPACE,
type ReactProfilerSessionApi,
type ProfilerSessionPaths,
getCachedProfilerPaths,
} from "../../../blueprints/react-profiler-session";
@@ -31,12 +33,6 @@ const annotationSchema = z.object({
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Must match the value passed to react-profiler-start/stop."
),
project_root: z
.string()
.describe("Absolute path to the RN project root for session context detection"),
@@ -71,13 +67,15 @@ is returned by react-profiler-start.
Use when the profiling session is complete and you need to interpret the collected data.
Fails if react-profiler-stop has not been called or no profiling data is stored.`,
zodSchema,
services: () => ({}),
async execute(_services, params) {
// Read-only: resolve paths from cache only — no live CDP connection needed.
const sessionPaths: ProfilerSessionPaths | undefined = getCachedProfilerPaths(
params.port,
params.device_id
);
services: (params) => ({
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
}),
async execute(services, params) {
const api = services.profilerSession as ReactProfilerSessionApi;
// Resolve session paths from session or cache
const sessionPaths: ProfilerSessionPaths | undefined =
api.sessionPaths ?? getCachedProfilerPaths(api.port) ?? undefined;
if (!sessionPaths) {
throw new Error(
@@ -1,9 +1,11 @@
import { z } from "zod";
import { promises as fs } from "fs";
import type { ToolDefinition } from "@argent/registry";
import { REACT_PROFILER_SESSION_NAMESPACE } from "../../../blueprints/react-profiler-session";
import { buildAstIndexWithDiagnostics } from "../../../utils/react-profiler/pipeline/06-resolve/ast-index";
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
component_name: z.string().describe("Name of the React component to look up"),
project_root: z.string().describe("Absolute path to the RN project root"),
});
@@ -17,7 +19,9 @@ export const reactProfilerComponentSourceTool: ToolDefinition<
Call this per-finding after react-profiler-analyze to inspect source before proposing a fix.
Returns found: false if the component is not found in user-owned code (e.g. lives in node_modules).`,
zodSchema,
services: () => ({}),
services: (params) => ({
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
}),
async execute(_services, params) {
const astIndex = await buildAstIndexWithDiagnostics(params.project_root);
const entry = astIndex.index.get(params.component_name);
@@ -1,6 +1,10 @@
import { z } from "zod";
import type { ToolDefinition } from "@argent/registry";
import { getCachedProfilerPaths } from "../../../blueprints/react-profiler-session";
import {
REACT_PROFILER_SESSION_NAMESPACE,
type ReactProfilerSessionApi,
getCachedProfilerPaths,
} from "../../../blueprints/react-profiler-session";
import type {
HermesProfileNode,
HermesCpuProfile,
@@ -10,12 +14,6 @@ import { isArgentProfilerFunction } from "../../../utils/react-profiler/pipeline
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Must match the value passed to react-profiler-start/stop."
),
top_n: z.coerce
.number()
.int()
@@ -125,9 +123,13 @@ Call react-profiler-stop first. Reads directly from the stored cpuProfile.
Returns a markdown table of the top hotspot functions with self-time, total-time, and location.
Fails if react-profiler-stop has not been called or no CPU profile is stored.`,
zodSchema,
services: () => ({}),
async execute(_services, params) {
const sessionPaths = getCachedProfilerPaths(params.port, params.device_id);
services: (params) => ({
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
}),
async execute(services, params) {
const api = services.profilerSession as ReactProfilerSessionApi;
const sessionPaths = api.sessionPaths ?? getCachedProfilerPaths(api.port);
if (!sessionPaths?.cpuProfilePath) {
throw new Error(
"No CPU profile stored. Call react-profiler-start, exercise the app, then react-profiler-stop."
@@ -90,12 +90,6 @@ function buildFiberTreeScript(maxDepth: number, filter: string): string {
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Required when multiple simulators are connected to the same Metro port."
),
max_depth: z.coerce
.number()
.int()
@@ -113,7 +107,7 @@ Returns a nested JSON tree of fiber nodes with name, tag, actualDuration, selfBa
Fails if the React DevTools hook is not present or no fiber roots have been committed yet.`,
zodSchema,
services: (params) => ({
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}${params.device_id ? `:${params.device_id}` : ""}`,
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
}),
async execute(services, params) {
const api = services.profilerSession as ReactProfilerSessionApi;
@@ -91,12 +91,6 @@ function renderMarkdownTable(entries: RenderEntry[]): string {
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Required when multiple simulators are connected to the same Metro port."
),
top_n: z.coerce
.number()
.int()
@@ -113,7 +107,7 @@ Use when you want a quick snapshot of render counts without a full profiling ses
Fails if the React DevTools hook is not present in the runtime or the app is not connected.`,
zodSchema,
services: (params) => ({
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}${params.device_id ? `:${params.device_id}` : ""}`,
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`,
}),
async execute(services, params) {
const api = services.profilerSession as ReactProfilerSessionApi;
@@ -9,17 +9,12 @@ import { JS_RUNTIME_DEBUGGER_NAMESPACE } from "../../../blueprints/js-runtime-de
const COMMIT_CAPTURE_SCRIPT = `
(function __argent_commitCaptureInit() {
// Always reset the data array and commit index so each start() begins fresh.
globalThis.__ARGENT_DEVTOOLS_COMMITS__ = [];
globalThis.__ARGENT_DEVTOOLS_COMMIT_INDEX__ = 0;
var hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
if (!hook) return;
// Idempotency guard: only wrap onCommitFiberRoot once per JS runtime lifetime.
// Subsequent start() calls reset the arrays above but skip re-wrapping the hook,
// preventing N nested wrappers (and N duplicate entries per commit) on re-use.
if (hook.__argent_commit_capture__) return;
hook.__argent_commit_capture__ = true;
var origOnCommit = hook.onCommitFiberRoot;
var commitIndex = 0;
function __argent_getChangedHookIndices(fiber) {
if (!fiber.alternate) return null;
@@ -51,7 +46,7 @@ const COMMIT_CAPTURE_SCRIPT = `
hook.onCommitFiberRoot = function __argent_onCommitFiberRoot(rendererID, root, priorityLevel) {
var ts = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
var idx = globalThis.__ARGENT_DEVTOOLS_COMMIT_INDEX__++;
var idx = commitIndex++;
var commitDur = (root && root.current && typeof root.current.actualDuration === 'number')
? root.current.actualDuration : 0;
var stack = root && root.current ? [root.current] : [];
@@ -131,12 +126,6 @@ const COMMIT_CAPTURE_SCRIPT = `
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Required when multiple simulators are connected to the same Metro port. Omit to auto-select the first available target."
),
sample_interval_us: z.coerce
.number()
.int()
@@ -174,9 +163,8 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
zodSchema,
services: () => ({}),
async execute(_services, params) {
const deviceSuffix = params.device_id ? `:${params.device_id}` : "";
const jsdUrn = `${JS_RUNTIME_DEBUGGER_NAMESPACE}:${params.port}${deviceSuffix}`;
const psUrn = `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}${deviceSuffix}`;
const jsdUrn = `${JS_RUNTIME_DEBUGGER_NAMESPACE}:${params.port}`;
const psUrn = `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`;
const ignore = () => {};
async function disposeAndWait() {
@@ -280,7 +268,7 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
)
.catch(ignore);
clearCachedProfilerPaths(api.port, api.deviceId ?? undefined);
clearCachedProfilerPaths(api.port);
api.sessionPaths = null;
api.profilingActive = true;
api.anyCompilerOptimized = null;
@@ -15,12 +15,6 @@ import { getDebugDir, writeDumpCompact } from "../../../utils/react-profiler/deb
const zodSchema = z.object({
port: z.coerce.number().default(8081).describe("Metro server port"),
device_id: z
.string()
.optional()
.describe(
"iOS Simulator UDID (logicalDeviceId). Must match the value passed to react-profiler-start."
),
});
export function createReactProfilerStopTool(
@@ -37,8 +31,7 @@ Fails if no active profiling session exists or the CDP connection was lost durin
zodSchema,
services: () => ({}),
async execute(_services, params) {
const deviceSuffix = params.device_id ? `:${params.device_id}` : "";
const psUrn = `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}${deviceSuffix}`;
const psUrn = `${REACT_PROFILER_SESSION_NAMESPACE}:${params.port}`;
const snapshot = registry.getSnapshot();
const entry = snapshot.services.get(psUrn);
@@ -263,10 +256,6 @@ Fails if no active profiling session exists or the CDP connection was lost durin
hotCommitIndices: api.hotCommitIndices,
totalReactCommits: api.totalReactCommits,
profileStartWallMs: api.profileStartWallMs,
// Provenance fields — used by profiler-load to display session origin
projectRoot: api.projectRoot,
deviceId: api.deviceId,
port: api.port,
},
}
);
@@ -281,13 +270,9 @@ Fails if no active profiling session exists or the CDP connection was lost durin
anyCompilerOptimized: api.anyCompilerOptimized,
hotCommitIndices: api.hotCommitIndices,
totalReactCommits: api.totalReactCommits,
deviceId: api.deviceId,
deviceName: null,
appName: null,
projectRoot: api.projectRoot,
};
cacheProfilerPaths(api.port, sessionPaths, api.deviceId ?? undefined);
cacheProfilerPaths(api.port, sessionPaths);
api.sessionPaths = sessionPaths;
api.disposeSession();