UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

424 lines • 18.3 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { zod } from '../third_party/index.js'; import { byteSizeRangeSchema } from '../utils/bytes.js'; import { ToolCategory } from './categories.js'; import { definePageTool, defineTool } from './ToolDefinition.js'; const HEAP_SNAPSHOT_FILTERS = [ 'objectsRetainedByDetachedDomNodes', 'objectsRetainedByConsole', 'objectsRetainedByEventHandlers', 'objectsRetainedByContexts', 'sharedNativeContext', 'noNativeContext', 'attributedToSpecificNativeContext', ]; export const takeHeapSnapshot = definePageTool({ name: 'take_heapsnapshot', description: `Capture a heap snapshot of the target page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.`, annotations: { category: ToolCategory.MEMORY, readOnlyHint: false, }, schema: { filePath: zod .string() .describe('A path to a .heapsnapshot file to save the heapsnapshot to.'), }, blockedByDialog: true, verifyFilesSchema: { filePath: true, }, handler: async (request, response, context) => { const page = request.page; const snapshotPath = await context.ensureExtension(request.params.filePath, '.heapsnapshot'); await page.pptrPage.captureHeapSnapshot({ path: snapshotPath, }); response.appendResponseLine(`Heap snapshot saved to ${snapshotPath}`); }, }); export const getHeapSnapshotSummary = defineTool({ name: 'get_heapsnapshot_summary', description: 'Loads a memory heapsnapshot and returns snapshot summary stats, including native contexts and their sizes, and retained by context summary.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, handler: async (request, response, context) => { const stats = await context.getHeapSnapshotStats(request.params.filePath); const staticData = await context.getHeapSnapshotStaticData(request.params.filePath); const nativeContextSizes = await context.getHeapSnapshotNativeContextSizes(request.params.filePath); const retainedByContextSummary = await context.getHeapSnapshotRetainedByContextSummary(request.params.filePath); response.setHeapSnapshotStats(stats, staticData, nativeContextSizes, retainedByContextSummary); }, }); export const getHeapSnapshotDetails = defineTool({ name: 'get_heapsnapshot_details', description: 'Loads a memory heapsnapshot and returns all available information including statistics, static data, and aggregated node information. Supports pagination for aggregates.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), filterName: zod .enum(HEAP_SNAPSHOT_FILTERS) .optional() .describe('An optional filter to apply to the aggregates.'), objectId: zod .number() .optional() .describe('The object ID (nodeId) of the specific native context to filter by when filterName is attributedToSpecificNativeContext.'), pageIdx: zod .number() .optional() .describe('The page index for pagination of aggregates.'), pageSize: zod .number() .optional() .describe('The page size for pagination of aggregates.'), }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, handler: async (request, response, context) => { const aggregates = await context.getHeapSnapshotAggregates(request.params.filePath, request.params.filterName, request.params.objectId); response.setHeapSnapshotAggregates(aggregates, { pageIdx: request.params.pageIdx, pageSize: request.params.pageSize, }); }, }); export const getHeapSnapshotClassNodes = defineTool({ name: 'get_heapsnapshot_class_nodes', description: 'Loads a memory heapsnapshot and returns instances of a specific class with their IDs.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), id: zod.number().describe('The ID for the class, obtained from details.'), filterName: zod .enum(HEAP_SNAPSHOT_FILTERS) .optional() .describe('An optional filter to apply to the nodes.'), objectId: zod .number() .optional() .describe('The object ID (nodeId) of the specific native context to filter by when filterName is attributedToSpecificNativeContext.'), pageIdx: zod.number().optional().describe('The page index for pagination.'), pageSize: zod.number().optional().describe('The page size for pagination.'), }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, handler: async (request, response, context) => { const nodes = await context.getHeapSnapshotNodesById(request.params.filePath, request.params.id, request.params.filterName, request.params.objectId); response.setHeapSnapshotNodes(nodes, { pageIdx: request.params.pageIdx, pageSize: request.params.pageSize, }); }, }); export const getHeapSnapshotRetainers = defineTool({ name: 'get_heapsnapshot_retainers', description: 'Loads a memory heapsnapshot and returns retainers for a specific node ID.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), nodeId: zod.number().describe('The node ID to get retainers for.'), pageIdx: zod.number().optional().describe('The page index for pagination.'), pageSize: zod.number().optional().describe('The page size for pagination.'), }, handler: async (request, response, context) => { const retainers = await context.getHeapSnapshotRetainers(request.params.filePath, request.params.nodeId); response.setHeapSnapshotNodes(retainers, { pageIdx: request.params.pageIdx, pageSize: request.params.pageSize, }); }, }); export const closeHeapSnapshot = defineTool({ name: 'close_heapsnapshot', description: 'Closes a previously loaded memory heapsnapshot, freeing its memory.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: false, conditions: ['memoryDebugging'], }, verifyFilesSchema: { filePath: true, }, schema: { filePath: zod .string() .describe('A path to the .heapsnapshot file to close.'), }, blockedByDialog: false, handler: async (request, response, context) => { const closed = await context.closeHeapSnapshot(request.params.filePath); if (!closed) { throw new Error(`Failed to close heap snapshot: ${request.params.filePath} was not loaded.`); } response.appendResponseLine(`Closed heap snapshot: ${request.params.filePath}`); }, }); export const getHeapSnapshotRetainingPaths = defineTool({ name: 'get_heapsnapshot_retaining_paths', description: 'Loads a memory heapsnapshot and returns retaining paths for a specific node ID. This helps to understand why a node is not being garbage collected.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, verifyFilesSchema: { filePath: true, }, blockedByDialog: false, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), nodeId: zod.number().describe('The node ID to get retaining paths for.'), maxDepth: zod .number() .optional() .describe('The maximum depth to search for retaining paths.'), maxNodes: zod .number() .optional() .describe('The maximum number of nodes to return.'), maxSiblings: zod .number() .optional() .describe('The maximum number of siblings to return.'), }, handler: async (request, response, context) => { const retainingPaths = await context.getHeapSnapshotRetainingPaths(request.params.filePath, request.params.nodeId, request.params.maxDepth, request.params.maxNodes, request.params.maxSiblings); response.setHeapSnapshotRetainingPaths(retainingPaths); }, }); export const getHeapSnapshotEdges = defineTool({ name: 'get_heapsnapshot_edges', description: 'Loads a memory heapsnapshot and returns outgoing edges (references) for a specific node ID.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), nodeId: zod.number().describe('The node ID to get outgoing edges for.'), sortBy: zod .enum(['retainedSize', 'selfSize', 'name']) .optional() .describe('Sort order for edges. Default is retainedSize.'), retainedSize: byteSizeRangeSchema('Inclusive retained size range (e.g. "1MB-2MB", "-1MB", or "1MB-") for target nodes. A single value is treated as a minimum. Currently, only the lower bound is applied.').optional(), excludePrimitives: zod .boolean() .optional() .describe('Whether to exclude primitive target nodes. Default is true.'), pageIdx: zod.number().optional().describe('The page index for pagination.'), pageSize: zod.number().optional().describe('The page size for pagination.'), }, handler: async (request, response, context) => { const edges = await context.getHeapSnapshotEdges(request.params.filePath, request.params.nodeId, { sortBy: request.params.sortBy ?? 'retainedSize', // DevTools currently only supports a lower retained-size bound here. minRetainedSize: request.params.retainedSize?.min, excludePrimitives: request.params.excludePrimitives ?? true, }); response.setHeapSnapshotNodes(edges, { pageIdx: request.params.pageIdx, pageSize: request.params.pageSize, }); }, }); export const getHeapSnapshotDominators = defineTool({ name: 'get_heapsnapshot_dominators', description: 'Loads a memory heapsnapshot and returns the dominator chain for a specific node ID. This helps to identify which objects are keeping the target node alive.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), nodeId: zod .number() .describe('The node ID to get the dominator chain for.'), }, handler: async (request, response, context) => { const dominators = await context.getHeapSnapshotDominators(request.params.filePath, request.params.nodeId); response.setHeapSnapshotDominators(dominators); }, }); export const compareHeapSnapshots = defineTool({ name: 'compare_heapsnapshots', description: 'Loads two memory heapsnapshots and returns the comparison. If classIndex is provided, returns detailed diff for that class, otherwise returns summary diff.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, verifyFilesSchema: { baseFilePath: true, currentFilePath: true, }, schema: { baseFilePath: zod .string() .describe('A path to the base .heapsnapshot file (earlier snapshot).'), currentFilePath: zod .string() .describe('A path to the current .heapsnapshot file (later snapshot).'), classIndex: zod .number() .optional() .describe('Optional 0-based index of the class in the summary list to filter results, showing individual objects.'), }, blockedByDialog: false, handler: async (request, response, context) => { if (request.params.classIndex !== undefined) { const classDiffResult = await context.getHeapSnapshotDetailedClassDiff(request.params.baseFilePath, request.params.currentFilePath, request.params.classIndex); response.setHeapSnapshotDetailedClassDiff(classDiffResult); } else { const diff = await context.getHeapSnapshotClassDiffs(request.params.baseFilePath, request.params.currentFilePath); response.setHeapSnapshotClassDiffs(diff); } }, }); export const getHeapSnapshotDuplicateStrings = defineTool({ name: 'get_heapsnapshot_duplicate_strings', description: 'Loads a memory heapsnapshot and returns duplicate strings grouped by their value.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), pageIdx: zod.number().optional().describe('The page index for pagination.'), pageSize: zod.number().optional().describe('The page size for pagination.'), }, handler: async (request, response, context) => { const duplicateStrings = await context.getHeapSnapshotDuplicateStrings(request.params.filePath); response.setHeapSnapshotDuplicateStrings(duplicateStrings, { pageIdx: request.params.pageIdx, pageSize: request.params.pageSize, }); }, }); export const getHeapSnapshotObjectDetails = defineTool({ name: 'get_heapsnapshot_object_details', description: 'Loads a memory heapsnapshot and returns detailed information about a specific object by its node ID, including size, type, distance, and DOM detachedness.', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, blockedByDialog: false, verifyFilesSchema: { filePath: true, }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), nodeId: zod.number().describe('The node ID to get object details for.'), }, handler: async (request, response, context) => { const objectInfo = await context.getHeapSnapshotObjectDetails(request.params.filePath, request.params.nodeId); response.setHeapSnapshotObjectDetails(objectInfo); }, }); export const queryHeapSnapshotObjects = defineTool({ name: 'query_heapsnapshot_objects', description: 'Loads a memory heapsnapshot and queries objects matching specific filters (className, propertyName, nodeType, retainedSize, selfSize, isDetached, sortBy).', annotations: { category: ToolCategory.MEMORY, readOnlyHint: true, conditions: ['memoryDebugging'], }, blockedByDialog: false, verifyFilesSchema: { filePath: true }, schema: { filePath: zod.string().describe('A path to a .heapsnapshot file to read.'), className: zod .string() .optional() .describe('Optional regex or text matching object class name.'), propertyName: zod .string() .optional() .describe('Optional property name filter for outgoing reference edges.'), nodeType: zod .string() .optional() .describe('Optional V8 node type filter (e.g. object, closure, string, array, code).'), retainedSize: byteSizeRangeSchema('Inclusive retained size range (e.g. "1MB-2MB", "-1MB", or "1MB-"). A single value is treated as a minimum.').optional(), selfSize: byteSizeRangeSchema('Inclusive self size range (e.g. "1MB-2MB", "-1MB", or "1MB-"). A single value is treated as a minimum.').optional(), isDetached: zod .boolean() .optional() .describe('Whether to filter for detached DOM nodes.'), sortBy: zod .enum(['retainedSize', 'selfSize', 'id']) .optional() .describe('Sort order for results. Default is retainedSize.'), pageIdx: zod.number().optional().describe('The page index for pagination.'), pageSize: zod.number().optional().describe('The page size for pagination.'), }, handler: async (request, response, context) => { const range = await context.queryHeapSnapshotObjects(request.params.filePath, { className: request.params.className, propertyName: request.params.propertyName, nodeType: request.params.nodeType, minRetainedSize: request.params.retainedSize?.min, maxRetainedSize: request.params.retainedSize?.max, minSelfSize: request.params.selfSize?.min, maxSelfSize: request.params.selfSize?.max, isDetached: request.params.isDetached, sortBy: request.params.sortBy, }); response.setHeapSnapshotNodes(range, { pageIdx: request.params.pageIdx, pageSize: request.params.pageSize, }); }, }); //# sourceMappingURL=memory.js.map