synabun 2026.3.31007 → 2026.4.1

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +51 -52
  2. package/hooks/claude-code/prompt-submit.mjs +6 -6
  3. package/mcp-server/dist/services/neural-interface.d.ts +20 -19
  4. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  5. package/mcp-server/dist/services/neural-interface.js +52 -45
  6. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  7. package/mcp-server/dist/tools/browser-advanced.d.ts +4 -0
  8. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
  9. package/mcp-server/dist/tools/browser-advanced.js +7 -4
  10. package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
  11. package/mcp-server/dist/tools/browser-interact.d.ts +16 -0
  12. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
  13. package/mcp-server/dist/tools/browser-interact.js +25 -16
  14. package/mcp-server/dist/tools/browser-interact.js.map +1 -1
  15. package/mcp-server/dist/tools/browser-navigate.d.ts +8 -0
  16. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
  17. package/mcp-server/dist/tools/browser-navigate.js +13 -8
  18. package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
  19. package/mcp-server/dist/tools/browser-observe.d.ts +48 -0
  20. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
  21. package/mcp-server/dist/tools/browser-observe.js +74 -49
  22. package/mcp-server/dist/tools/browser-observe.js.map +1 -1
  23. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +20 -0
  24. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
  25. package/mcp-server/dist/tools/leonardo-browser-tools.js +39 -34
  26. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
  27. package/mcp-server/src/services/neural-interface.ts +58 -46
  28. package/mcp-server/src/tools/browser-advanced.ts +10 -5
  29. package/mcp-server/src/tools/browser-interact.ts +34 -24
  30. package/mcp-server/src/tools/browser-navigate.ts +83 -77
  31. package/mcp-server/src/tools/browser-observe.ts +99 -73
  32. package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
  33. package/neural-interface/public/shared/styles.css +49 -0
  34. package/neural-interface/public/shared/ui-automation-studio.js +71 -3
  35. package/neural-interface/public/shared/ui-claude-panel.js +29 -23
  36. package/neural-interface/public/shared/ui-settings.js +10 -10
  37. package/neural-interface/public/shared/ui-terminal.js +9 -7
  38. package/neural-interface/server.js +732 -386
  39. package/package.json +1 -1
@@ -2,6 +2,8 @@ import { z } from 'zod';
2
2
  import * as ni from '../services/neural-interface.js';
3
3
  import { text, image } from './response.js';
4
4
 
5
+ const tabIdField = z.string().optional().describe('Target a specific tab within the session. Auto-resolved from environment if omitted.');
6
+
5
7
  // ── browser_snapshot ──
6
8
 
7
9
  export const browserSnapshotSchema = {
@@ -9,6 +11,7 @@ export const browserSnapshotSchema = {
9
11
  'Scope snapshot to a specific element\'s subtree. Dramatically reduces output on complex pages. Twitter: [data-testid="primaryColumn"] for main feed, [data-testid="tweet"] for a single tweet card.'
10
12
  ),
11
13
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
14
+ tabId: tabIdField,
12
15
  };
13
16
 
14
17
  export const browserSnapshotDescription =
@@ -59,11 +62,11 @@ function formatSnapshotNode(node: Record<string, unknown>, indent = 0): string {
59
62
  return line;
60
63
  }
61
64
 
62
- export async function handleBrowserSnapshot(args: { selector?: string; sessionId?: string }) {
63
- const resolved = await ni.resolveSession(args.sessionId);
65
+ export async function handleBrowserSnapshot(args: { selector?: string; sessionId?: string; tabId?: string }) {
66
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
64
67
  if ('error' in resolved) return text(resolved.error);
65
68
 
66
- const result = await ni.snapshot(resolved.sessionId, args.selector);
69
+ const result = await ni.snapshot(resolved.sessionId, args.selector, resolved.tabId);
67
70
  if (result.error) return text(`Snapshot failed: ${result.error}`);
68
71
 
69
72
  const tree = result.snapshot as Record<string, unknown> | null;
@@ -88,17 +91,18 @@ export const browserContentSchema = {
88
91
  'Output format. "text" returns raw innerText (fast, no structure). "markdown" returns clean markdown with headings, links, and lists preserved — strips nav/header/footer/ads automatically. Markdown is better for LLM consumption.'
89
92
  ),
90
93
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
94
+ tabId: tabIdField,
91
95
  };
92
96
 
93
97
  export const browserContentDescription =
94
98
  'Get the content of the current page. Set format="markdown" for clean markdown with structure preserved (headings, links, lists) — nav/header/footer/ads stripped automatically. Default format="text" returns raw visible text. Markdown is preferred for LLM consumption — up to 80% more token-efficient than raw HTML.';
95
99
 
96
- export async function handleBrowserContent(args: { format?: string; sessionId?: string }) {
97
- const resolved = await ni.resolveSession(args.sessionId);
100
+ export async function handleBrowserContent(args: { format?: string; sessionId?: string; tabId?: string }) {
101
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
98
102
  if ('error' in resolved) return text(resolved.error);
99
103
 
100
104
  if (args.format === 'markdown') {
101
- const result = await ni.getMarkdown(resolved.sessionId);
105
+ const result = await ni.getMarkdown(resolved.sessionId, resolved.tabId);
102
106
  if (result.error) return text(`Markdown extraction failed: ${result.error}`);
103
107
 
104
108
  let msg = `URL: ${result.url}\nTitle: "${result.title}"\nTokens: ~${result.tokens}\n\n`;
@@ -107,7 +111,7 @@ export async function handleBrowserContent(args: { format?: string; sessionId?:
107
111
  }
108
112
 
109
113
  // Default: plain text
110
- const result = await ni.getContent(resolved.sessionId);
114
+ const result = await ni.getContent(resolved.sessionId, resolved.tabId);
111
115
  if (result.error) return text(`Content failed: ${result.error}`);
112
116
 
113
117
  let msg = `URL: ${result.url}\nTitle: "${result.title}"\n\n`;
@@ -120,16 +124,17 @@ export async function handleBrowserContent(args: { format?: string; sessionId?:
120
124
 
121
125
  export const browserScreenshotSchema = {
122
126
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
127
+ tabId: tabIdField,
123
128
  };
124
129
 
125
130
  export const browserScreenshotDescription =
126
131
  'Take a screenshot of the current page. Returns a base64-encoded JPEG image. Use sparingly — prefer browser_snapshot for most tasks as it is far more token-efficient.';
127
132
 
128
- export async function handleBrowserScreenshot(args: { sessionId?: string }) {
129
- const resolved = await ni.resolveSession(args.sessionId);
133
+ export async function handleBrowserScreenshot(args: { sessionId?: string; tabId?: string }) {
134
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
130
135
  if ('error' in resolved) return text(resolved.error);
131
136
 
132
- const result = await ni.screenshot(resolved.sessionId);
137
+ const result = await ni.screenshot(resolved.sessionId, resolved.tabId);
133
138
  if (result.error) return text(`Screenshot failed: ${result.error}`);
134
139
 
135
140
  return {
@@ -163,16 +168,17 @@ Array.from(document.querySelectorAll('[data-testid="tweet"]')).map(el => {
163
168
 
164
169
  export const browserExtractTweetsSchema = {
165
170
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
171
+ tabId: tabIdField,
166
172
  };
167
173
 
168
174
  export const browserExtractTweetsDescription =
169
175
  'Extract all currently visible tweets as structured JSON (author, handle, text, time, url, replies, reposts, likes, views). Much faster than browser_snapshot for data harvesting — use this in scraping/loop flows instead of reading the ARIA tree. Navigate to x.com/search?q=%23hashtag&f=live first for latest-first hashtag results.';
170
176
 
171
- export async function handleBrowserExtractTweets(args: { sessionId?: string }) {
172
- const resolved = await ni.resolveSession(args.sessionId);
177
+ export async function handleBrowserExtractTweets(args: { sessionId?: string; tabId?: string }) {
178
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
173
179
  if ('error' in resolved) return text(resolved.error);
174
180
 
175
- const result = await ni.evaluate(resolved.sessionId, TWEET_EXTRACTOR_SCRIPT);
181
+ const result = await ni.evaluate(resolved.sessionId, TWEET_EXTRACTOR_SCRIPT, resolved.tabId);
176
182
  if (result.error) return text(`Extract failed: ${result.error}`);
177
183
 
178
184
  const tweets = result.result as Array<Record<string, unknown>>;
@@ -206,6 +212,7 @@ Array.from(document.querySelectorAll('[role="article"]')).map(el => {
206
212
 
207
213
  export const browserExtractFbPostsSchema = {
208
214
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
215
+ tabId: tabIdField,
209
216
  };
210
217
 
211
218
  export const browserExtractFbPostsDescription =
@@ -213,11 +220,11 @@ export const browserExtractFbPostsDescription =
213
220
  'Works on group feeds and Pages. Scroll down first with browser_scroll to load more posts. ' +
214
221
  'Much faster than browser_snapshot for data harvesting from Facebook.';
215
222
 
216
- export async function handleBrowserExtractFbPosts(args: { sessionId?: string }) {
217
- const resolved = await ni.resolveSession(args.sessionId);
223
+ export async function handleBrowserExtractFbPosts(args: { sessionId?: string; tabId?: string }) {
224
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
218
225
  if ('error' in resolved) return text(resolved.error);
219
226
 
220
- const result = await ni.evaluate(resolved.sessionId, FB_POST_EXTRACTOR_SCRIPT);
227
+ const result = await ni.evaluate(resolved.sessionId, FB_POST_EXTRACTOR_SCRIPT, resolved.tabId);
221
228
  if (result.error) return text(`Extract failed: ${result.error}`);
222
229
 
223
230
  const posts = result.result as Array<Record<string, unknown>>;
@@ -258,6 +265,7 @@ const TIKTOK_FEED_EXTRACTOR_SCRIPT = `
258
265
 
259
266
  export const browserExtractTiktokVideosSchema = {
260
267
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
268
+ tabId: tabIdField,
261
269
  };
262
270
 
263
271
  export const browserExtractTiktokVideosDescription =
@@ -266,11 +274,11 @@ export const browserExtractTiktokVideosDescription =
266
274
  'Navigate to tiktok.com/ or tiktok.com/following first. Scroll with browser_scroll to load more videos. ' +
267
275
  'Much faster than browser_snapshot for data harvesting from TikTok feeds.';
268
276
 
269
- export async function handleBrowserExtractTiktokVideos(args: { sessionId?: string }) {
270
- const resolved = await ni.resolveSession(args.sessionId);
277
+ export async function handleBrowserExtractTiktokVideos(args: { sessionId?: string; tabId?: string }) {
278
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
271
279
  if ('error' in resolved) return text(resolved.error);
272
280
 
273
- const result = await ni.evaluate(resolved.sessionId, TIKTOK_FEED_EXTRACTOR_SCRIPT);
281
+ const result = await ni.evaluate(resolved.sessionId, TIKTOK_FEED_EXTRACTOR_SCRIPT, resolved.tabId);
274
282
  if (result.error) return text(`Extract failed: ${result.error}`);
275
283
 
276
284
  const videos = result.result as Array<Record<string, unknown>>;
@@ -302,6 +310,7 @@ Array.from(document.querySelectorAll('[data-e2e="search_top-item"]')).map(el =>
302
310
 
303
311
  export const browserExtractTiktokSearchSchema = {
304
312
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
313
+ tabId: tabIdField,
305
314
  };
306
315
 
307
316
  export const browserExtractTiktokSearchDescription =
@@ -310,11 +319,11 @@ export const browserExtractTiktokSearchDescription =
310
319
  'Navigate to tiktok.com/search?q=<query> first. Scroll to load more results. ' +
311
320
  'Much faster than browser_snapshot for harvesting TikTok search results.';
312
321
 
313
- export async function handleBrowserExtractTiktokSearch(args: { sessionId?: string }) {
314
- const resolved = await ni.resolveSession(args.sessionId);
322
+ export async function handleBrowserExtractTiktokSearch(args: { sessionId?: string; tabId?: string }) {
323
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
315
324
  if ('error' in resolved) return text(resolved.error);
316
325
 
317
- const result = await ni.evaluate(resolved.sessionId, TIKTOK_SEARCH_EXTRACTOR_SCRIPT);
326
+ const result = await ni.evaluate(resolved.sessionId, TIKTOK_SEARCH_EXTRACTOR_SCRIPT, resolved.tabId);
318
327
  if (result.error) return text(`Extract failed: ${result.error}`);
319
328
 
320
329
  const videos = result.result as Array<Record<string, unknown>>;
@@ -348,6 +357,7 @@ Array.from(document.querySelectorAll('[data-tt="components_PostInfoCell_a"]')).m
348
357
 
349
358
  export const browserExtractTiktokStudioSchema = {
350
359
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
360
+ tabId: tabIdField,
351
361
  };
352
362
 
353
363
  export const browserExtractTiktokStudioDescription =
@@ -356,11 +366,11 @@ export const browserExtractTiktokStudioDescription =
356
366
  'Navigate to tiktok.com/tiktokstudio/content first. Scroll to load more posts. ' +
357
367
  'Use this to audit, manage, or bulk-read your published TikTok content.';
358
368
 
359
- export async function handleBrowserExtractTiktokStudio(args: { sessionId?: string }) {
360
- const resolved = await ni.resolveSession(args.sessionId);
369
+ export async function handleBrowserExtractTiktokStudio(args: { sessionId?: string; tabId?: string }) {
370
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
361
371
  if ('error' in resolved) return text(resolved.error);
362
372
 
363
- const result = await ni.evaluate(resolved.sessionId, TIKTOK_STUDIO_EXTRACTOR_SCRIPT);
373
+ const result = await ni.evaluate(resolved.sessionId, TIKTOK_STUDIO_EXTRACTOR_SCRIPT, resolved.tabId);
364
374
  if (result.error) return text(`Extract failed: ${result.error}`);
365
375
 
366
376
  const posts = result.result as Array<Record<string, unknown>>;
@@ -395,6 +405,7 @@ const TIKTOK_PROFILE_EXTRACTOR_SCRIPT = `
395
405
 
396
406
  export const browserExtractTiktokProfileSchema = {
397
407
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
408
+ tabId: tabIdField,
398
409
  };
399
410
 
400
411
  export const browserExtractTiktokProfileDescription =
@@ -403,11 +414,11 @@ export const browserExtractTiktokProfileDescription =
403
414
  'Navigate to tiktok.com/@username first. Scroll down to load more videos in the grid. ' +
404
415
  'Use this to audit a creator profile or collect video URLs for further processing.';
405
416
 
406
- export async function handleBrowserExtractTiktokProfile(args: { sessionId?: string }) {
407
- const resolved = await ni.resolveSession(args.sessionId);
417
+ export async function handleBrowserExtractTiktokProfile(args: { sessionId?: string; tabId?: string }) {
418
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
408
419
  if ('error' in resolved) return text(resolved.error);
409
420
 
410
- const result = await ni.evaluate(resolved.sessionId, TIKTOK_PROFILE_EXTRACTOR_SCRIPT);
421
+ const result = await ni.evaluate(resolved.sessionId, TIKTOK_PROFILE_EXTRACTOR_SCRIPT, resolved.tabId);
411
422
  if (result.error) return text(`Extract failed: ${result.error}`);
412
423
 
413
424
  const profile = result.result as Record<string, unknown>;
@@ -443,6 +454,7 @@ Array.from(document.querySelectorAll('[aria-label="Lista de conversas"] [role="r
443
454
 
444
455
  export const browserExtractWaChatsSchema = {
445
456
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
457
+ tabId: tabIdField,
446
458
  };
447
459
 
448
460
  export const browserExtractWaChatsDescription =
@@ -451,11 +463,11 @@ export const browserExtractWaChatsDescription =
451
463
  'Must be on web.whatsapp.com with the chat list visible. Scroll the sidebar with browser_scroll to load more chats. ' +
452
464
  'Use browser_click on span[title="Chat Name"] to open a specific chat.';
453
465
 
454
- export async function handleBrowserExtractWaChats(args: { sessionId?: string }) {
455
- const resolved = await ni.resolveSession(args.sessionId);
466
+ export async function handleBrowserExtractWaChats(args: { sessionId?: string; tabId?: string }) {
467
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
456
468
  if ('error' in resolved) return text(resolved.error);
457
469
 
458
- const result = await ni.evaluate(resolved.sessionId, WA_CHATS_EXTRACTOR_SCRIPT);
470
+ const result = await ni.evaluate(resolved.sessionId, WA_CHATS_EXTRACTOR_SCRIPT, resolved.tabId);
459
471
  if (result.error) return text(`Extract failed: ${result.error}`);
460
472
 
461
473
  const chats = result.result as Array<Record<string, unknown>>;
@@ -489,6 +501,7 @@ Array.from(document.querySelectorAll('div.copyable-text[data-pre-plain-text]')).
489
501
 
490
502
  export const browserExtractWaMessagesSchema = {
491
503
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
504
+ tabId: tabIdField,
492
505
  };
493
506
 
494
507
  export const browserExtractWaMessagesDescription =
@@ -497,11 +510,11 @@ export const browserExtractWaMessagesDescription =
497
510
  'Open a chat first by clicking span[title="Chat Name"]. Scroll up with browser_scroll to load older messages. ' +
498
511
  'direction is "in" for received and "out" for sent messages.';
499
512
 
500
- export async function handleBrowserExtractWaMessages(args: { sessionId?: string }) {
501
- const resolved = await ni.resolveSession(args.sessionId);
513
+ export async function handleBrowserExtractWaMessages(args: { sessionId?: string; tabId?: string }) {
514
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
502
515
  if ('error' in resolved) return text(resolved.error);
503
516
 
504
- const result = await ni.evaluate(resolved.sessionId, WA_MESSAGES_EXTRACTOR_SCRIPT);
517
+ const result = await ni.evaluate(resolved.sessionId, WA_MESSAGES_EXTRACTOR_SCRIPT, resolved.tabId);
505
518
  if (result.error) return text(`Extract failed: ${result.error}`);
506
519
 
507
520
  const messages = result.result as Array<Record<string, unknown>>;
@@ -537,6 +550,7 @@ Array.from(document.querySelectorAll('article')).map(el => {
537
550
 
538
551
  export const browserExtractIgFeedSchema = {
539
552
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
553
+ tabId: tabIdField,
540
554
  };
541
555
 
542
556
  export const browserExtractIgFeedDescription =
@@ -544,11 +558,11 @@ export const browserExtractIgFeedDescription =
544
558
  '(username, profileUrl, postUrl, caption, likes, comments, time, datetime, isSponsored, hasFollow). ' +
545
559
  'Navigate to instagram.com/ first. Scroll down with browser_scroll to load more posts.';
546
560
 
547
- export async function handleBrowserExtractIgFeed(args: { sessionId?: string }) {
548
- const resolved = await ni.resolveSession(args.sessionId);
561
+ export async function handleBrowserExtractIgFeed(args: { sessionId?: string; tabId?: string }) {
562
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
549
563
  if ('error' in resolved) return text(resolved.error);
550
564
 
551
- const result = await ni.evaluate(resolved.sessionId, IG_FEED_EXTRACTOR_SCRIPT);
565
+ const result = await ni.evaluate(resolved.sessionId, IG_FEED_EXTRACTOR_SCRIPT, resolved.tabId);
552
566
  if (result.error) return text(`Extract failed: ${result.error}`);
553
567
 
554
568
  const posts = result.result as Array<Record<string, unknown>>;
@@ -599,6 +613,7 @@ const IG_PROFILE_EXTRACTOR_SCRIPT = `
599
613
 
600
614
  export const browserExtractIgProfileSchema = {
601
615
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
616
+ tabId: tabIdField,
602
617
  };
603
618
 
604
619
  export const browserExtractIgProfileDescription =
@@ -606,11 +621,11 @@ export const browserExtractIgProfileDescription =
606
621
  '(username, displayName, bio, posts, followers, followerExact, following, isVerified, website, gridPosts, highlights). ' +
607
622
  'Navigate to instagram.com/username/ first. Returns bio, stats, post grid (up to 12), and story highlights.';
608
623
 
609
- export async function handleBrowserExtractIgProfile(args: { sessionId?: string }) {
610
- const resolved = await ni.resolveSession(args.sessionId);
624
+ export async function handleBrowserExtractIgProfile(args: { sessionId?: string; tabId?: string }) {
625
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
611
626
  if ('error' in resolved) return text(resolved.error);
612
627
 
613
- const result = await ni.evaluate(resolved.sessionId, IG_PROFILE_EXTRACTOR_SCRIPT);
628
+ const result = await ni.evaluate(resolved.sessionId, IG_PROFILE_EXTRACTOR_SCRIPT, resolved.tabId);
614
629
  if (result.error) return text(`Extract failed: ${result.error}`);
615
630
 
616
631
  const profile = result.result as Record<string, unknown>;
@@ -674,6 +689,7 @@ const IG_POST_EXTRACTOR_SCRIPT = `
674
689
 
675
690
  export const browserExtractIgPostSchema = {
676
691
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
692
+ tabId: tabIdField,
677
693
  };
678
694
 
679
695
  export const browserExtractIgPostDescription =
@@ -682,11 +698,11 @@ export const browserExtractIgPostDescription =
682
698
  'Navigate to instagram.com/p/POST_ID/ or instagram.com/reel/REEL_ID/ first. ' +
683
699
  'Scroll the comment area to load more comments before extracting.';
684
700
 
685
- export async function handleBrowserExtractIgPost(args: { sessionId?: string }) {
686
- const resolved = await ni.resolveSession(args.sessionId);
701
+ export async function handleBrowserExtractIgPost(args: { sessionId?: string; tabId?: string }) {
702
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
687
703
  if ('error' in resolved) return text(resolved.error);
688
704
 
689
- const result = await ni.evaluate(resolved.sessionId, IG_POST_EXTRACTOR_SCRIPT);
705
+ const result = await ni.evaluate(resolved.sessionId, IG_POST_EXTRACTOR_SCRIPT, resolved.tabId);
690
706
  if (result.error) return text(`Extract failed: ${result.error}`);
691
707
 
692
708
  const post = result.result as Record<string, unknown>;
@@ -740,6 +756,7 @@ const IG_REELS_EXTRACTOR_SCRIPT = `
740
756
 
741
757
  export const browserExtractIgReelsSchema = {
742
758
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
759
+ tabId: tabIdField,
743
760
  };
744
761
 
745
762
  export const browserExtractIgReelsDescription =
@@ -747,11 +764,11 @@ export const browserExtractIgReelsDescription =
747
764
  '(username, profileUrl, caption, likes, comments, audioName, audioUrl, hasFollow). ' +
748
765
  'Navigate to instagram.com/reels/ first. Scroll down to load more reels.';
749
766
 
750
- export async function handleBrowserExtractIgReels(args: { sessionId?: string }) {
751
- const resolved = await ni.resolveSession(args.sessionId);
767
+ export async function handleBrowserExtractIgReels(args: { sessionId?: string; tabId?: string }) {
768
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
752
769
  if ('error' in resolved) return text(resolved.error);
753
770
 
754
- const result = await ni.evaluate(resolved.sessionId, IG_REELS_EXTRACTOR_SCRIPT);
771
+ const result = await ni.evaluate(resolved.sessionId, IG_REELS_EXTRACTOR_SCRIPT, resolved.tabId);
755
772
  if (result.error) return text(`Extract failed: ${result.error}`);
756
773
 
757
774
  const reels = result.result as Array<Record<string, unknown>>;
@@ -774,6 +791,7 @@ Array.from(document.querySelectorAll('main a[href*="/p/"], main a[href*="/reel/"
774
791
 
775
792
  export const browserExtractIgSearchSchema = {
776
793
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
794
+ tabId: tabIdField,
777
795
  };
778
796
 
779
797
  export const browserExtractIgSearchDescription =
@@ -781,11 +799,11 @@ export const browserExtractIgSearchDescription =
781
799
  'Navigate to instagram.com/explore/ first. Scroll to load more content. ' +
782
800
  'For hashtag search, navigate to instagram.com/explore/tags/HASHTAG/.';
783
801
 
784
- export async function handleBrowserExtractIgSearch(args: { sessionId?: string }) {
785
- const resolved = await ni.resolveSession(args.sessionId);
802
+ export async function handleBrowserExtractIgSearch(args: { sessionId?: string; tabId?: string }) {
803
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
786
804
  if ('error' in resolved) return text(resolved.error);
787
805
 
788
- const result = await ni.evaluate(resolved.sessionId, IG_SEARCH_EXTRACTOR_SCRIPT);
806
+ const result = await ni.evaluate(resolved.sessionId, IG_SEARCH_EXTRACTOR_SCRIPT, resolved.tabId);
789
807
  if (result.error) return text(`Extract failed: ${result.error}`);
790
808
 
791
809
  const posts = result.result as Array<Record<string, unknown>>;
@@ -835,6 +853,7 @@ Array.from(document.querySelectorAll('.feed-shared-update-v2[data-urn*="urn:li:a
835
853
 
836
854
  export const browserExtractLiFeedSchema = {
837
855
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
856
+ tabId: tabIdField,
838
857
  };
839
858
 
840
859
  export const browserExtractLiFeedDescription =
@@ -843,11 +862,11 @@ export const browserExtractLiFeedDescription =
843
862
  'Navigate to linkedin.com/feed/ first. Scroll down with browser_scroll to load more posts. ' +
844
863
  'Much faster than browser_snapshot for data harvesting from the LinkedIn feed.';
845
864
 
846
- export async function handleBrowserExtractLiFeed(args: { sessionId?: string }) {
847
- const resolved = await ni.resolveSession(args.sessionId);
865
+ export async function handleBrowserExtractLiFeed(args: { sessionId?: string; tabId?: string }) {
866
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
848
867
  if ('error' in resolved) return text(resolved.error);
849
868
 
850
- const result = await ni.evaluate(resolved.sessionId, LI_FEED_EXTRACTOR_SCRIPT);
869
+ const result = await ni.evaluate(resolved.sessionId, LI_FEED_EXTRACTOR_SCRIPT, resolved.tabId);
851
870
  if (result.error) return text(`Extract failed: ${result.error}`);
852
871
 
853
872
  const posts = result.result as Array<Record<string, unknown>>;
@@ -890,6 +909,7 @@ const LI_PROFILE_EXTRACTOR_SCRIPT = `
890
909
 
891
910
  export const browserExtractLiProfileSchema = {
892
911
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
912
+ tabId: tabIdField,
893
913
  };
894
914
 
895
915
  export const browserExtractLiProfileDescription =
@@ -898,11 +918,11 @@ export const browserExtractLiProfileDescription =
898
918
  'Navigate to linkedin.com/in/USERNAME/ first. Sections include Experience, Education, Skills, etc. ' +
899
919
  'Use linkedin.com/in/me/ for the logged-in user\'s own profile.';
900
920
 
901
- export async function handleBrowserExtractLiProfile(args: { sessionId?: string }) {
902
- const resolved = await ni.resolveSession(args.sessionId);
921
+ export async function handleBrowserExtractLiProfile(args: { sessionId?: string; tabId?: string }) {
922
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
903
923
  if ('error' in resolved) return text(resolved.error);
904
924
 
905
- const result = await ni.evaluate(resolved.sessionId, LI_PROFILE_EXTRACTOR_SCRIPT);
925
+ const result = await ni.evaluate(resolved.sessionId, LI_PROFILE_EXTRACTOR_SCRIPT, resolved.tabId);
906
926
  if (result.error) return text(`Extract failed: ${result.error}`);
907
927
 
908
928
  const profile = result.result as Record<string, unknown>;
@@ -946,6 +966,7 @@ const LI_POST_EXTRACTOR_SCRIPT = `
946
966
 
947
967
  export const browserExtractLiPostSchema = {
948
968
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
969
+ tabId: tabIdField,
949
970
  };
950
971
 
951
972
  export const browserExtractLiPostDescription =
@@ -954,11 +975,11 @@ export const browserExtractLiPostDescription =
954
975
  'Navigate to linkedin.com/feed/update/urn:li:activity:ID/ first. ' +
955
976
  'Click "Comentar" button to expand comment section, then scroll to load more comments before extracting.';
956
977
 
957
- export async function handleBrowserExtractLiPost(args: { sessionId?: string }) {
958
- const resolved = await ni.resolveSession(args.sessionId);
978
+ export async function handleBrowserExtractLiPost(args: { sessionId?: string; tabId?: string }) {
979
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
959
980
  if ('error' in resolved) return text(resolved.error);
960
981
 
961
- const result = await ni.evaluate(resolved.sessionId, LI_POST_EXTRACTOR_SCRIPT);
982
+ const result = await ni.evaluate(resolved.sessionId, LI_POST_EXTRACTOR_SCRIPT, resolved.tabId);
962
983
  if (result.error) return text(`Extract failed: ${result.error}`);
963
984
 
964
985
  const post = result.result as Record<string, unknown>;
@@ -987,6 +1008,7 @@ Array.from(document.querySelectorAll('article.nt-card')).map(card => {
987
1008
 
988
1009
  export const browserExtractLiNotificationsSchema = {
989
1010
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
1011
+ tabId: tabIdField,
990
1012
  };
991
1013
 
992
1014
  export const browserExtractLiNotificationsDescription =
@@ -994,11 +1016,11 @@ export const browserExtractLiNotificationsDescription =
994
1016
  'Navigate to linkedin.com/notifications/ first. Scroll down to load older notifications. ' +
995
1017
  'Useful for monitoring engagement, connection requests, and mentions.';
996
1018
 
997
- export async function handleBrowserExtractLiNotifications(args: { sessionId?: string }) {
998
- const resolved = await ni.resolveSession(args.sessionId);
1019
+ export async function handleBrowserExtractLiNotifications(args: { sessionId?: string; tabId?: string }) {
1020
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
999
1021
  if ('error' in resolved) return text(resolved.error);
1000
1022
 
1001
- const result = await ni.evaluate(resolved.sessionId, LI_NOTIFICATIONS_EXTRACTOR_SCRIPT);
1023
+ const result = await ni.evaluate(resolved.sessionId, LI_NOTIFICATIONS_EXTRACTOR_SCRIPT, resolved.tabId);
1002
1024
  if (result.error) return text(`Extract failed: ${result.error}`);
1003
1025
 
1004
1026
  const notifs = result.result as Array<Record<string, unknown>>;
@@ -1035,6 +1057,7 @@ const LI_MESSAGES_EXTRACTOR_SCRIPT = `
1035
1057
 
1036
1058
  export const browserExtractLiMessagesSchema = {
1037
1059
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
1060
+ tabId: tabIdField,
1038
1061
  };
1039
1062
 
1040
1063
  export const browserExtractLiMessagesDescription =
@@ -1044,11 +1067,11 @@ export const browserExtractLiMessagesDescription =
1044
1067
  'Navigate to linkedin.com/messaging/ first. Click a conversation to load its messages. ' +
1045
1068
  'To send a message: browser_fill on .msg-form__contenteditable then browser_click on .msg-form__send-button.';
1046
1069
 
1047
- export async function handleBrowserExtractLiMessages(args: { sessionId?: string }) {
1048
- const resolved = await ni.resolveSession(args.sessionId);
1070
+ export async function handleBrowserExtractLiMessages(args: { sessionId?: string; tabId?: string }) {
1071
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
1049
1072
  if ('error' in resolved) return text(resolved.error);
1050
1073
 
1051
- const result = await ni.evaluate(resolved.sessionId, LI_MESSAGES_EXTRACTOR_SCRIPT);
1074
+ const result = await ni.evaluate(resolved.sessionId, LI_MESSAGES_EXTRACTOR_SCRIPT, resolved.tabId);
1052
1075
  if (result.error) return text(`Extract failed: ${result.error}`);
1053
1076
 
1054
1077
  const data = result.result as Record<string, unknown>;
@@ -1104,6 +1127,7 @@ const LI_SEARCH_PEOPLE_EXTRACTOR_SCRIPT = `
1104
1127
 
1105
1128
  export const browserExtractLiSearchPeopleSchema = {
1106
1129
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
1130
+ tabId: tabIdField,
1107
1131
  };
1108
1132
 
1109
1133
  export const browserExtractLiSearchPeopleDescription =
@@ -1112,11 +1136,11 @@ export const browserExtractLiSearchPeopleDescription =
1112
1136
  'Navigate to linkedin.com/search/results/people/?keywords=QUERY first. ' +
1113
1137
  'Scroll down to load more results. Also works on linkedin.com/search/results/all/.';
1114
1138
 
1115
- export async function handleBrowserExtractLiSearchPeople(args: { sessionId?: string }) {
1116
- const resolved = await ni.resolveSession(args.sessionId);
1139
+ export async function handleBrowserExtractLiSearchPeople(args: { sessionId?: string; tabId?: string }) {
1140
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
1117
1141
  if ('error' in resolved) return text(resolved.error);
1118
1142
 
1119
- const result = await ni.evaluate(resolved.sessionId, LI_SEARCH_PEOPLE_EXTRACTOR_SCRIPT);
1143
+ const result = await ni.evaluate(resolved.sessionId, LI_SEARCH_PEOPLE_EXTRACTOR_SCRIPT, resolved.tabId);
1120
1144
  if (result.error) return text(`Extract failed: ${result.error}`);
1121
1145
 
1122
1146
  const people = result.result as Array<Record<string, unknown>>;
@@ -1175,6 +1199,7 @@ const LI_NETWORK_EXTRACTOR_SCRIPT = `
1175
1199
 
1176
1200
  export const browserExtractLiNetworkSchema = {
1177
1201
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
1202
+ tabId: tabIdField,
1178
1203
  };
1179
1204
 
1180
1205
  export const browserExtractLiNetworkDescription =
@@ -1185,11 +1210,11 @@ export const browserExtractLiNetworkDescription =
1185
1210
  'To accept an invitation: browser_click on button with the acceptLabel. ' +
1186
1211
  'To connect: browser_click on the respective Conectar/Connect button.';
1187
1212
 
1188
- export async function handleBrowserExtractLiNetwork(args: { sessionId?: string }) {
1189
- const resolved = await ni.resolveSession(args.sessionId);
1213
+ export async function handleBrowserExtractLiNetwork(args: { sessionId?: string; tabId?: string }) {
1214
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
1190
1215
  if ('error' in resolved) return text(resolved.error);
1191
1216
 
1192
- const result = await ni.evaluate(resolved.sessionId, LI_NETWORK_EXTRACTOR_SCRIPT);
1217
+ const result = await ni.evaluate(resolved.sessionId, LI_NETWORK_EXTRACTOR_SCRIPT, resolved.tabId);
1193
1218
  if (result.error) return text(`Extract failed: ${result.error}`);
1194
1219
 
1195
1220
  const data = result.result as Record<string, unknown>;
@@ -1304,6 +1329,7 @@ const LI_JOBS_EXTRACTOR_SCRIPT = `
1304
1329
 
1305
1330
  export const browserExtractLiJobsSchema = {
1306
1331
  sessionId: z.string().optional().describe('Browser session ID. If omitted, auto-selects the only open session.'),
1332
+ tabId: tabIdField,
1307
1333
  };
1308
1334
 
1309
1335
  export const browserExtractLiJobsDescription =
@@ -1314,11 +1340,11 @@ export const browserExtractLiJobsDescription =
1314
1340
  'For search: use linkedin.com/jobs/search/?keywords=QUERY&location=LOCATION. ' +
1315
1341
  'Returns an array of job objects.';
1316
1342
 
1317
- export async function handleBrowserExtractLiJobs(args: { sessionId?: string }) {
1318
- const resolved = await ni.resolveSession(args.sessionId);
1343
+ export async function handleBrowserExtractLiJobs(args: { sessionId?: string; tabId?: string }) {
1344
+ const resolved = await ni.resolveSession(args.sessionId, undefined, args.tabId);
1319
1345
  if ('error' in resolved) return text(resolved.error);
1320
1346
 
1321
- const result = await ni.evaluate(resolved.sessionId, LI_JOBS_EXTRACTOR_SCRIPT);
1347
+ const result = await ni.evaluate(resolved.sessionId, LI_JOBS_EXTRACTOR_SCRIPT, resolved.tabId);
1322
1348
  if (result.error) return text(`Extract failed: ${result.error}`);
1323
1349
 
1324
1350
  const jobs = result.result as Array<Record<string, unknown>>;