replicas-computer 0.3.30 → 0.3.32

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/dist/index.js CHANGED
@@ -1130,6 +1130,10 @@ async function computerRecordStopCommand() {
1130
1130
  }
1131
1131
 
1132
1132
  // src/lib/chrome-session.ts
1133
+ function filterChromePages(pages, filter) {
1134
+ const has = (value, needle) => (value ?? "").toLowerCase().includes(needle.toLowerCase());
1135
+ return pages.filter((page) => (!filter.targetId || page.id === filter.targetId) && (!filter.title || has(page.title, filter.title)) && (!filter.url || has(page.url, filter.url)));
1136
+ }
1133
1137
  async function openChromeSession(webSocketDebuggerUrl) {
1134
1138
  const socket = new WebSocket(webSocketDebuggerUrl);
1135
1139
  const pending = /* @__PURE__ */ new Map();
@@ -1233,18 +1237,7 @@ async function getChromePages() {
1233
1237
  }
1234
1238
  async function selectChromePage(options = {}) {
1235
1239
  const pages = await getChromePages();
1236
- let matches = pages;
1237
- if (options.targetId) {
1238
- matches = matches.filter((page2) => page2.id === options.targetId);
1239
- }
1240
- if (options.title) {
1241
- const needle = options.title.toLowerCase();
1242
- matches = matches.filter((page2) => (page2.title ?? "").toLowerCase().includes(needle));
1243
- }
1244
- if (options.url) {
1245
- const needle = options.url.toLowerCase();
1246
- matches = matches.filter((page2) => (page2.url ?? "").toLowerCase().includes(needle));
1247
- }
1240
+ const matches = filterChromePages(pages, options);
1248
1241
  const index = options.page ? parseCoord(options.page, "--page") : 0;
1249
1242
  if (index < 0) fail("--page must be >= 0");
1250
1243
  const page = matches[index];
@@ -1914,13 +1907,22 @@ async function computerBrowserStateCommand(path, options = {}) {
1914
1907
  }));
1915
1908
  console.log(JSON.stringify({ targetId: page.id ?? "unknown", ...observation }, null, 2));
1916
1909
  }
1917
- var DESKTOP_POINT_JS = `
1910
+ var VIEWPORT_ORIGIN_JS = `
1911
+ const viewportOrigin = () => {
1912
+ const zoom = window.devicePixelRatio;
1913
+ const border = Math.max(0, (window.outerWidth - window.innerWidth * zoom) / 2);
1914
+ return {
1915
+ left: window.screenX + border,
1916
+ top: window.screenY + Math.max(0, window.outerHeight - window.innerHeight * zoom - border),
1917
+ zoom,
1918
+ };
1919
+ };`;
1920
+ var DESKTOP_POINT_JS = `${VIEWPORT_ORIGIN_JS}
1918
1921
  const desktopPoint = (rect) => {
1919
- const borderX = Math.max(0, (window.outerWidth - window.innerWidth) / 2);
1920
- const topChrome = Math.max(0, window.outerHeight - window.innerHeight - borderX);
1922
+ const { left, top, zoom } = viewportOrigin();
1921
1923
  return {
1922
- x: Math.round(window.screenX + borderX + rect.x + rect.width / 2),
1923
- y: Math.round(window.screenY + topChrome + rect.y + rect.height / 2),
1924
+ x: Math.round(left + (rect.x + rect.width / 2) * zoom),
1925
+ y: Math.round(top + (rect.y + rect.height / 2) * zoom),
1924
1926
  };
1925
1927
  };`;
1926
1928
  function parseBrowserRef(value) {
@@ -3323,7 +3325,7 @@ async function computerStatusCommand() {
3323
3325
  }
3324
3326
 
3325
3327
  // src/version.ts
3326
- var COMPUTER_USE_VERSION = "0.3.30";
3328
+ var COMPUTER_USE_VERSION = "0.3.32";
3327
3329
  var COMPUTER_USE_PROTOCOL_VERSION = 1;
3328
3330
 
3329
3331
  // src/program.ts
@@ -3915,6 +3917,42 @@ function superviseEncoder(options) {
3915
3917
  // src/ice-servers.ts
3916
3918
  import { readFileSync as readFileSync7 } from "fs";
3917
3919
 
3920
+ // ../shared/src/urls.ts
3921
+ var DASHBOARD_PATH = "/dashboard";
3922
+ var WEB_APP_PATHS = {
3923
+ home: "/",
3924
+ onboarding: "/onboarding",
3925
+ workspaces: "/workspace",
3926
+ environmentSetup: "/workspace/setup",
3927
+ automations: "/automations",
3928
+ environment: "/environment",
3929
+ analytics: "/analytics",
3930
+ review: "/review",
3931
+ dashboard: DASHBOARD_PATH,
3932
+ agents: `${DASHBOARD_PATH}/agents`,
3933
+ integrations: `${DASHBOARD_PATH}/integrations`,
3934
+ integrationsGithub: `${DASHBOARD_PATH}/integrations/github`,
3935
+ integrationsGitlab: `${DASHBOARD_PATH}/integrations/gitlab`,
3936
+ integrationsSlack: `${DASHBOARD_PATH}/integrations/slack`,
3937
+ settings: `${DASHBOARD_PATH}/settings`,
3938
+ billing: `${DASHBOARD_PATH}/billing`,
3939
+ members: `${DASHBOARD_PATH}/members`,
3940
+ learnings: `${DASHBOARD_PATH}/learnings`,
3941
+ "audit-logs": `${DASHBOARD_PATH}/audit-logs`,
3942
+ accountAgents: `${DASHBOARD_PATH}/account/agents`,
3943
+ accountProfile: `${DASHBOARD_PATH}/account/profile`,
3944
+ accountConnections: `${DASHBOARD_PATH}/account/connections`,
3945
+ accountIntegrations: `${DASHBOARD_PATH}/account/integrations`,
3946
+ accountApiKeys: `${DASHBOARD_PATH}/account/api-keys`,
3947
+ accountPreferences: `${DASHBOARD_PATH}/account/preferences`,
3948
+ fullscreenWorkspaces: "/workspaces"
3949
+ };
3950
+ var LEGACY_WEB_APP_PATHS = {
3951
+ app: "/app",
3952
+ home: "/home",
3953
+ dashboardWorkspaces: `${DASHBOARD_PATH}/workspaces`
3954
+ };
3955
+
3918
3956
  // ../shared/src/desktop-stream.ts
3919
3957
  function isStreamIceServer(value) {
3920
3958
  return isRecord(value) && typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
@@ -3978,6 +4016,10 @@ function isDesktopWebRtcFailure(value) {
3978
4016
  "codec"
3979
4017
  ].every((key) => typeof value[key] === "string" && value[key].length <= 200) && ["elapsedMs", "candidateErrors", "framesDecoded", "packetsReceived", "packetsLost", "bytesReceived", "lastFrameAgeMs", "encoderFps"].every((key) => typeof value[key] === "number" && Number.isFinite(value[key]));
3980
4018
  }
4019
+ var isDesktopRect = (value) => isRecord(value) && ["x", "y", "width", "height"].every((key) => typeof value[key] === "number" && Number.isFinite(value[key]));
4020
+ function isDesktopElement(value) {
4021
+ return isRecord(value) && isDesktopRect(value.rect) && ["url", "tag", "selector"].every((key) => typeof value[key] === "string") && ["text", "label", "source"].every((key) => value[key] === void 0 || typeof value[key] === "string") && (value.components === void 0 || Array.isArray(value.components) && value.components.every((name) => typeof name === "string"));
4022
+ }
3981
4023
 
3982
4024
  // ../shared/src/runtime-env.ts
3983
4025
  var REPLICAS_RUNTIME_ENV_ALIASES = {
@@ -4942,6 +4984,92 @@ var InputBridge = class {
4942
4984
  }
4943
4985
  };
4944
4986
 
4987
+ // src/desktop-inspect.ts
4988
+ var PAGE_GEOMETRY = `(() => {${VIEWPORT_ORIGIN_JS}
4989
+ return { visible: document.visibilityState === 'visible', focused: document.hasFocus(), innerWidth, innerHeight, ...viewportOrigin() };
4990
+ })()`;
4991
+ var DESCRIBE_ELEMENT = `function () {
4992
+ let el = this.nodeType === 1 ? this : this.parentElement;
4993
+ if (!el) return null;
4994
+ while (el.ownerSVGElement) el = el.ownerSVGElement;
4995
+ el = el.closest('a, button, label, summary, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], [role="checkbox"], [role="switch"]') || el;
4996
+ const clip = (value, max) => {
4997
+ const text = String(value || '').replace(/\\s+/g, ' ').trim();
4998
+ return !text ? undefined : text.length > max ? text.slice(0, max - 1) + '\u2026' : text;
4999
+ };
5000
+ const step = (node) => {
5001
+ if (node.id) return '#' + CSS.escape(node.id);
5002
+ const same = node.parentElement ? [...node.parentElement.children].filter((child) => child.localName === node.localName) : [];
5003
+ return node.localName + [...node.classList].filter((name) => /^[\\w-]+$/.test(name)).slice(0, 2).map((name) => '.' + name).join('')
5004
+ + (same.length > 1 ? ':nth-of-type(' + (same.indexOf(node) + 1) + ')' : '');
5005
+ };
5006
+ const steps = [];
5007
+ for (let node = el; node && node !== document.documentElement && steps.length < 4; node = node.parentElement) {
5008
+ steps.unshift(step(node));
5009
+ if (node.id) break;
5010
+ }
5011
+ const components = [];
5012
+ let source;
5013
+ try {
5014
+ const key = Object.keys(el).find((name) => name.startsWith('__reactFiber$') || name.startsWith('__reactInternalInstance$'));
5015
+ const typeName = (type, depth = 0) => typeof type === 'function' ? type.displayName || type.name
5016
+ : type && depth < 3 ? type.displayName || typeName(type.render, depth + 1) || typeName(type.type, depth + 1) : undefined;
5017
+ const host = key && el[key];
5018
+ const next = host && host._debugOwner ? (fiber) => fiber._debugOwner || fiber.owner : (fiber) => fiber.return;
5019
+ for (let fiber = host && next(host); fiber && components.length < 4; fiber = next(fiber)) {
5020
+ const name = typeof fiber.tag !== 'number' ? fiber.name : [0, 1, 11, 14, 15].includes(fiber.tag) ? typeName(fiber.type) : undefined;
5021
+ if (typeof name === 'string' && name.length > 1 && !/^_c\\d*$/.test(name) && components.at(-1) !== name) components.push(name);
5022
+ }
5023
+ const debugSource = host && host._debugSource;
5024
+ if (debugSource && debugSource.fileName) source = debugSource.fileName + ':' + debugSource.lineNumber;
5025
+ } catch {}
5026
+ const rect = el.getBoundingClientRect();
5027
+ let x = rect.x, y = rect.y;
5028
+ try {
5029
+ for (let view = window; view.frameElement; view = view.parent) {
5030
+ const frame = view.frameElement.getBoundingClientRect();
5031
+ x += frame.x + view.frameElement.clientLeft;
5032
+ y += frame.y + view.frameElement.clientTop;
5033
+ }
5034
+ } catch {}
5035
+ return {
5036
+ url: location.href,
5037
+ tag: el.localName,
5038
+ selector: steps.join(' > '),
5039
+ rect: { x, y, width: rect.width, height: rect.height },
5040
+ text: clip(el.innerText || (el.type === 'password' ? '' : el.value) || el.textContent, 120),
5041
+ label: clip(el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('alt') || el.getAttribute('placeholder'), 80),
5042
+ components: components.length ? components : undefined,
5043
+ source,
5044
+ };
5045
+ }`;
5046
+ async function inspectPage(session, x, y) {
5047
+ const page = chromeCallValue(await sendChromeSessionCommand(session, "Runtime.evaluate", { expression: PAGE_GEOMETRY, returnByValue: true }));
5048
+ if (!isRecord(page) || page.visible !== true) return null;
5049
+ const [left, top, zoom, innerWidth, innerHeight] = [page.left, page.top, page.zoom, page.innerWidth, page.innerHeight].map(Number);
5050
+ const pageX = (x - left) / zoom;
5051
+ const pageY = (y - top) / zoom;
5052
+ if (!(pageX >= 0 && pageY >= 0 && pageX < innerWidth && pageY < innerHeight)) return null;
5053
+ const focused = page.focused === true;
5054
+ const located = await sendChromeSessionCommand(session, "DOM.getNodeForLocation", { x: Math.floor(pageX), y: Math.floor(pageY) });
5055
+ const resolved = await sendChromeSessionCommand(session, "DOM.resolveNode", { backendNodeId: located.backendNodeId });
5056
+ const objectId = isRecord(resolved.object) ? resolved.object.objectId : void 0;
5057
+ if (typeof objectId !== "string") return { focused, element: null };
5058
+ const described = chromeCallValue(await sendChromeSessionCommand(session, "Runtime.callFunctionOn", { objectId, functionDeclaration: DESCRIBE_ELEMENT, returnByValue: true }));
5059
+ if (!isRecord(described) || !isRecord(described.rect)) return { focused, element: null };
5060
+ const { x: rectX, y: rectY, width: width2, height: height2 } = described.rect;
5061
+ const element = {
5062
+ ...described,
5063
+ rect: { x: left + Number(rectX) * zoom, y: top + Number(rectY) * zoom, width: Number(width2) * zoom, height: Number(height2) * zoom }
5064
+ };
5065
+ return { focused, element: isDesktopElement(element) ? element : null };
5066
+ }
5067
+ async function inspectDesktopPoint(x, y) {
5068
+ const pages = (await getChromePages()).flatMap((page) => page.webSocketDebuggerUrl ? [page.webSocketDebuggerUrl] : []);
5069
+ const hits = (await Promise.all(pages.map((url) => withChromeSession(url, (session) => inspectPage(session, x, y)).catch(() => null)))).filter((hit) => hit !== null);
5070
+ return hits.find((hit) => hit.focused)?.element ?? null;
5071
+ }
5072
+
4945
5073
  // src/media-server.ts
4946
5074
  var width = Number.parseInt(process.env.REPLICAS_DESKTOP_WIDTH || String(DESKTOP_VIEWER_WIDTH), 10);
4947
5075
  var height = Number.parseInt(process.env.REPLICAS_DESKTOP_HEIGHT || String(DESKTOP_VIEWER_HEIGHT), 10);
@@ -5849,6 +5977,19 @@ async function runMediaServer() {
5849
5977
  });
5850
5978
  return;
5851
5979
  }
5980
+ if (url.pathname === "/inspect") {
5981
+ const x = Number(url.searchParams.get("x"));
5982
+ const y = Number(url.searchParams.get("y"));
5983
+ response.setHeader("cache-control", "no-store");
5984
+ if (!(x >= 0 && x < width && y >= 0 && y < height)) {
5985
+ response.statusCode = 400;
5986
+ response.end("x and y must be a point on the desktop");
5987
+ return;
5988
+ }
5989
+ response.setHeader("content-type", "application/json");
5990
+ void inspectDesktopPoint(x, y).catch(() => null).then((element) => response.end(JSON.stringify(element)));
5991
+ return;
5992
+ }
5852
5993
  const requested = url.pathname === "/" ? "stream-index.html" : url.pathname.slice(1);
5853
5994
  const path = resolve3(webroot, requested);
5854
5995
  if (!path.startsWith(resolve3(webroot) + "/") || !existsSync8(path) || !statSync2(path).isFile()) {
@@ -3,6 +3,43 @@ function isRecord(value) {
3
3
  return typeof value === "object" && value !== null && !Array.isArray(value);
4
4
  }
5
5
 
6
+ // ../shared/src/urls.ts
7
+ var DEFAULT_WEB_APP_URL = "https://app.replicas.dev";
8
+ var DASHBOARD_PATH = "/dashboard";
9
+ var WEB_APP_PATHS = {
10
+ home: "/",
11
+ onboarding: "/onboarding",
12
+ workspaces: "/workspace",
13
+ environmentSetup: "/workspace/setup",
14
+ automations: "/automations",
15
+ environment: "/environment",
16
+ analytics: "/analytics",
17
+ review: "/review",
18
+ dashboard: DASHBOARD_PATH,
19
+ agents: `${DASHBOARD_PATH}/agents`,
20
+ integrations: `${DASHBOARD_PATH}/integrations`,
21
+ integrationsGithub: `${DASHBOARD_PATH}/integrations/github`,
22
+ integrationsGitlab: `${DASHBOARD_PATH}/integrations/gitlab`,
23
+ integrationsSlack: `${DASHBOARD_PATH}/integrations/slack`,
24
+ settings: `${DASHBOARD_PATH}/settings`,
25
+ billing: `${DASHBOARD_PATH}/billing`,
26
+ members: `${DASHBOARD_PATH}/members`,
27
+ learnings: `${DASHBOARD_PATH}/learnings`,
28
+ "audit-logs": `${DASHBOARD_PATH}/audit-logs`,
29
+ accountAgents: `${DASHBOARD_PATH}/account/agents`,
30
+ accountProfile: `${DASHBOARD_PATH}/account/profile`,
31
+ accountConnections: `${DASHBOARD_PATH}/account/connections`,
32
+ accountIntegrations: `${DASHBOARD_PATH}/account/integrations`,
33
+ accountApiKeys: `${DASHBOARD_PATH}/account/api-keys`,
34
+ accountPreferences: `${DASHBOARD_PATH}/account/preferences`,
35
+ fullscreenWorkspaces: "/workspaces"
36
+ };
37
+ var LEGACY_WEB_APP_PATHS = {
38
+ app: "/app",
39
+ home: "/home",
40
+ dashboardWorkspaces: `${DASHBOARD_PATH}/workspaces`
41
+ };
42
+
6
43
  // ../shared/src/desktop-stream.ts
7
44
  function isStreamIceServer(value) {
8
45
  return isRecord(value) && typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
@@ -50,6 +87,18 @@ var DESKTOP_WEBRTC_FAILURE_REASONS = {
50
87
  };
51
88
  var DESKTOP_QUALITY_MESSAGE = "replicas-desktop-quality";
52
89
  var DESKTOP_CONNECTION_MESSAGE = "replicas-desktop-connection";
90
+ var DESKTOP_ANNOTATE_MESSAGE = "replicas-desktop-annotate";
91
+ function isDesktopAnnotateOrigin(origin, viewerHostname) {
92
+ if (origin === new URL(DEFAULT_WEB_APP_URL).origin) return true;
93
+ return viewerHostname.split(".").at(-1) === "localhost" && URL.canParse(origin) && new URL(origin).hostname === "localhost";
94
+ }
95
+ function isDesktopAnnotateRequest(value) {
96
+ return isRecord(value) && value.type === DESKTOP_ANNOTATE_MESSAGE && typeof value.id === "number" && (value.action === "capture" || value.action === "inspect" && Number.isFinite(value.x) && Number.isFinite(value.y));
97
+ }
98
+ var isDesktopRect = (value) => isRecord(value) && ["x", "y", "width", "height"].every((key) => typeof value[key] === "number" && Number.isFinite(value[key]));
99
+ function isDesktopElement(value) {
100
+ return isRecord(value) && isDesktopRect(value.rect) && ["url", "tag", "selector"].every((key) => typeof value[key] === "string") && ["text", "label", "source"].every((key) => value[key] === void 0 || typeof value[key] === "string") && (value.components === void 0 || Array.isArray(value.components) && value.components.every((name) => typeof name === "string"));
101
+ }
53
102
 
54
103
  // src/input-validation.ts
55
104
  var finite = (value) => typeof value === "number" && Number.isFinite(value);
@@ -280,6 +329,20 @@ var announceTransport = () => {
280
329
  if (window.parent === window) return;
281
330
  window.parent.postMessage({ type: DESKTOP_TRANSPORT_MESSAGE, transport: transport ?? "connecting", reason: transportReason, path: route.path, codec: route.codec, failure, recovery }, "*");
282
331
  };
332
+ addEventListener("message", (event) => {
333
+ if (window.parent === window || event.source !== window.parent || !isDesktopAnnotateOrigin(event.origin, location.hostname) || !isDesktopAnnotateRequest(event.data)) return;
334
+ const request2 = event.data;
335
+ const reply = (result) => window.parent.postMessage({ type: DESKTOP_ANNOTATE_MESSAGE, id: request2.id, ...result }, event.origin);
336
+ if (request2.action === "capture") {
337
+ void fetch("/snapshot", { cache: "no-store" }).then((response) => response.ok ? response.blob() : null).catch(() => null).then((image) => reply({ image }));
338
+ return;
339
+ }
340
+ void fetch(`/inspect?x=${Math.round(request2.x)}&y=${Math.round(request2.y)}`).then((response) => response.ok ? response.json() : Promise.resolve(null)).catch(() => null).then((element) => reply({ element: isDesktopElement(element) ? element : null }));
341
+ });
342
+ var parentOrigin = URL.canParse(document.referrer) ? new URL(document.referrer).origin : null;
343
+ if (window.parent !== window && parentOrigin && isDesktopAnnotateOrigin(parentOrigin, location.hostname)) {
344
+ window.parent.postMessage({ type: DESKTOP_ANNOTATE_MESSAGE, ready: true }, parentOrigin);
345
+ }
283
346
  var average = (sum, count) => count ? Math.round(sum * 10 / count) / 10 : void 0;
284
347
  var reportConnection = (ended) => {
285
348
  if (window.parent === window || !config) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-computer",
3
- "version": "0.3.30",
3
+ "version": "0.3.32",
4
4
  "description": "Replicas workspace computer-use runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",