cobro-mcp 0.9.3 → 0.9.4

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to this project are documented here. The format follows [Kee
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.4] — 2026-09-22
8
+ ### Fixed
9
+ - Source locations are no longer dropped when the project directory itself sits under a node_modules path (library detection is now relative to the project root).
10
+
7
11
  ## [0.9.3] — 2026-09-22
8
12
  ### Changed
9
13
  - MCP instructions reorganized into 8 short lines (payload reading and ref/region conventions on separate lines); same rules, about 17% shorter.
package/dist/overlay.js CHANGED
@@ -976,37 +976,48 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
976
976
  const frame = frames.find((f) => !REACT_INTERNAL_FN.test(f.fn) && !NODE_MODULES_URL.test(f.url));
977
977
  return frame ? { url: frame.url, line: frame.line, col: frame.col } : null;
978
978
  }
979
+ function normalizeDrivePath(path) {
980
+ return path.replace(/\\/g, "/").replace(/^\/?([a-zA-Z]):\//, (_, drive) => `${drive.toLowerCase()}:/`);
981
+ }
982
+ function isLibraryPath(path, root) {
983
+ const norm = normalizeDrivePath(path);
984
+ let rel = norm;
985
+ if (root) {
986
+ const r = normalizeDrivePath(root).replace(/\/+$/, "");
987
+ if (norm.startsWith(`${r}/`)) rel = norm.slice(r.length + 1);
988
+ }
989
+ return /(^|\/)node_modules\//.test(rel);
990
+ }
979
991
 
980
992
  // src/overlay/frameworks/react.ts
981
- function userLoc(f) {
993
+ function userLoc(f, root) {
982
994
  const src = f._debugSource;
983
- const fileName = src?.fileName?.replace(/\\/g, "/");
984
- if (fileName && !fileName.includes("node_modules/")) return `${src.fileName}${src.lineNumber ? ":" + src.lineNumber : ""}`;
995
+ const fileName = src?.fileName;
996
+ if (fileName && !isLibraryPath(fileName, root)) return `${fileName}${src.lineNumber ? ":" + src.lineNumber : ""}`;
985
997
  const stack = typeof f._debugStack === "string" ? f._debugStack : f._debugStack?.stack;
986
998
  return stack ? pickUserFrame(stack) ?? void 0 : void 0;
987
999
  }
988
- function reactInfo(el) {
1000
+ function reactInfo(el, root) {
989
1001
  const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
990
1002
  if (!key) return void 0;
991
1003
  let f = el[key] ?? null;
992
1004
  let fallback;
993
1005
  let loc;
994
1006
  let component;
995
- const callers = [];
996
- const callerFrames = [];
1007
+ const callerLocs = [];
997
1008
  let pending;
998
1009
  const commit = (p) => {
999
- if (callers.length + callerFrames.length >= 2) return;
1010
+ if (callerLocs.length >= 2) return;
1000
1011
  if (typeof p === "string") {
1001
- if (p !== loc && !callers.includes(p)) callers.push(p);
1002
- } else callerFrames.push(p);
1012
+ if (p !== loc && !callerLocs.includes(p)) callerLocs.push(p);
1013
+ } else callerLocs.push(p);
1003
1014
  };
1004
1015
  let libBelow = false;
1005
1016
  for (let i = 0; f && i < 30; i++, f = f.return ?? null) {
1006
1017
  const t = f.type;
1007
1018
  const name = t && typeof t !== "string" ? t.displayName || t.name : void 0;
1008
1019
  if (loc === void 0) {
1009
- const l2 = userLoc(f);
1020
+ const l2 = userLoc(f, root);
1010
1021
  if (l2 !== void 0) {
1011
1022
  loc = l2;
1012
1023
  component = name;
@@ -1016,13 +1027,13 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
1016
1027
  continue;
1017
1028
  }
1018
1029
  if (!name) {
1019
- if ((f._debugSource !== void 0 || f._debugStack !== void 0) && userLoc(f) === void 0) libBelow = true;
1030
+ if ((f._debugSource !== void 0 || f._debugStack !== void 0) && userLoc(f, root) === void 0) libBelow = true;
1020
1031
  continue;
1021
1032
  }
1022
1033
  if (component === void 0) {
1023
- if (libBelow && userLoc(f) === void 0) continue;
1034
+ if (libBelow && userLoc(f, root) === void 0) continue;
1024
1035
  component = name;
1025
- const l2 = userLoc(f);
1036
+ const l2 = userLoc(f, root);
1026
1037
  if (l2 !== void 0) pending = l2;
1027
1038
  continue;
1028
1039
  }
@@ -1030,36 +1041,35 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
1030
1041
  commit(pending);
1031
1042
  pending = void 0;
1032
1043
  }
1033
- const l = userLoc(f);
1044
+ const l = userLoc(f, root);
1034
1045
  if (l !== void 0) pending = l;
1035
1046
  }
1036
1047
  if (loc === void 0) return fallback;
1037
1048
  if (!component) return fallback;
1038
1049
  const hit = typeof loc === "string" ? { component, source: loc } : { component, frame: loc };
1039
- if (callers.length) hit.callers = callers;
1040
- if (callerFrames.length) hit.callerFrames = callerFrames;
1050
+ if (callerLocs.length) hit.callerLocs = callerLocs;
1041
1051
  return hit;
1042
1052
  }
1043
1053
  var react = { key: "react", detect: reactInfo };
1044
1054
 
1045
1055
  // src/overlay/frameworks/vue.ts
1046
1056
  var ABSOLUTE_RE = /^([a-zA-Z]:\/|\/)/;
1047
- function normalizeVueSource(file) {
1057
+ function normalizeVueSource(file, root) {
1048
1058
  const norm = file.replace(/\\/g, "/");
1049
- if (norm.includes("node_modules")) return void 0;
1059
+ if (isLibraryPath(norm, root)) return void 0;
1050
1060
  if (!ABSOLUTE_RE.test(norm)) return norm;
1051
1061
  const idx = norm.lastIndexOf("/src/");
1052
1062
  if (idx !== -1) return norm.slice(idx + 1);
1053
1063
  const segs = norm.split("/").filter(Boolean);
1054
1064
  return segs.slice(-2).join("/");
1055
1065
  }
1056
- function vueInfo(el) {
1066
+ function vueInfo(el, root) {
1057
1067
  let c = el["__vueParentComponent"] ?? null;
1058
1068
  for (let i = 0; c && i < 10; i++, c = c.parent ?? null) {
1059
1069
  const name = c.type?.name || c.type?.__name;
1060
1070
  if (name) {
1061
1071
  const file = c.type?.__file;
1062
- const source = file ? normalizeVueSource(file) : void 0;
1072
+ const source = file ? normalizeVueSource(file, root) : void 0;
1063
1073
  return source ? { component: name, source } : { component: name };
1064
1074
  }
1065
1075
  }
@@ -1069,9 +1079,13 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
1069
1079
 
1070
1080
  // src/overlay/frameworks/index.ts
1071
1081
  var adapters = [react, vue];
1082
+ var projectRoot;
1083
+ function setProjectRoot(root) {
1084
+ projectRoot = root;
1085
+ }
1072
1086
  function detectComponent(el) {
1073
1087
  for (const adapter of adapters) {
1074
- const info = adapter.detect(el);
1088
+ const info = adapter.detect(el, projectRoot);
1075
1089
  if (info) return { key: adapter.key, info };
1076
1090
  }
1077
1091
  return void 0;
@@ -1173,6 +1187,7 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
1173
1187
  if (window.top !== window) return;
1174
1188
  const PORT = __COBRO_PORT__;
1175
1189
  const TOKEN = __COBRO_TOKEN__;
1190
+ setProjectRoot(__COBRO_ROOT__);
1176
1191
  const boot = () => {
1177
1192
  if (document.documentElement.hasAttribute("data-cobro")) return;
1178
1193
  document.documentElement.setAttribute("data-cobro", "1");
package/dist/server.js CHANGED
@@ -308,6 +308,22 @@ var ChannelServer = class {
308
308
 
309
309
  // src/core/sourcemap.ts
310
310
  import { FlattenMap, originalPositionFor } from "@jridgewell/trace-mapping";
311
+
312
+ // src/core/frame.ts
313
+ function normalizeDrivePath(path) {
314
+ return path.replace(/\\/g, "/").replace(/^\/?([a-zA-Z]):\//, (_, drive) => `${drive.toLowerCase()}:/`);
315
+ }
316
+ function isLibraryPath(path, root) {
317
+ const norm = normalizeDrivePath(path);
318
+ let rel = norm;
319
+ if (root) {
320
+ const r = normalizeDrivePath(root).replace(/\/+$/, "");
321
+ if (norm.startsWith(`${r}/`)) rel = norm.slice(r.length + 1);
322
+ }
323
+ return /(^|\/)node_modules\//.test(rel);
324
+ }
325
+
326
+ // src/core/sourcemap.ts
311
327
  var FRAMEWORK_KEYS = ["react", "vue"];
312
328
  function findSourceMapUrl(moduleText, moduleUrl) {
313
329
  const re = /\/\/[#@]\s*sourceMappingURL=(\S+)/g;
@@ -322,11 +338,7 @@ function findSourceMapUrl(moduleText, moduleUrl) {
322
338
  return void 0;
323
339
  }
324
340
  }
325
- function normalizeDrivePath(path) {
326
- return path.replace(/\\/g, "/").replace(/^\/?([a-zA-Z]):\//, (_, drive) => `${drive.toLowerCase()}:/`);
327
- }
328
- function normalizeFileSource(pathname, root) {
329
- const path = normalizeDrivePath(decodeURIComponent(pathname));
341
+ function normalizeFileSource(path, root) {
330
342
  if (root) {
331
343
  const r = normalizeDrivePath(root).replace(/\/+$/, "");
332
344
  if (path === r) return "";
@@ -351,18 +363,30 @@ function resolveOriginal(mapJson, opts) {
351
363
  } catch {
352
364
  return void 0;
353
365
  }
354
- const path = (url.protocol === "file:" ? normalizeFileSource(url.pathname, opts.root) : decodeURIComponent(url.pathname).replace(/^\/+/, "")).split("?")[0];
355
- if (!path || path.includes("node_modules/") || path.split("/").includes("..") || /[\x00-\x1f]/.test(path)) return void 0;
366
+ const raw = decodeURIComponent(url.pathname);
367
+ const abs = url.protocol === "file:" ? normalizeDrivePath(raw) : raw.replace(/^\/+/, "");
368
+ if (isLibraryPath(abs, opts.root)) return void 0;
369
+ const path = (url.protocol === "file:" ? normalizeFileSource(abs, opts.root) : abs).split("?")[0];
370
+ if (!path || path.split("/").includes("..") || /[\x00-\x1f]/.test(path)) return void 0;
356
371
  return `${path}:${pos.line}`;
357
372
  }
358
373
  function stripFrame(e) {
359
374
  let out = e;
360
375
  for (const key of FRAMEWORK_KEYS) {
361
376
  const info = out[key];
362
- if (!info?.frame && !info?.callerFrames) continue;
363
- const rest = { component: info.component };
364
- if (info.source) rest.source = info.source;
365
- if (info.callers?.length) rest.callers = info.callers;
377
+ if (!info) continue;
378
+ if (typeof info.component !== "string") {
379
+ const rest2 = { ...out };
380
+ delete rest2[key];
381
+ out = rest2;
382
+ continue;
383
+ }
384
+ const rest = { component: info.component.slice(0, 200) };
385
+ if (typeof info.source === "string") rest.source = info.source.slice(0, 200);
386
+ if (Array.isArray(info.callers)) {
387
+ const callers = info.callers.filter((c) => typeof c === "string").map((c) => c.slice(0, 200)).slice(0, 2);
388
+ if (callers.length) rest.callers = callers;
389
+ }
366
390
  out = { ...out, [key]: rest };
367
391
  }
368
392
  return out;
@@ -409,17 +433,15 @@ async function resolveElementSources(batch, page, fetchText, opts) {
409
433
  const source = await resolveFrame(react.frame, page, fetchText, root);
410
434
  if (source) react.source = source;
411
435
  }
412
- if (react.callerFrames?.length) {
413
- const callers = react.callers ? [...react.callers] : [];
414
- for (const frame of react.callerFrames) {
415
- if (callers.length >= 2) break;
416
- const source = await resolveFrame(frame, page, fetchText, root);
417
- if (!source || source === react.source || callers.includes(source)) continue;
418
- callers.push(source);
419
- }
420
- if (callers.length) react.callers = callers;
421
- else delete react.callers;
436
+ const callers = [];
437
+ for (const loc of react.callerLocs ?? []) {
438
+ if (callers.length >= 2) break;
439
+ const s = typeof loc === "string" ? loc : await resolveFrame(loc, page, fetchText, root);
440
+ if (!s || s === react.source || callers.includes(s)) continue;
441
+ callers.push(s);
422
442
  }
443
+ if (callers.length) react.callers = callers;
444
+ else delete react.callers;
423
445
  }
424
446
  }
425
447
 
@@ -623,7 +645,7 @@ var BrowserLauncher = class {
623
645
  return this.launchedOnce;
624
646
  }
625
647
  injected() {
626
- return this.opts.overlaySource.replace(/__COBRO_PORT__/g, String(this.opts.port)).replace(/__COBRO_TOKEN__/g, JSON.stringify(this.opts.token));
648
+ return this.opts.overlaySource.replace(/__COBRO_PORT__/g, () => String(this.opts.port)).replace(/__COBRO_TOKEN__/g, () => JSON.stringify(this.opts.token)).replace(/__COBRO_ROOT__/g, () => JSON.stringify(this.opts.root));
627
649
  }
628
650
  async launch() {
629
651
  const engine2 = this.opts.engine ?? "chromium";
@@ -913,7 +935,7 @@ var bridge = await createBridge({
913
935
  });
914
936
  var fixed = configStrategy();
915
937
  if (fixed) bridge.core.setStrategy(fixed);
916
- launcher = new BrowserLauncher({ overlaySource, port: bridge.port, token, profileDir, headless: process.env.COBRO_HEADLESS === "1", engine });
938
+ launcher = new BrowserLauncher({ overlaySource, port: bridge.port, token, root: process.cwd(), profileDir, headless: process.env.COBRO_HEADLESS === "1", engine });
917
939
  var mcp = createMcpServer({
918
940
  core: bridge.core,
919
941
  browser: launcher,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cobro-mcp",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "Co-browse for coding agents: pick elements in the browser, write a note, Send — the context lands in the agent's chat. MCP server with 6 tools.",
5
5
  "keywords": [
6
6
  "mcp",