nodebench-mcp 2.28.0 → 2.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Domain: openclaw (domain #40)
9
9
  */
10
+ import { getDb } from "../db.js";
10
11
  export const openclawTools = [
11
12
  // ═══ SESSION MANAGEMENT ═══
12
13
  {
@@ -1014,4 +1015,783 @@ export const openclawTools = [
1014
1015
  },
1015
1016
  },
1016
1017
  ];
1018
+ const VIEW_MANIFEST = [
1019
+ { viewId: "research", title: "Home", description: "Landing page and research hub — overview, signals, briefing, deals, changes.", paths: ["/", "/research"], actions: ["switchTab", "search"], dataEndpoints: ["forYouFeed", "morningDigest"], tags: ["home", "research", "signals", "briefing"], requiresAuth: false },
1020
+ { viewId: "for-you-feed", title: "For You", description: "Personalized feed ranked by relevance.", paths: ["/for-you"], actions: ["engageItem", "filterByTag"], dataEndpoints: ["forYouFeed"], tags: ["feed", "personalized", "discovery"], requiresAuth: false },
1021
+ { viewId: "documents", title: "My Workspace", description: "Document management — create, browse, search.", paths: ["/documents"], actions: ["createDocument", "searchDocuments"], dataEndpoints: ["documents"], tags: ["documents", "workspace"], requiresAuth: true },
1022
+ { viewId: "agents", title: "Assistants", description: "AI assistants — templates, conversations, status.", paths: ["/agents"], actions: ["startAgent", "viewAgentHistory"], dataEndpoints: ["agentTemplates", "activeAgents"], tags: ["agents", "assistants", "chat"], requiresAuth: false },
1023
+ { viewId: "calendar", title: "Calendar", description: "Events, agenda, scheduling.", paths: ["/calendar"], actions: ["createEvent", "navigateDate"], dataEndpoints: ["events"], tags: ["calendar", "events"], requiresAuth: true },
1024
+ { viewId: "signals", title: "Signals", description: "Real-time research signals stream.", paths: ["/signals"], actions: ["filterSignals"], dataEndpoints: ["signals"], tags: ["signals", "intelligence"], requiresAuth: false },
1025
+ { viewId: "funding", title: "Funding", description: "Deal flow, investment rounds, sector analysis.", paths: ["/funding"], actions: ["filterByStage", "filterBySector"], dataEndpoints: ["fundingBrief"], tags: ["funding", "deals", "investment"], requiresAuth: false },
1026
+ { viewId: "benchmarks", title: "Benchmarks", description: "Model evaluation workbench — leaderboard, scenarios.", paths: ["/benchmarks"], actions: ["runEval", "compareModels"], dataEndpoints: ["leaderboard", "scenarios"], tags: ["benchmarks", "evaluation"], requiresAuth: false },
1027
+ { viewId: "github-explorer", title: "GitHub", description: "Repository explorer — repos, PRs, code.", paths: ["/github"], actions: ["searchCode", "viewPR"], dataEndpoints: ["repos"], tags: ["github", "code"], requiresAuth: true },
1028
+ { viewId: "entity", title: "Entity Profile", description: "Deep profile for a company, person, or topic.", paths: ["/entity/:name"], actions: ["browseRelated", "viewTimeline"], dataEndpoints: ["entityProfile"], tags: ["entity", "profile"], requiresAuth: false },
1029
+ { viewId: "dogfood", title: "Quality Review", description: "QA scores, screenshots, governance.", paths: ["/dogfood"], actions: ["runQA", "viewScreenshots"], dataEndpoints: ["qaResults"], tags: ["quality", "review"], requiresAuth: true },
1030
+ { viewId: "activity", title: "Activity", description: "Public activity feed.", paths: ["/activity"], actions: ["filterActivity"], dataEndpoints: ["activity"], tags: ["activity", "feed"], requiresAuth: false },
1031
+ { viewId: "spreadsheets", title: "Spreadsheets", description: "Spreadsheet editor.", paths: ["/spreadsheets"], actions: ["createSpreadsheet"], dataEndpoints: ["spreadsheets"], tags: ["spreadsheets", "data"], requiresAuth: true },
1032
+ { viewId: "roadmap", title: "Roadmap", description: "Interactive product roadmap.", paths: ["/roadmap"], actions: ["navigatePhase"], dataEndpoints: [], tags: ["roadmap", "milestones"], requiresAuth: false },
1033
+ { viewId: "timeline", title: "Timeline", description: "Chronological timeline.", paths: ["/timeline"], actions: ["scrollToDate"], dataEndpoints: [], tags: ["timeline", "history"], requiresAuth: false },
1034
+ { viewId: "showcase", title: "Showcase", description: "Feature demo gallery.", paths: ["/showcase"], actions: [], dataEndpoints: [], tags: ["showcase", "demo"], requiresAuth: false },
1035
+ { viewId: "footnotes", title: "Sources", description: "Citation library with verification.", paths: ["/footnotes"], actions: ["searchSources"], dataEndpoints: ["citations"], tags: ["sources", "citations"], requiresAuth: false },
1036
+ { viewId: "analytics-hitl", title: "Review Queue", description: "Human-in-the-loop review.", paths: ["/analytics/hitl"], actions: ["approveItem", "flagItem"], dataEndpoints: ["reviewQueue"], tags: ["analytics", "review"], requiresAuth: true },
1037
+ { viewId: "analytics-components", title: "Performance Analytics", description: "Component performance metrics.", paths: ["/analytics/components"], actions: [], dataEndpoints: ["componentMetrics"], tags: ["analytics", "performance"], requiresAuth: true },
1038
+ { viewId: "analytics-recommendations", title: "Feedback", description: "Recommendation feedback dashboard.", paths: ["/analytics/recommendations"], actions: [], dataEndpoints: ["feedbackData"], tags: ["analytics", "feedback"], requiresAuth: true },
1039
+ { viewId: "cost-dashboard", title: "Usage & Costs", description: "API usage and cost tracking.", paths: ["/cost"], actions: ["setAlert"], dataEndpoints: ["costData"], tags: ["costs", "usage"], requiresAuth: true },
1040
+ { viewId: "industry-updates", title: "Industry News", description: "Curated industry updates.", paths: ["/industry"], actions: ["filterByTopic"], dataEndpoints: ["industryUpdates"], tags: ["industry", "news"], requiresAuth: false },
1041
+ { viewId: "document-recommendations", title: "Suggestions", description: "AI document recommendations.", paths: ["/recommendations"], actions: ["dismiss", "save"], dataEndpoints: ["recommendations"], tags: ["recommendations", "discover"], requiresAuth: true },
1042
+ { viewId: "agent-marketplace", title: "Agent Templates", description: "Browse agent templates.", paths: ["/marketplace"], actions: ["installTemplate"], dataEndpoints: ["templates"], tags: ["agents", "marketplace"], requiresAuth: true },
1043
+ { viewId: "pr-suggestions", title: "PR Suggestions", description: "AI pull request suggestions.", paths: ["/pr-suggestions"], actions: ["applySuggestion"], dataEndpoints: ["prSuggestions"], tags: ["pull-requests", "code-review"], requiresAuth: true },
1044
+ { viewId: "linkedin-posts", title: "LinkedIn Posts", description: "Post archive with engagement.", paths: ["/linkedin"], actions: ["searchPosts"], dataEndpoints: ["posts"], tags: ["linkedin", "social"], requiresAuth: true },
1045
+ { viewId: "mcp-ledger", title: "Activity Log", description: "Tool call audit trail.", paths: ["/mcp-ledger"], actions: ["filterByTool", "filterByDate"], dataEndpoints: ["toolCalls"], tags: ["audit", "tools"], requiresAuth: true },
1046
+ ];
1047
+ // ---------------------------------------------------------------------------
1048
+ // MCP Gateway helper — calls Convex backend via HTTP
1049
+ // ---------------------------------------------------------------------------
1050
+ async function callMcpGateway(fn, args = {}) {
1051
+ const siteUrl = process.env.CONVEX_SITE_URL || process.env.VITE_CONVEX_URL;
1052
+ const secret = process.env.MCP_SECRET;
1053
+ if (!siteUrl || !secret) {
1054
+ return { success: false, error: "Missing CONVEX_SITE_URL or MCP_SECRET. Cannot call backend." };
1055
+ }
1056
+ try {
1057
+ const res = await fetch(`${siteUrl.replace(/\/$/, "")}/api/mcpGateway`, {
1058
+ method: "POST",
1059
+ headers: { "Content-Type": "application/json", "x-mcp-secret": secret },
1060
+ body: JSON.stringify({ fn, args }),
1061
+ });
1062
+ const data = await res.json();
1063
+ return data;
1064
+ }
1065
+ catch (e) {
1066
+ return { success: false, error: `Gateway call failed: ${e.message}` };
1067
+ }
1068
+ }
1069
+ // Tool name → gateway function mapping (for invoke_view_tool routing)
1070
+ const TOOL_GATEWAY_MAP = {
1071
+ nb_search_research: { gatewayFn: "hybridSearch", mapArgs: (a) => ({ query: String(a.query ?? ""), topK: Number(a.limit ?? 10) }) },
1072
+ nb_get_signals: { gatewayFn: "getSignalTimeseries" },
1073
+ nb_get_feed_items: { gatewayFn: "getPublicForYouFeed", mapArgs: (a) => ({ limit: Number(a.limit ?? 20) }) },
1074
+ nb_list_documents: { gatewayFn: "mcpListDocuments" },
1075
+ nb_create_document: { gatewayFn: "mcpCreateDocument" },
1076
+ nb_search_documents: { gatewayFn: "mcpSearchDocuments" },
1077
+ nb_get_funding_brief: { gatewayFn: "getDealFlow" },
1078
+ nb_list_deals: { gatewayFn: "getDealFlow" },
1079
+ nb_filter_by_stage: { gatewayFn: "getDealFlow", mapArgs: (a) => ({ stage: a.stage }) },
1080
+ nb_list_repos: { gatewayFn: "getTrendingRepos", mapArgs: (a) => ({ limit: Number(a.limit ?? 20) }) },
1081
+ nb_list_signals: { gatewayFn: "getSignalTimeseries" },
1082
+ nb_get_signal_detail: { gatewayFn: "getSignalTimeseries" },
1083
+ };
1084
+ // Endpoint name → gateway function mapping (for query_view_data routing)
1085
+ const ENDPOINT_GATEWAY_MAP = {
1086
+ forYouFeed: "getPublicForYouFeed",
1087
+ morningDigest: "getLatestDashboardSnapshot",
1088
+ signals: "getSignalTimeseries",
1089
+ fundingBrief: "getDealFlow",
1090
+ repos: "getTrendingRepos",
1091
+ documents: "mcpListDocuments",
1092
+ entityProfile: "getLatestPublicDossier",
1093
+ citations: "getPublicThreads",
1094
+ };
1095
+ // Per-view tool map — what tools each view exposes (matches src/lib/viewToolMap.ts)
1096
+ const VIEW_TOOLS = {
1097
+ research: [
1098
+ { name: "nb_search_research", description: "Search research signals and briefings" },
1099
+ { name: "nb_get_signals", description: "Get latest research signals" },
1100
+ { name: "nb_switch_research_tab", description: "Switch research hub tab" },
1101
+ ],
1102
+ "for-you-feed": [
1103
+ { name: "nb_get_feed_items", description: "Get personalized feed items" },
1104
+ { name: "nb_engage_feed_item", description: "Record engagement on feed item" },
1105
+ ],
1106
+ documents: [
1107
+ { name: "nb_list_documents", description: "List workspace documents" },
1108
+ { name: "nb_create_document", description: "Create new document" },
1109
+ { name: "nb_search_documents", description: "Search document content" },
1110
+ ],
1111
+ agents: [
1112
+ { name: "nb_list_agents", description: "List agent templates and active threads" },
1113
+ { name: "nb_start_agent", description: "Start new agent conversation" },
1114
+ { name: "nb_get_agent_status", description: "Get agent thread status" },
1115
+ ],
1116
+ calendar: [
1117
+ { name: "nb_list_events", description: "List calendar events" },
1118
+ { name: "nb_create_event", description: "Create calendar event" },
1119
+ ],
1120
+ funding: [
1121
+ { name: "nb_get_funding_brief", description: "Get funding intelligence" },
1122
+ { name: "nb_list_deals", description: "List funding deals" },
1123
+ { name: "nb_filter_by_stage", description: "Filter by funding stage" },
1124
+ ],
1125
+ benchmarks: [
1126
+ { name: "nb_get_leaderboard", description: "Get model leaderboard" },
1127
+ { name: "nb_list_scenarios", description: "List eval scenarios" },
1128
+ ],
1129
+ "github-explorer": [
1130
+ { name: "nb_list_repos", description: "List tracked repos" },
1131
+ { name: "nb_get_pr_status", description: "Get PR status" },
1132
+ ],
1133
+ signals: [
1134
+ { name: "nb_list_signals", description: "List public signals" },
1135
+ { name: "nb_get_signal_detail", description: "Get signal details" },
1136
+ ],
1137
+ dogfood: [
1138
+ { name: "nb_get_qa_results", description: "Get QA pipeline results" },
1139
+ { name: "nb_view_screenshots", description: "Get route screenshots" },
1140
+ ],
1141
+ };
1142
+ // In-memory write-through cache (hot reads, SQLite for durability)
1143
+ const agentViewSessions = new Map();
1144
+ function ensureTraverseTable() {
1145
+ try {
1146
+ const db = getDb();
1147
+ db.exec(`CREATE TABLE IF NOT EXISTS traverse_sessions (
1148
+ session_id TEXT PRIMARY KEY,
1149
+ current_view TEXT NOT NULL,
1150
+ history TEXT NOT NULL DEFAULT '[]',
1151
+ interactions TEXT NOT NULL DEFAULT '[]',
1152
+ started_at INTEGER NOT NULL,
1153
+ updated_at INTEGER NOT NULL
1154
+ )`);
1155
+ }
1156
+ catch {
1157
+ // SQLite not available — fall back to in-memory only
1158
+ }
1159
+ }
1160
+ function persistSession(sessionId, session) {
1161
+ agentViewSessions.set(sessionId, session);
1162
+ try {
1163
+ const db = getDb();
1164
+ ensureTraverseTable();
1165
+ db.prepare(`INSERT OR REPLACE INTO traverse_sessions (session_id, current_view, history, interactions, started_at, updated_at)
1166
+ VALUES (?, ?, ?, ?, ?, ?)`).run(sessionId, session.currentView, JSON.stringify(session.history), JSON.stringify(session.interactions), session.startedAt, Date.now());
1167
+ }
1168
+ catch {
1169
+ // SQLite write failed — session still in memory
1170
+ }
1171
+ }
1172
+ function loadSession(sessionId) {
1173
+ // Check in-memory cache first
1174
+ const cached = agentViewSessions.get(sessionId);
1175
+ if (cached)
1176
+ return cached;
1177
+ // Try SQLite
1178
+ try {
1179
+ const db = getDb();
1180
+ ensureTraverseTable();
1181
+ const row = db.prepare("SELECT * FROM traverse_sessions WHERE session_id = ?").get(sessionId);
1182
+ if (row) {
1183
+ const session = {
1184
+ currentView: row.current_view,
1185
+ history: JSON.parse(row.history),
1186
+ interactions: JSON.parse(row.interactions),
1187
+ startedAt: row.started_at,
1188
+ };
1189
+ agentViewSessions.set(sessionId, session);
1190
+ return session;
1191
+ }
1192
+ }
1193
+ catch {
1194
+ // SQLite read failed
1195
+ }
1196
+ return undefined;
1197
+ }
1198
+ function loadAllSessions() {
1199
+ // Load from SQLite to merge with in-memory
1200
+ try {
1201
+ const db = getDb();
1202
+ ensureTraverseTable();
1203
+ const rows = db.prepare("SELECT * FROM traverse_sessions ORDER BY updated_at DESC LIMIT 50").all();
1204
+ for (const row of rows) {
1205
+ if (!agentViewSessions.has(row.session_id)) {
1206
+ agentViewSessions.set(row.session_id, {
1207
+ currentView: row.current_view,
1208
+ history: JSON.parse(row.history),
1209
+ interactions: JSON.parse(row.interactions),
1210
+ startedAt: row.started_at,
1211
+ });
1212
+ }
1213
+ }
1214
+ }
1215
+ catch {
1216
+ // SQLite not available
1217
+ }
1218
+ return Array.from(agentViewSessions.entries());
1219
+ }
1220
+ export const agentTraverseTools = [
1221
+ // ═══ VIEW DISCOVERY ═══
1222
+ {
1223
+ name: "list_available_views",
1224
+ description: "List all available views in the NodeBench AI frontend with titles, " +
1225
+ "descriptions, actions, data endpoints, and tags. Use this to discover " +
1226
+ "what the app offers before navigating. Returns a manifest of 27 views.",
1227
+ inputSchema: {
1228
+ type: "object",
1229
+ properties: {
1230
+ search: {
1231
+ type: "string",
1232
+ description: "Filter views by keyword (matches title, description, tags)",
1233
+ },
1234
+ includeAuthOnly: {
1235
+ type: "boolean",
1236
+ description: "Include views that require authentication (default: true)",
1237
+ },
1238
+ },
1239
+ },
1240
+ handler: async (args) => {
1241
+ let views = [...VIEW_MANIFEST];
1242
+ if (args.search) {
1243
+ const q = String(args.search).toLowerCase();
1244
+ views = views.filter((v) => v.title.toLowerCase().includes(q) ||
1245
+ v.description.toLowerCase().includes(q) ||
1246
+ v.tags.some((t) => t.includes(q)));
1247
+ }
1248
+ if (args.includeAuthOnly === false) {
1249
+ views = views.filter((v) => !v.requiresAuth);
1250
+ }
1251
+ return {
1252
+ success: true,
1253
+ views: views.map((v) => ({
1254
+ viewId: v.viewId,
1255
+ title: v.title,
1256
+ description: v.description,
1257
+ path: v.paths[0] ?? `/${v.viewId}`,
1258
+ actions: v.actions,
1259
+ dataEndpoints: v.dataEndpoints,
1260
+ tags: v.tags,
1261
+ requiresAuth: v.requiresAuth,
1262
+ hasViewTools: !!VIEW_TOOLS[v.viewId],
1263
+ viewToolCount: VIEW_TOOLS[v.viewId]?.length ?? 0,
1264
+ })),
1265
+ totalViews: views.length,
1266
+ quickRef: {
1267
+ nextAction: "Pick a view and navigate to it with navigate_to_view.",
1268
+ nextTools: ["navigate_to_view", "get_view_capabilities"],
1269
+ methodology: "agent_traversal",
1270
+ },
1271
+ };
1272
+ },
1273
+ },
1274
+ {
1275
+ name: "get_view_capabilities",
1276
+ description: "Get full capabilities for a specific view — actions, data endpoints, " +
1277
+ "available per-view tools, tags, and auth requirements. Use this to " +
1278
+ "understand what you can do on a view before interacting with it.",
1279
+ inputSchema: {
1280
+ type: "object",
1281
+ properties: {
1282
+ viewId: {
1283
+ type: "string",
1284
+ description: "View ID (e.g., 'research', 'funding', 'agents'). Use list_available_views to discover valid IDs.",
1285
+ },
1286
+ },
1287
+ required: ["viewId"],
1288
+ },
1289
+ handler: async (args) => {
1290
+ const viewId = String(args.viewId);
1291
+ const entry = VIEW_MANIFEST.find((v) => v.viewId === viewId);
1292
+ if (!entry) {
1293
+ return {
1294
+ success: false,
1295
+ error: `Unknown view: ${viewId}`,
1296
+ availableViews: VIEW_MANIFEST.map((v) => v.viewId),
1297
+ };
1298
+ }
1299
+ const viewTools = VIEW_TOOLS[viewId] ?? [];
1300
+ return {
1301
+ success: true,
1302
+ view: entry,
1303
+ viewTools,
1304
+ viewToolCount: viewTools.length,
1305
+ quickRef: {
1306
+ nextAction: `Navigate to ${entry.title} with navigate_to_view, or invoke per-view tools with invoke_view_tool.`,
1307
+ nextTools: ["navigate_to_view", "invoke_view_tool"],
1308
+ methodology: "agent_traversal",
1309
+ },
1310
+ };
1311
+ },
1312
+ },
1313
+ // ═══ VIEW NAVIGATION ═══
1314
+ {
1315
+ name: "navigate_to_view",
1316
+ description: "Navigate to a specific view in the NodeBench AI frontend. " +
1317
+ "Creates a navigation intent (via Convex agentNavigation) and returns " +
1318
+ "the target view's capabilities so you know what to expect. " +
1319
+ "Tracks navigation in the session history for audit.",
1320
+ inputSchema: {
1321
+ type: "object",
1322
+ properties: {
1323
+ viewId: {
1324
+ type: "string",
1325
+ description: "Target view ID (e.g., 'research', 'funding', 'agents')",
1326
+ },
1327
+ reason: {
1328
+ type: "string",
1329
+ description: "Why you're navigating (logged for audit trail)",
1330
+ },
1331
+ sessionId: {
1332
+ type: "string",
1333
+ description: "Session ID for state tracking (optional — auto-creates if omitted)",
1334
+ },
1335
+ },
1336
+ required: ["viewId"],
1337
+ },
1338
+ handler: async (args) => {
1339
+ const viewId = String(args.viewId);
1340
+ const reason = args.reason ?? "agent_navigation";
1341
+ const sessionId = args.sessionId ?? `traverse_${Date.now()}`;
1342
+ const entry = VIEW_MANIFEST.find((v) => v.viewId === viewId);
1343
+ if (!entry) {
1344
+ return {
1345
+ success: false,
1346
+ error: `Unknown view: ${viewId}`,
1347
+ availableViews: VIEW_MANIFEST.map((v) => v.viewId),
1348
+ };
1349
+ }
1350
+ // Track in session state (SQLite-backed)
1351
+ let session = loadSession(sessionId);
1352
+ if (!session) {
1353
+ session = { currentView: viewId, history: [], interactions: [], startedAt: Date.now() };
1354
+ }
1355
+ session.currentView = viewId;
1356
+ session.history.push({ view: viewId, timestamp: Date.now(), reason });
1357
+ persistSession(sessionId, session);
1358
+ const viewTools = VIEW_TOOLS[viewId] ?? [];
1359
+ return {
1360
+ success: true,
1361
+ navigated: true,
1362
+ sessionId,
1363
+ targetView: entry,
1364
+ availableActions: entry.actions,
1365
+ availableTools: viewTools.map((t) => t.name),
1366
+ dataEndpoints: entry.dataEndpoints,
1367
+ instructions: [
1368
+ `Navigated to ${entry.title} (${entry.description}).`,
1369
+ entry.actions.length > 0
1370
+ ? `Available actions: ${entry.actions.join(", ")}.`
1371
+ : "No view-specific actions on this view.",
1372
+ viewTools.length > 0
1373
+ ? `Per-view tools: ${viewTools.map((t) => t.name).join(", ")}. Use invoke_view_tool to call them.`
1374
+ : "No per-view tools on this view.",
1375
+ ],
1376
+ quickRef: {
1377
+ nextAction: viewTools.length > 0
1378
+ ? `Use invoke_view_tool to interact with ${entry.title}.`
1379
+ : `Read data from ${entry.title} using query_view_data.`,
1380
+ nextTools: viewTools.length > 0
1381
+ ? ["invoke_view_tool", "query_view_data", "get_view_state"]
1382
+ : ["query_view_data", "get_view_state", "navigate_to_view"],
1383
+ methodology: "agent_traversal",
1384
+ },
1385
+ };
1386
+ },
1387
+ },
1388
+ // ═══ VIEW INTERACTION ═══
1389
+ {
1390
+ name: "invoke_view_tool",
1391
+ description: "Invoke a per-view tool on the current or specified view. " +
1392
+ "Each view exposes contextual tools (e.g., 'nb_search_research' on " +
1393
+ "the research view, 'nb_list_deals' on funding). Call get_view_capabilities " +
1394
+ "first to see available tools. Session-injected for audit trail.",
1395
+ inputSchema: {
1396
+ type: "object",
1397
+ properties: {
1398
+ tool: {
1399
+ type: "string",
1400
+ description: "Tool name (e.g., 'nb_search_research', 'nb_list_deals')",
1401
+ },
1402
+ args: {
1403
+ type: "object",
1404
+ description: "Arguments to pass to the tool",
1405
+ additionalProperties: true,
1406
+ },
1407
+ viewId: {
1408
+ type: "string",
1409
+ description: "View context (auto-detected from session if omitted)",
1410
+ },
1411
+ sessionId: {
1412
+ type: "string",
1413
+ description: "Session ID for state tracking",
1414
+ },
1415
+ },
1416
+ required: ["tool"],
1417
+ },
1418
+ handler: async (args) => {
1419
+ const toolName = String(args.tool);
1420
+ const toolArgs = args.args ?? {};
1421
+ const sessionId = args.sessionId ?? "default";
1422
+ // Resolve view from session (SQLite-backed) or explicit arg
1423
+ const session = loadSession(sessionId);
1424
+ const viewId = args.viewId ?? session?.currentView ?? "research";
1425
+ // Validate the tool exists for this view
1426
+ const viewTools = VIEW_TOOLS[viewId] ?? [];
1427
+ const toolDef = viewTools.find((t) => t.name === toolName);
1428
+ if (!toolDef) {
1429
+ // Try all views
1430
+ const allToolNames = [];
1431
+ for (const [vid, tools] of Object.entries(VIEW_TOOLS)) {
1432
+ for (const t of tools) {
1433
+ allToolNames.push(`${t.name} (${vid})`);
1434
+ }
1435
+ }
1436
+ return {
1437
+ success: false,
1438
+ error: `Tool "${toolName}" not found on view "${viewId}".`,
1439
+ availableOnThisView: viewTools.map((t) => t.name),
1440
+ allViewTools: allToolNames,
1441
+ };
1442
+ }
1443
+ // Track interaction (SQLite-backed)
1444
+ if (session) {
1445
+ session.interactions.push({
1446
+ view: viewId,
1447
+ action: toolName,
1448
+ args: toolArgs,
1449
+ timestamp: Date.now(),
1450
+ });
1451
+ persistSession(sessionId, session);
1452
+ }
1453
+ // Route to real backend via MCP Gateway
1454
+ const route = TOOL_GATEWAY_MAP[toolName];
1455
+ let result;
1456
+ if (route) {
1457
+ const mappedArgs = route.mapArgs ? route.mapArgs(toolArgs) : toolArgs;
1458
+ const gwResult = await callMcpGateway(route.gatewayFn, mappedArgs);
1459
+ result = gwResult.success
1460
+ ? { data: gwResult.data, source: "convex_gateway", gatewayFn: route.gatewayFn }
1461
+ : { error: gwResult.error, source: "convex_gateway", gatewayFn: route.gatewayFn };
1462
+ }
1463
+ else {
1464
+ result = {
1465
+ note: `Tool ${toolName} has no gateway mapping. Frontend-only interaction.`,
1466
+ source: "stub",
1467
+ };
1468
+ }
1469
+ return {
1470
+ success: true,
1471
+ tool: toolName,
1472
+ view: viewId,
1473
+ description: toolDef.description,
1474
+ args: toolArgs,
1475
+ result,
1476
+ quickRef: {
1477
+ nextAction: "Check results. Navigate to another view or invoke more tools.",
1478
+ nextTools: ["invoke_view_tool", "navigate_to_view", "get_view_state"],
1479
+ methodology: "agent_traversal",
1480
+ },
1481
+ };
1482
+ },
1483
+ },
1484
+ {
1485
+ name: "query_view_data",
1486
+ description: "Query data from a view's data endpoints. Each view has named " +
1487
+ "endpoints (e.g., 'forYouFeed', 'fundingBrief', 'documents'). " +
1488
+ "Use get_view_capabilities to see available endpoints first.",
1489
+ inputSchema: {
1490
+ type: "object",
1491
+ properties: {
1492
+ viewId: {
1493
+ type: "string",
1494
+ description: "View to query data from",
1495
+ },
1496
+ endpoint: {
1497
+ type: "string",
1498
+ description: "Data endpoint name (e.g., 'forYouFeed', 'fundingBrief')",
1499
+ },
1500
+ params: {
1501
+ type: "object",
1502
+ description: "Query parameters (view-specific)",
1503
+ additionalProperties: true,
1504
+ },
1505
+ },
1506
+ required: ["viewId", "endpoint"],
1507
+ },
1508
+ handler: async (args) => {
1509
+ const viewId = String(args.viewId);
1510
+ const endpoint = String(args.endpoint);
1511
+ const params = args.params ?? {};
1512
+ const entry = VIEW_MANIFEST.find((v) => v.viewId === viewId);
1513
+ if (!entry) {
1514
+ return { success: false, error: `Unknown view: ${viewId}` };
1515
+ }
1516
+ if (!entry.dataEndpoints.includes(endpoint)) {
1517
+ return {
1518
+ success: false,
1519
+ error: `Endpoint "${endpoint}" not found on view "${viewId}".`,
1520
+ availableEndpoints: entry.dataEndpoints,
1521
+ };
1522
+ }
1523
+ // Route to real backend via MCP Gateway
1524
+ const gatewayFn = ENDPOINT_GATEWAY_MAP[endpoint];
1525
+ let result;
1526
+ if (gatewayFn) {
1527
+ const gwResult = await callMcpGateway(gatewayFn, params);
1528
+ result = gwResult.success
1529
+ ? { data: gwResult.data, source: "convex_gateway", gatewayFn }
1530
+ : { error: gwResult.error, source: "convex_gateway", gatewayFn };
1531
+ }
1532
+ else {
1533
+ result = {
1534
+ note: `Endpoint "${endpoint}" has no gateway mapping. ` +
1535
+ `Available mapped endpoints: ${Object.keys(ENDPOINT_GATEWAY_MAP).join(", ")}.`,
1536
+ source: "stub",
1537
+ };
1538
+ }
1539
+ return {
1540
+ success: true,
1541
+ viewId,
1542
+ endpoint,
1543
+ params,
1544
+ result,
1545
+ quickRef: {
1546
+ nextAction: "Process the data. Navigate to another view or invoke view tools.",
1547
+ nextTools: ["invoke_view_tool", "navigate_to_view", "traverse_feed"],
1548
+ methodology: "agent_traversal",
1549
+ },
1550
+ };
1551
+ },
1552
+ },
1553
+ // ═══ FEED TRAVERSAL (Moltbook pattern) ═══
1554
+ {
1555
+ name: "traverse_feed",
1556
+ description: "Traverse content feeds with Moltbook-style sorting. " +
1557
+ "Feed types: research, signals, documents, agents, funding, activity. " +
1558
+ "Sort by: hot (engagement-weighted recency), new (chronological), " +
1559
+ "top (highest score), rising (fastest growing). " +
1560
+ "Supports limit and cursor-based pagination.",
1561
+ inputSchema: {
1562
+ type: "object",
1563
+ properties: {
1564
+ feedType: {
1565
+ type: "string",
1566
+ enum: ["research", "signals", "documents", "agents", "funding", "activity"],
1567
+ description: "Which feed to traverse",
1568
+ },
1569
+ sort: {
1570
+ type: "string",
1571
+ enum: ["hot", "new", "top", "rising"],
1572
+ description: "Sort order (default: hot)",
1573
+ },
1574
+ limit: {
1575
+ type: "number",
1576
+ description: "Items to return (default: 10, max: 50)",
1577
+ },
1578
+ cursor: {
1579
+ type: "string",
1580
+ description: "Pagination cursor from previous response",
1581
+ },
1582
+ tags: {
1583
+ type: "array",
1584
+ items: { type: "string" },
1585
+ description: "Filter by tags",
1586
+ },
1587
+ },
1588
+ required: ["feedType"],
1589
+ },
1590
+ handler: async (args) => {
1591
+ const feedType = String(args.feedType);
1592
+ const sort = args.sort ?? "hot";
1593
+ const limit = Math.min(args.limit ?? 10, 50);
1594
+ const cursor = args.cursor;
1595
+ const tags = args.tags ?? [];
1596
+ // Map feed type to view
1597
+ const feedViewMap = {
1598
+ research: "for-you-feed",
1599
+ signals: "signals",
1600
+ documents: "documents",
1601
+ agents: "agents",
1602
+ funding: "funding",
1603
+ activity: "activity",
1604
+ };
1605
+ const targetView = feedViewMap[feedType] ?? "for-you-feed";
1606
+ // Route feed traversal to real backend
1607
+ const feedGatewayMap = {
1608
+ research: "getPublicForYouFeed",
1609
+ signals: "getSignalTimeseries",
1610
+ documents: "mcpListDocuments",
1611
+ funding: "getDealFlow",
1612
+ activity: "getLatestDashboardSnapshot",
1613
+ };
1614
+ const feedGwFn = feedGatewayMap[feedType];
1615
+ let feedResult;
1616
+ if (feedGwFn) {
1617
+ const gwResult = await callMcpGateway(feedGwFn, { limit, sort });
1618
+ feedResult = gwResult.success
1619
+ ? { data: gwResult.data, source: "convex_gateway", gatewayFn: feedGwFn }
1620
+ : { error: gwResult.error, source: "convex_gateway", gatewayFn: feedGwFn };
1621
+ }
1622
+ else {
1623
+ feedResult = { note: `No gateway mapping for feed type "${feedType}".`, source: "stub" };
1624
+ }
1625
+ return {
1626
+ success: true,
1627
+ feedType,
1628
+ sort,
1629
+ limit,
1630
+ cursor: cursor ?? null,
1631
+ tags,
1632
+ targetView,
1633
+ result: feedResult,
1634
+ quickRef: {
1635
+ nextAction: "Process items. Use cursor for next page, or switch feed type.",
1636
+ nextTools: ["traverse_feed", "navigate_to_view", "invoke_view_tool"],
1637
+ methodology: "agent_traversal",
1638
+ },
1639
+ };
1640
+ },
1641
+ },
1642
+ // ═══ SESSION STATE ═══
1643
+ {
1644
+ name: "get_view_state",
1645
+ description: "Get the current agent traversal session state — which view you're on, " +
1646
+ "navigation history, interaction log, and session duration. " +
1647
+ "Use this for self-awareness and audit.",
1648
+ inputSchema: {
1649
+ type: "object",
1650
+ properties: {
1651
+ sessionId: {
1652
+ type: "string",
1653
+ description: "Session ID (uses most recent if omitted)",
1654
+ },
1655
+ },
1656
+ },
1657
+ handler: async (args) => {
1658
+ const sessionId = args.sessionId;
1659
+ if (sessionId) {
1660
+ const session = loadSession(sessionId);
1661
+ if (!session) {
1662
+ return { success: false, error: `Session not found: ${sessionId}` };
1663
+ }
1664
+ return {
1665
+ success: true,
1666
+ sessionId,
1667
+ currentView: session.currentView,
1668
+ viewsVisited: session.history.length,
1669
+ history: session.history.slice(-10),
1670
+ interactions: session.interactions.slice(-10),
1671
+ durationMs: Date.now() - session.startedAt,
1672
+ };
1673
+ }
1674
+ // Return summary of all sessions (loads from SQLite + in-memory)
1675
+ const allSessions = loadAllSessions();
1676
+ const sessions = allSessions.map(([id, s]) => ({
1677
+ sessionId: id,
1678
+ currentView: s.currentView,
1679
+ viewsVisited: s.history.length,
1680
+ interactions: s.interactions.length,
1681
+ durationMs: Date.now() - s.startedAt,
1682
+ }));
1683
+ return {
1684
+ success: true,
1685
+ activeSessions: sessions.length,
1686
+ sessions,
1687
+ quickRef: {
1688
+ nextAction: "Review session state. Continue navigating or end session.",
1689
+ nextTools: ["navigate_to_view", "list_available_views"],
1690
+ methodology: "agent_traversal",
1691
+ },
1692
+ };
1693
+ },
1694
+ },
1695
+ {
1696
+ name: "get_traversal_plan",
1697
+ description: "Generate a traversal plan for accomplishing a goal across multiple views. " +
1698
+ "Given a goal (e.g., 'find recent AI funding deals and create a summary document'), " +
1699
+ "returns an ordered list of views to visit and actions to take on each.",
1700
+ inputSchema: {
1701
+ type: "object",
1702
+ properties: {
1703
+ goal: {
1704
+ type: "string",
1705
+ description: "What you want to accomplish (natural language)",
1706
+ },
1707
+ constraints: {
1708
+ type: "object",
1709
+ properties: {
1710
+ maxViews: { type: "number", description: "Max views to visit (default: 5)" },
1711
+ requiresAuth: { type: "boolean", description: "Can use auth-required views?" },
1712
+ preferredViews: {
1713
+ type: "array",
1714
+ items: { type: "string" },
1715
+ description: "Preferred views to include",
1716
+ },
1717
+ },
1718
+ },
1719
+ },
1720
+ required: ["goal"],
1721
+ },
1722
+ handler: async (args) => {
1723
+ const goal = String(args.goal).toLowerCase();
1724
+ const maxViews = args.constraints?.maxViews ?? 5;
1725
+ const requiresAuth = args.constraints?.requiresAuth ?? true;
1726
+ const preferredViews = args.constraints?.preferredViews ?? [];
1727
+ // Score each view by relevance to the goal
1728
+ const scored = VIEW_MANIFEST
1729
+ .filter((v) => requiresAuth || !v.requiresAuth)
1730
+ .map((v) => {
1731
+ let score = 0;
1732
+ // Tag matching
1733
+ for (const tag of v.tags) {
1734
+ if (goal.includes(tag))
1735
+ score += 3;
1736
+ }
1737
+ // Title matching
1738
+ if (goal.includes(v.title.toLowerCase()))
1739
+ score += 5;
1740
+ // Description keyword matching
1741
+ const descWords = v.description.toLowerCase().split(/\s+/);
1742
+ for (const word of descWords) {
1743
+ if (word.length > 3 && goal.includes(word))
1744
+ score += 1;
1745
+ }
1746
+ // Action matching
1747
+ for (const action of v.actions) {
1748
+ if (goal.includes(action.toLowerCase()))
1749
+ score += 2;
1750
+ }
1751
+ // Preferred view bonus
1752
+ if (preferredViews.includes(v.viewId))
1753
+ score += 10;
1754
+ // Has data endpoints = more useful
1755
+ score += v.dataEndpoints.length;
1756
+ // Has view tools = more interactive
1757
+ score += (VIEW_TOOLS[v.viewId]?.length ?? 0) * 0.5;
1758
+ return { view: v, score };
1759
+ })
1760
+ .filter((s) => s.score > 0)
1761
+ .sort((a, b) => b.score - a.score)
1762
+ .slice(0, maxViews);
1763
+ const plan = scored.map((s, i) => ({
1764
+ step: i + 1,
1765
+ viewId: s.view.viewId,
1766
+ title: s.view.title,
1767
+ relevance: Math.round(s.score),
1768
+ suggestedActions: s.view.actions.filter((a) => {
1769
+ const aLower = a.toLowerCase();
1770
+ return goal.includes(aLower) || goal.includes(aLower.replace(/([A-Z])/g, " $1").toLowerCase());
1771
+ }),
1772
+ availableTools: VIEW_TOOLS[s.view.viewId]?.map((t) => t.name) ?? [],
1773
+ dataEndpoints: s.view.dataEndpoints,
1774
+ }));
1775
+ return {
1776
+ success: true,
1777
+ goal,
1778
+ plan,
1779
+ totalSteps: plan.length,
1780
+ estimatedViews: plan.length,
1781
+ instructions: [
1782
+ `Traversal plan for: "${args.goal}"`,
1783
+ `${plan.length} views to visit in order of relevance.`,
1784
+ "Use navigate_to_view for each step, then invoke_view_tool or query_view_data.",
1785
+ ],
1786
+ quickRef: {
1787
+ nextAction: plan.length > 0
1788
+ ? `Start with navigate_to_view("${plan[0].viewId}").`
1789
+ : "Refine your goal or use list_available_views to explore.",
1790
+ nextTools: ["navigate_to_view", "invoke_view_tool", "query_view_data"],
1791
+ methodology: "agent_traversal",
1792
+ },
1793
+ };
1794
+ },
1795
+ },
1796
+ ];
1017
1797
  //# sourceMappingURL=openclawTools.js.map