@moxxy/cli 0.25.0 → 0.26.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.
- package/dist/bin.js +46364 -48325
- package/package.json +3 -2
- package/dist/bin.js.map +0 -1
- package/dist/read-handler.js.map +0 -1
- package/dist/sidecar.js.map +0 -1
package/dist/read-handler.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../tools-builtin/src/util.ts","../../tools-builtin/src/read-handler.ts"],"names":["fs","MoxxyError"],"mappings":";;;;;;AAeO,SAAS,WAAA,CAAY,KAAa,MAAA,EAAwB;AAC/D,EAAA,IAAS,IAAA,CAAA,UAAA,CAAW,MAAM,CAAA,EAAG,OAAY,eAAU,MAAM,CAAA;AACzD,EAAA,OAAY,IAAA,CAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AACjC;AAiDO,IAAM,cAAA,GAAiB,KAAK,IAAA,GAAO,IAAA;AAOnC,SAAS,sBAAsB,CAAA,EAAmB;AACvD,EAAA,MAAM,IAAA,GAAO,CAAA,CAAE,UAAA,CAAW,CAAA,CAAE,SAAS,CAAC,CAAA;AACtC,EAAA,IAAI,IAAA,IAAQ,SAAU,IAAA,IAAQ,KAAA,SAAe,CAAA,CAAE,KAAA,CAAM,GAAG,EAAE,CAAA;AAC1D,EAAA,OAAO,CAAA;AACT;AAEO,SAAS,WAAA,CAAY,GAAW,GAAA,EAAqB;AAC1D,EAAA,IAAI,CAAA,CAAE,MAAA,IAAU,GAAA,EAAK,OAAO,CAAA;AAC5B,EAAA,OAAO,sBAAsB,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAC,CAAA,GAAI;AAAA,eAAA,EAAoB,CAAA,CAAE,SAAS,GAAG,CAAA,OAAA,CAAA;AACpF;;;AC1DA,eAAsB,WAAA,CAAY,OAAkB,GAAA,EAAmC;AACrF,EAAA,MAAM,EAAE,SAAA,EAAW,MAAA,GAAS,CAAA,EAAG,KAAA,GAAQ,KAAK,GAAI,KAAA;AAChD,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,GAAA,CAAI,GAAA,EAAK,SAAS,CAAA;AAK/C,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,GAAK,GAAA,CAAI,EAAA,GAAK,MAAM,GAAA,CAAI,EAAA,CAAG,IAAA,CAAK,QAAQ,CAAA,GAAI,MAAMA,QAAA,CAAG,IAAA,CAAK,QAAQ,CAAA;AACxE,IAAA,IAAI,EAAA,CAAG,OAAO,cAAA,EAAgB;AAC5B,MAAA,MAAM,IAAIC,UAAAA,CAAW;AAAA,QACnB,IAAA,EAAM,YAAA;AAAA,QACN,OAAA,EAAS,CAAA,4BAAA,EAA0B,EAAA,CAAG,IAAI,eAAe,cAAc,CAAA,2DAAA;AAAA,OACxE,CAAA;AAAA,IACH;AAAA,EACF,SAAS,CAAA,EAAG;AAGV,IAAA,IAAI,CAAA,YAAaA,YAAY,MAAM,CAAA;AAAA,EACrC;AAOA,EAAA,MAAM,OAAO,GAAA,CAAI,EAAA,GACb,MAAM,GAAA,CAAI,EAAA,CAAG,SAAS,QAAA,EAAU,EAAE,UAAU,MAAA,EAAQ,KACnD,MAAMD,QAAA,CAAG,SAAS,QAAQ,CAAA,EAAG,SAAS,MAAM,CAAA;AACjD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ,SAAS,KAAK,CAAA;AACjD,EAAA,MAAM,QAAA,GAAW,OACd,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM,CAAA,EAAG,OAAO,MAAA,GAAS,CAAA,GAAI,CAAC,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,IAAK,IAAI,CAAA,CAAE,CAAA,CACtE,IAAA,CAAK,IAAI,CAAA;AACZ,EAAA,OAAO,WAAA,CAAY,UAAU,GAAO,CAAA;AACtC","file":"read-handler.js","sourcesContent":["import * as path from 'node:path';\nimport { MoxxyError } from '@moxxy/sdk';\n\n/**\n * Normalize `target` against `cwd`. Returns an absolute path. **Does not\n * sandbox** — absolute targets and `../` traversal are allowed by design,\n * because the agent often needs to touch paths outside the cwd (`~/.config`,\n * `/etc/...`). Safety against unintended access lives at the permission\n * layer (`PermissionEngine` + the resolver), which prompts the user before\n * any tool runs. Tools that genuinely need to confine to cwd should use\n * `resolveWithinCwd` instead.\n *\n * Renamed from `resolveSafe` to make the contract honest — the old name\n * implied a sandbox it never performed.\n */\nexport function resolvePath(cwd: string, target: string): string {\n if (path.isAbsolute(target)) return path.normalize(target);\n return path.resolve(cwd, target);\n}\n\n/**\n * Like `resolvePath` but throws if the result escapes `cwd`. Use for tools\n * that should be strictly confined (rare).\n */\nexport function resolveWithinCwd(cwd: string, target: string): string {\n const resolved = resolvePath(cwd, target);\n const cwdAbs = path.resolve(cwd);\n const rel = path.relative(cwdAbs, resolved);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new MoxxyError({\n code: 'TOOL_ERROR',\n message: `Path escapes cwd: ${target} (resolved to ${resolved}, outside ${cwdAbs})`,\n });\n }\n return resolved;\n}\n\n/**\n * @deprecated Use `resolvePath` (no behavior change — only honest name).\n * Kept as a thin alias so external callers still compile.\n */\nexport const resolveSafe = resolvePath;\n\n/**\n * Directory names skipped during recursive traversal by the Glob and Grep\n * tools — build outputs and VCS metadata that are never useful search targets.\n * Kept in one place so the two walkers stay in sync.\n */\nexport const IGNORED_DIR_NAMES = new Set(['node_modules', '.git', 'dist', '.turbo']);\n\n/**\n * Max directory depth either walker (Glob, Grep) descends into. Both recurse\n * into real subdirectories; an unbounded recursion over a pathologically deep\n * tree (build artifacts, fuzzers, a symlink-free but genuinely-deep checkout)\n * would otherwise overflow the call stack with an uncatchable RangeError that\n * the per-dir `try/catch` (which only wraps `readdir`) cannot recover from.\n * Shared so the two walkers stay in sync.\n */\nexport const MAX_WALK_DEPTH = 100;\n\n/**\n * Files larger than this are not slurped fully into the heap — a multi-hundred-MB\n * log, a SQLite db, or a media blob would otherwise OOM the process on a path the\n * model invokes constantly (Read/Edit/Write/Grep). Result clamping bounds the\n * OUTPUT, not the per-file working set; this bounds the working set. Shared so\n * every file-reading built-in caps at the same point.\n */\nexport const MAX_FILE_BYTES = 10 * 1024 * 1024;\n\n/**\n * Slicing a string on a UTF-16 code-unit boundary can split an astral-plane\n * char (emoji, some CJK) and leave a lone surrogate that re-encodes to U+FFFD.\n * Drop a trailing lone high surrogate so the truncated text stays valid.\n */\nexport function dropDanglingSurrogate(s: string): string {\n const last = s.charCodeAt(s.length - 1);\n if (last >= 0xd800 && last <= 0xdbff) return s.slice(0, -1);\n return s;\n}\n\nexport function clampString(s: string, max: number): string {\n if (s.length <= max) return s;\n return dropDanglingSurrogate(s.slice(0, max)) + `\\n... [truncated ${s.length - max} chars]`;\n}\n\n/**\n * Cap the length of a model/user-supplied glob before it is compiled to a\n * RegExp. `globToRegExp` only ever emits linear constructs (`.*`, `[^/]*`,\n * `[^/]`), so it can't produce catastrophic backtracking — but an unbounded\n * input would still build an unbounded regex and let an absurd pattern grow\n * the heap. A 4 KB ceiling is far beyond any real glob and degrades to a clean\n * error instead of a silent blowup. Mirrors Grep's `MAX_PATTERN_LEN`.\n */\nexport const MAX_GLOB_LEN = 4_096;\n\n/**\n * Convert a glob pattern (`**`, `*`, `?`) to an anchored RegExp. Shared by\n * the Glob and Grep tools; not exposed externally. Rejects absurdly long\n * inputs rather than compiling them.\n */\nexport function globToRegExp(pattern: string): RegExp {\n if (pattern.length > MAX_GLOB_LEN) {\n throw new MoxxyError({\n code: 'TOOL_ERROR',\n message: `glob pattern too long (${pattern.length} > ${MAX_GLOB_LEN} chars).`,\n });\n }\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\?/g, '[^/]')\n .replace(/\\*\\*\\//g, '(?:.*/)?')\n .replace(/\\*\\*/g, '.*')\n .replace(/\\*/g, '[^/]*');\n return new RegExp('^' + escaped + '$');\n}\n","import { promises as fs } from 'node:fs';\nimport { MoxxyError, type BrokeredFs } from '@moxxy/sdk';\nimport { clampString, MAX_FILE_BYTES, resolvePath } from './util.js';\n\n/**\n * Pure handler module for the Read tool. Lives in its own file so the\n * worker_threads isolator (`@moxxy/isolator-worker`) can re-import it\n * on the worker side via the `handlerModule` reference declared in\n * `read.ts`.\n *\n * Closures can't cross thread boundaries; module exports can.\n */\nexport interface ReadInput {\n readonly file_path: string;\n readonly offset?: number;\n readonly limit?: number;\n}\n\nexport interface ReadCtxLike {\n readonly cwd: string;\n /** Capability-mediated fs. Present when invoked under an isolator that\n * brokers (`@moxxy/isolator-worker`); absent under `none` / `inproc`. */\n readonly fs?: BrokeredFs;\n}\n\nexport async function readHandler(input: ReadInput, ctx: ReadCtxLike): Promise<string> {\n const { file_path, offset = 0, limit = 2000 } = input;\n const resolved = resolvePath(ctx.cwd, file_path);\n // Refuse to slurp a file that exceeds the working-set cap. Without this the\n // whole file (and a per-line array of it) is materialized in the heap before\n // any output clamp — a multi-hundred-MB log / db / media blob OOM-kills the\n // process on a path the model invokes constantly. (Grep already guards this.)\n try {\n const st = ctx.fs ? await ctx.fs.stat(resolved) : await fs.stat(resolved);\n if (st.size > MAX_FILE_BYTES) {\n throw new MoxxyError({\n code: 'TOOL_ERROR',\n message: `Read: file too large — ${st.size} bytes (max ${MAX_FILE_BYTES}). Use Grep, or narrow with offset/limit on a smaller file.`,\n });\n }\n } catch (e) {\n // Re-throw our own size error; let a genuine stat failure (missing file,\n // perms) fall through to readFile so its error message is preserved.\n if (e instanceof MoxxyError) throw e;\n }\n // Use the brokered fs when the isolator provides one. The broker\n // re-validates the path against the tool's declared `caps.fs.read`\n // on the parent side, so reads outside the cap are denied at the\n // boundary regardless of what's in the input. Without a broker\n // (inproc / none), fall back to direct `node:fs` — input-level\n // cap-check already screened the file_path.\n const text = ctx.fs\n ? await ctx.fs.readFile(resolved, { encoding: 'utf8' })\n : (await fs.readFile(resolved)).toString('utf8');\n const lines = text.split('\\n');\n const sliced = lines.slice(offset, offset + limit);\n const numbered = sliced\n .map((line, i) => `${String(offset + i + 1).padStart(6, ' ')}\\t${line}`)\n .join('\\n');\n return clampString(numbered, 200_000);\n}\n"]}
|
package/dist/sidecar.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../plugin-browser/src/ssrf-guard.ts","../../plugin-browser/src/sidecar/types.ts","../../plugin-browser/src/sidecar/install.ts","../../plugin-browser/src/sidecar/dispatch.ts","../../plugin-browser/src/sidecar.ts"],"names":["dnsLookup","state","text"],"mappings":"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","file":"sidecar.js","sourcesContent":["import { isIP } from 'node:net';\nimport { lookup as dnsLookup } from 'node:dns/promises';\n\n/**\n * Shared SSRF guard for every model-drivable navigation/fetch path in this\n * plugin: `web_fetch` (initial URL + every redirect hop), `browser_session`'s\n * goto (parent side), the sidecar's goto dispatch (child-process side), and\n * the sidecar's in-page navigation interceptor.\n *\n * Blocks non-HTTP(S) schemes, loopback (incl. `localhost`/`*.localhost`),\n * 0.0.0.0/8, RFC-1918 private ranges, link-local 169.254/16 (incl. the\n * 169.254.169.254 cloud metadata endpoint), CGNAT 100.64/10, multicast/\n * reserved, and IPv6 loopback/link-local/unique-local (+ v4-mapped). Hostnames\n * are resolved so an internal DNS name (or rebinding) can't smuggle a private\n * target past the literal-IP checks.\n *\n * NOTE: deliberately NO `@moxxy/sdk` import. The Playwright sidecar — a\n * separate child process whose bundle must stay free of non-builtin deps —\n * imports this module too. Parent-side callers wrap `SsrfBlockedError` into\n * `MoxxyError` themselves.\n */\n\n/** Thrown for every guard rejection (bad scheme, private/loopback target, unparseable URL). */\nexport class SsrfBlockedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SsrfBlockedError';\n }\n}\n\nexport type DnsResolver = (host: string) => Promise<ReadonlyArray<string>>;\nconst defaultResolver: DnsResolver = async (host) =>\n (await dnsLookup(host, { all: true })).map((a) => a.address);\nlet resolver: DnsResolver = defaultResolver;\n\n/** Test seam: override DNS resolution so SSRF tests stay hermetic. Pass null to reset. */\nexport function setSsrfDnsResolver(fn: DnsResolver | null): void {\n resolver = fn ?? defaultResolver;\n}\n\nfunction isBlockedIpv4(ip: string): boolean {\n const parts = ip.split('.').map((p) => Number(p));\n if (parts.length !== 4 || parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) return true;\n const [a, b] = parts as [number, number, number, number];\n if (a === 0) return true; // 0.0.0.0/8 \"this host\"\n if (a === 127) return true; // loopback\n if (a === 10) return true; // private\n if (a === 172 && b >= 16 && b <= 31) return true; // private\n if (a === 192 && b === 168) return true; // private\n if (a === 169 && b === 254) return true; // link-local incl. 169.254.169.254 metadata\n if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT 100.64/10\n if (a >= 224) return true; // multicast / reserved\n return false;\n}\n\nfunction isBlockedIpv6(ip: string): boolean {\n const addr = ip.toLowerCase().replace(/^\\[|\\]$/g, '').split('%')[0]!; // strip zone id\n if (addr === '::1' || addr === '::') return true; // loopback / unspecified\n if (addr.startsWith('fe80')) return true; // link-local\n if (addr.startsWith('fc') || addr.startsWith('fd')) return true; // unique-local fc00::/7\n // Embedded-v4 forms: re-run the v4 policy against the embedded address.\n // The WHATWG URL parser canonicalizes [::ffff:127.0.0.1] to its HEX form\n // ::ffff:7f00:1, so the dotted regex alone NEVER matches a real URL — we must\n // also reconstruct the dotted quad from the trailing hex groups. Covers\n // v4-mapped (::ffff:a.b.c.d / ::ffff:0:a.b.c.d), v4-compatible (::a.b.c.d),\n // and NAT64 (64:ff9b::/96) — any of which could otherwise smuggle a private\n // v4 target (loopback / 169.254.169.254 metadata) past the guard.\n const embeddedV4 = extractEmbeddedV4(addr);\n if (embeddedV4) return isBlockedIpv4(embeddedV4);\n return false;\n}\n\n/**\n * Pull the embedded IPv4 out of any v4-mapped / v4-compatible / NAT64 IPv6\n * address, returning it as a dotted quad, or null if there is none. Handles\n * both the dotted spelling (`::ffff:127.0.0.1`) and — critically — the HEX\n * spelling the URL parser actually produces (`::ffff:7f00:1`).\n */\nfunction extractEmbeddedV4(addr: string): string | null {\n // Dotted spelling: ::ffff:a.b.c.d, ::ffff:0:a.b.c.d, ::a.b.c.d, 64:ff9b::a.b.c.d\n const dotted = addr.match(/(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/);\n if (dotted && (addr.startsWith('::ffff:') || addr.startsWith('::') || addr.startsWith('64:ff9b:'))) {\n return dotted[1]!;\n }\n // Hex spelling: the embedded v4 lives in the two trailing 16-bit groups.\n const hex = addr.match(/^(?:::ffff:|::ffff:0:|64:ff9b:(?::|.*:)|::)([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);\n if (hex) {\n const hi = parseInt(hex[1]!, 16);\n const lo = parseInt(hex[2]!, 16);\n if (Number.isInteger(hi) && Number.isInteger(lo) && hi <= 0xffff && lo <= 0xffff) {\n return `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`;\n }\n }\n return null;\n}\n\nexport function isBlockedIp(ip: string): boolean {\n const kind = isIP(ip);\n if (kind === 4) return isBlockedIpv4(ip);\n if (kind === 6) return isBlockedIpv6(ip);\n return true; // not a parseable IP → block\n}\n\n/**\n * Reject `raw` unless it is an http(s) URL whose host is (and resolves to)\n * a public address. `label` prefixes the error message so each caller's\n * rejections stay attributable (e.g. \"web_fetch\", \"browser_session\").\n *\n * Returns the vetted resolved addresses so callers can PIN the subsequent\n * connection to exactly what was checked (closing the DNS-rebinding TOCTOU\n * where the guard resolves one answer and the fetch independently resolves\n * another — see web-fetch's pinned undici dispatcher). Returns `null` when\n * there is nothing to pin: the host is already an IP literal (vetted above,\n * no DNS involved) or resolution failed (fail-open — a name the guard can't\n * resolve, the fetch's identical system resolver can't reach either).\n */\nexport async function assertPublicUrl(\n raw: string,\n label = 'request',\n opts: { failClosed?: boolean } = {},\n): Promise<ReadonlyArray<string> | null> {\n let u: URL;\n try {\n u = new URL(raw);\n } catch {\n throw new SsrfBlockedError(`${label}: invalid URL: ${raw}`);\n }\n if (u.protocol !== 'http:' && u.protocol !== 'https:') {\n throw new SsrfBlockedError(`${label}: refusing non-HTTP(S) scheme \"${u.protocol}\"`);\n }\n const host = u.hostname.replace(/^\\[|\\]$/g, '');\n if (host === 'localhost' || host.endsWith('.localhost')) {\n throw new SsrfBlockedError(`${label}: refusing to fetch loopback host \"${host}\"`);\n }\n if (isIP(host)) {\n if (isBlockedIp(host))\n throw new SsrfBlockedError(`${label}: refusing private/loopback address \"${host}\"`);\n return null;\n }\n // Resolve the name and block if it maps to a private range (internal name or\n // DNS rebinding). web_fetch fails OPEN on resolution error (its identical\n // system resolver + pinned dispatcher mean an unresolvable name is no vector),\n // but the BROWSER paths hand the URL to Chromium's own resolver/cache — which\n // may resolve a name node:dns just failed on — so they pass `failClosed` to\n // SOFT-BLOCK instead of allowing an un-vetted name through to the browser.\n let addrs: ReadonlyArray<string>;\n try {\n addrs = await resolver(host);\n } catch {\n if (opts.failClosed) {\n throw new SsrfBlockedError(\n `${label}: refusing host \"${host}\" — DNS resolution failed (cannot vet against private ranges)`,\n );\n }\n return null;\n }\n for (const addr of addrs) {\n if (isBlockedIp(addr)) {\n throw new SsrfBlockedError(\n `${label}: host \"${host}\" resolves to a private/loopback address (${addr})`,\n );\n }\n }\n return addrs;\n}\n","export type BrowserKind = 'chromium' | 'firefox' | 'webkit';\n\nexport type ErrorKind =\n | 'init'\n | 'needs-install' // the `playwright` npm package itself isn't installed yet\n | 'navigation'\n | 'runtime'\n | 'timeout'\n | 'unknown';\n\nexport interface Req {\n readonly id: string;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\nexport interface Ok {\n readonly id: string;\n readonly ok: true;\n readonly result?: unknown;\n /** One-shot human-readable note (e.g. \"Auto-installed Chromium\"). */\n readonly notice?: string;\n}\n\nexport interface Err {\n readonly id: string;\n readonly ok: false;\n readonly error: { message: string; kind: ErrorKind };\n}\n\nexport type Reply = Ok | Err;\n\nexport interface BrowserType {\n launch(opts: {\n headless: boolean;\n }): Promise<{ close(): Promise<void>; newContext(opts?: unknown): Promise<unknown> }>;\n}\n\nexport interface PageHandle {\n goto(url: string, opts?: unknown): Promise<unknown>;\n click(selector: string, opts?: unknown): Promise<void>;\n fill(selector: string, value: string, opts?: unknown): Promise<void>;\n textContent(selector: string): Promise<string | null>;\n content(): Promise<string>;\n screenshot(opts?: unknown): Promise<Buffer>;\n evaluate(fn: string): Promise<unknown>;\n url(): string;\n close(): Promise<void>;\n // Coordinate-based input + viewport control for the live browser surface\n // (loosely typed — the real Playwright Page provides all of these).\n viewportSize(): { width: number; height: number } | null;\n setViewportSize(size: { width: number; height: number }): Promise<void>;\n goBack(opts?: unknown): Promise<unknown>;\n goForward(opts?: unknown): Promise<unknown>;\n reload(opts?: unknown): Promise<unknown>;\n readonly mouse: {\n move(x: number, y: number, opts?: unknown): Promise<void>;\n click(x: number, y: number, opts?: unknown): Promise<void>;\n wheel(dx: number, dy: number): Promise<void>;\n };\n readonly keyboard: {\n press(key: string): Promise<void>;\n type(text: string): Promise<void>;\n };\n}\n\n/** Minimal slice of Playwright's `Request` used by the navigation SSRF guard. */\nexport interface RouteRequest {\n url(): string;\n isNavigationRequest(): boolean;\n}\n\n/** Minimal slice of Playwright's `Route` used by the navigation SSRF guard. */\nexport interface RouteHandle {\n request(): RouteRequest;\n abort(errorCode?: string): Promise<void>;\n continue(): Promise<void>;\n}\n\nexport interface PlaywrightHandle {\n // Loosely typed so we can avoid importing the playwright types at compile time —\n // they're an optional peer dependency.\n readonly browser: { close(): Promise<void> };\n readonly context: {\n newPage(): Promise<unknown>;\n close(): Promise<void>;\n /** Optional because the type is a loose projection; real Playwright contexts always have it. */\n route?(pattern: string, handler: (route: RouteHandle) => Promise<void> | void): Promise<void>;\n };\n readonly page: PageHandle;\n}\n\nexport function errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * An error carrying a typed {@link ErrorKind} so the dispatch layer can map it\n * onto the wire reply's `error.kind` without poking an untyped `kind` property\n * onto a bare `Error`.\n */\nexport class SidecarError extends Error {\n constructor(\n message: string,\n readonly kind: ErrorKind,\n ) {\n super(message);\n this.name = 'SidecarError';\n }\n}\n\nexport function badParams(msg: string): SidecarError {\n return new SidecarError(msg, 'runtime');\n}\n","/**\n * Playwright lifecycle: import, launch, and one-shot auto-install of the\n * per-browser binary. Keeps the dispatch layer free of node:child_process\n * + Playwright-import noise.\n */\n\nimport { spawn } from 'node:child_process';\nimport { assertPublicUrl } from '../ssrf-guard.js';\nimport { SidecarError } from './types.js';\nimport type { BrowserKind, BrowserType, PageHandle, PlaywrightHandle } from './types.js';\n\nexport interface LaunchResult {\n handle: PlaywrightHandle;\n /** Set when the browser binary was auto-downloaded during this launch. */\n installNotice: string | null;\n}\n\nexport async function importPlaywright(): Promise<{\n chromium: BrowserType;\n firefox: BrowserType;\n webkit: BrowserType;\n}> {\n try {\n return (await import('playwright')) as never;\n } catch (err) {\n const underlying = err instanceof Error ? err.message : String(err);\n // Distinguish \"the npm package isn't installed\" (recoverable — the surface\n // can offer a one-click install, see `installPlaywrightPackage`) from any\n // other import failure. The `kind` rides the JSON-RPC reply so the browser\n // surface shows an \"Install\" affordance instead of a dead-end error.\n throw new SidecarError(\n `Playwright is not installed. Run \\`pnpm add playwright\\` (or \\`npm i playwright\\`) and then \\`npx playwright install\\` in the moxxy install dir.\\n` +\n `Underlying: ${underlying}`,\n isModuleNotFound(underlying) ? 'needs-install' : 'init',\n );\n }\n}\n\n/** True when an import failure is \"the `playwright` package can't be found\"\n * (vs. a load/runtime error inside an installed package). */\nfunction isModuleNotFound(message: string): boolean {\n return (\n /cannot find (package|module) ['\"]?playwright/i.test(message) ||\n /ERR_MODULE_NOT_FOUND/i.test(message) ||\n /failed to resolve ['\"]?playwright/i.test(message)\n );\n}\n\nexport interface InstallPlaywrightOptions {\n /** Directory whose `node_modules` should receive `playwright` — the CLI\n * install root (e.g. `<userData>/cli`). `npm` runs with this as its cwd. */\n readonly rootDir: string;\n /** Which browser engine binary to download after the npm package lands. */\n readonly browser?: BrowserKind;\n /** Per-line progress (npm/npx stdout+stderr) for streaming to the UI. */\n readonly onProgress?: (line: string) => void;\n readonly signal?: AbortSignal;\n}\n\n/**\n * Install the `playwright` npm package into `rootDir`, then download the browser\n * engine binary — the two halves the desktop browser surface needs. Driven by\n * the surface AFTER the user consents (the download is ~200MB). Streams progress\n * via `onProgress`; resolves on success, rejects with the failing step's output.\n *\n * Lives next to {@link importPlaywright} (which reports the `needs-install` that\n * triggers this) but is invoked in the RUNNER process — `rootDir`'s node_modules\n * is the one the sidecar later imports `playwright` from.\n */\n/** Pin the npm install to this range so the one-click installer fetches a\n * vetted, deterministic version (matching the package's `playwright`\n * peerDependency) rather than whatever \"latest\" resolves to. */\nconst PLAYWRIGHT_VERSION_RANGE = '^1.40.0';\n\nexport async function installPlaywrightPackage(opts: InstallPlaywrightOptions): Promise<void> {\n const which = opts.browser ?? 'chromium';\n opts.onProgress?.(\n `Installing the playwright npm package (${PLAYWRIGHT_VERSION_RANGE}) into ${opts.rootDir}…`,\n );\n // `--save-exact false` keeps the range; the explicit version pin (vs bare\n // \"playwright\") stops a hijacked registry \"latest\" from being pulled.\n await runProcess(\n 'npm',\n ['install', '--no-fund', '--no-audit', `playwright@${PLAYWRIGHT_VERSION_RANGE}`],\n opts,\n );\n opts.onProgress?.(`Downloading the ${which} browser engine (~150MB, one-time)…`);\n await runProcess('npx', ['playwright', 'install', which], opts);\n opts.onProgress?.('Playwright installed.');\n}\n\n/** Spawn a child, forward its output to `onProgress`, resolve on exit-0. */\nfunction runProcess(cmd: string, args: string[], opts: InstallPlaywrightOptions): Promise<void> {\n return new Promise((resolve, reject) => {\n if (opts.signal?.aborted) return reject(new Error('install aborted'));\n const child = spawn(cmd, args, { cwd: opts.rootDir, stdio: ['ignore', 'pipe', 'pipe'] });\n let tail = '';\n const onChunk = (chunk: Buffer): void => {\n const text = chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) if (line.trim()) opts.onProgress?.(line);\n tail = (tail + text).slice(-4000);\n };\n child.stdout.on('data', onChunk);\n child.stderr.on('data', onChunk);\n const onAbort = (): void => {\n child.kill();\n };\n opts.signal?.addEventListener('abort', onAbort, { once: true });\n child.once('error', (err) => {\n opts.signal?.removeEventListener('abort', onAbort);\n reject(err);\n });\n child.once('close', (code) => {\n opts.signal?.removeEventListener('abort', onAbort);\n if (code === 0) resolve();\n else\n reject(\n new Error(`\\`${cmd} ${args.join(' ')}\\` failed (exit ${code}): ${tail.trim() || '(no output)'}`),\n );\n });\n });\n}\n\n/**\n * Try to launch the browser. If the binary isn't downloaded yet\n * (Playwright distinguishes the npm install from the per-browser\n * binary download), run `npx playwright install <which>` once and\n * retry. The install can take 30s–2min on the first run depending on\n * connection; we surface progress on stderr (parent forwards to the\n * logger) and return a one-shot notice for the first tool response.\n */\nexport async function launchWithAutoInstall(\n browserType: BrowserType,\n which: BrowserKind,\n headless: boolean,\n): Promise<LaunchResult> {\n try {\n return { handle: await launchOnce(browserType, headless), installNotice: null };\n } catch (err) {\n if (!isMissingBrowserError(err)) throw err;\n process.stderr.write(\n `moxxy-browser: ${which} binary missing, running \\`npx playwright install ${which}\\` ` +\n `(one-time, ~150MB). This may take a minute…\\n`,\n );\n try {\n await runPlaywrightInstall(which);\n } catch (installErr) {\n const msg = installErr instanceof Error ? installErr.message : String(installErr);\n throw new SidecarError(\n `Playwright browser auto-install failed: ${msg}. ` +\n `Run \\`npx playwright install ${which}\\` manually in the moxxy dir.`,\n 'init',\n );\n }\n process.stderr.write(`moxxy-browser: install complete, retrying launch\\n`);\n return {\n handle: await launchOnce(browserType, headless),\n installNotice: `Auto-installed Playwright ${which} browser (~150MB, one-time).`,\n };\n }\n}\n\nasync function launchOnce(browserType: BrowserType, headless: boolean): Promise<PlaywrightHandle> {\n const browser = await browserType.launch({ headless });\n // deviceScaleFactor: 2 so the live-view / region screenshots are captured at\n // Retina density — a 1× capture upscaled into a HiDPI pane is what made the\n // surface look blurry. Costs ~2× the screenshot bytes; worth it for crisp text.\n const context = (await browser.newContext({ deviceScaleFactor: 2 })) as PlaywrightHandle['context'];\n await installNavigationSsrfGuard(context);\n const page = (await context.newPage()) as unknown as PageHandle;\n return { browser, context, page };\n}\n\n/**\n * Block navigations to private/loopback origins for the lifetime of the\n * context. The goto RPC is validated in the parent AND in dispatch, but a\n * page reached via a legitimate public goto can then redirect or\n * script-navigate itself to e.g. http://169.254.169.254/ — those navigations\n * never pass through the RPC layer, so we intercept them here. Navigation\n * requests (top-level + iframes, which covers HTTP redirect hops too) go\n * through the same `assertPublicUrl` guard as web_fetch; everything else is\n * passed through untouched so ordinary page loads don't pay a DNS round-trip\n * per subresource.\n *\n * Residual risk: by default SUBRESOURCE requests (img/fetch/script) from a\n * loaded page are NOT filtered — a hostile page can fire blind GET/POSTs at\n * internal services as side effects (the browser's same-origin policy stops it\n * READING the responses, but the request still lands). Opt into filtering every\n * request — at the cost of a (cached) DNS resolution per distinct host — by\n * setting `MOXXY_BROWSER_FILTER_SUBRESOURCES=1`.\n */\nasync function installNavigationSsrfGuard(context: PlaywrightHandle['context']): Promise<void> {\n if (typeof context.route !== 'function') {\n // Loose projection: tests pass a stub without route(). But a REAL Playwright\n // context always has route(), so if a production context ever reaches here\n // the in-page navigation guard is silently absent — make that loud on stderr\n // (the parent forwards it to the logger) instead of failing open invisibly.\n process.stderr.write(\n 'moxxy-browser: WARNING context.route() unavailable — in-page navigation SSRF guard NOT installed\\n',\n );\n return;\n }\n const filterSubresources = process.env.MOXXY_BROWSER_FILTER_SUBRESOURCES === '1';\n // Cache per-host guard verdicts so the opt-in subresource path doesn't pay a\n // DNS round-trip per request (pages fire dozens at the same few hosts).\n // Bounded so a page referencing thousands of distinct hosts can't grow it\n // without limit (oldest entries evicted; re-resolved on next sight).\n const verdictCache = new Map<string, boolean>();\n const MAX_VERDICT_CACHE = 2048;\n const remember = (host: string, blocked: boolean): void => {\n if (verdictCache.size >= MAX_VERDICT_CACHE) {\n const oldest = verdictCache.keys().next().value;\n if (oldest !== undefined) verdictCache.delete(oldest);\n }\n verdictCache.set(host, blocked);\n };\n await context.route('**/*', async (route) => {\n const request = route.request();\n const isNav = request.isNavigationRequest();\n if (!isNav && !filterSubresources) return route.continue();\n const url = request.url();\n let host = '';\n try {\n host = new URL(url).host;\n } catch {\n // Unparseable URL — only the navigation path needs a hard verdict.\n if (isNav) return route.abort('blockedbyclient');\n return route.continue();\n }\n // Navigations are always re-checked live (cheap, and the cache is keyed by\n // host so a vetted host stays vetted). Subresources consult the cache first.\n const cached = !isNav ? verdictCache.get(host) : undefined;\n if (cached === true) return route.abort('blockedbyclient');\n if (cached === false) return route.continue();\n try {\n // fail-closed: an unresolvable name must not navigate the browser (whose\n // resolver may differ from node:dns).\n await assertPublicUrl(\n url,\n isNav ? 'browser_session navigation' : 'browser_session subresource',\n { failClosed: true },\n );\n remember(host, false);\n } catch {\n remember(host, true);\n return route.abort('blockedbyclient');\n }\n return route.continue();\n });\n}\n\nfunction isMissingBrowserError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n // Playwright's \"Executable doesn't exist at …\" launch error fires\n // when the npm package is installed but the per-browser binary\n // hasn't been downloaded. The message stays stable across versions.\n return /Executable doesn'?t exist at/i.test(err.message);\n}\n\n/**\n * Run `npx playwright install <which>` and stream its output to the\n * sidecar's stderr so the operator can watch progress. Resolves on\n * exit-0; rejects with the tail of stderr otherwise.\n */\nfunction runPlaywrightInstall(which: BrowserKind): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn('npx', ['playwright', 'install', which], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stderrTail = '';\n child.stdout.on('data', (chunk: Buffer) => process.stderr.write(chunk));\n child.stderr.on('data', (chunk: Buffer) => {\n process.stderr.write(chunk);\n stderrTail += chunk.toString('utf8');\n if (stderrTail.length > 4000) stderrTail = stderrTail.slice(-4000);\n });\n child.once('error', (err) => reject(err));\n child.once('close', (code) => {\n if (code === 0) resolve();\n else reject(new Error(`exit ${code}: ${stderrTail.trim() || '(no stderr)'}`));\n });\n });\n}\n","/**\n * JSON-RPC dispatch table for the sidecar. Each `method` here corresponds\n * one-to-one with the wire-format methods documented in `sidecar.ts`.\n */\n\nimport { assertPublicUrl } from '../ssrf-guard.js';\nimport { importPlaywright, launchWithAutoInstall } from './install.js';\nimport {\n badParams,\n errMsg,\n SidecarError,\n type BrowserKind,\n type PlaywrightHandle,\n type Reply,\n type Req,\n} from './types.js';\n\n/**\n * Ceiling on screenshot/capture waits inside the sidecar so a page whose render\n * is wedged fails the op cleanly instead of blocking the serial request queue\n * indefinitely (the parent has its own per-call timeout as a backstop, but\n * bounding it here drains the queue head sooner). Generous enough for a slow\n * full-page screenshot; well under the parent ceiling.\n */\nconst SCREENSHOT_TIMEOUT_MS = 30_000;\n/** Hard ceiling on viewport / screenshot-clip dimensions, matching Chromium's\n * max texture/screenshot size — bounds allocation from a malformed surface\n * message (e.g. width:1e9). */\nconst MAX_DIMENSION = 16_384;\n\nexport interface SidecarState {\n handle: PlaywrightHandle | null;\n /**\n * Set after a successful auto-install of browser binaries so the next\n * tool result can carry a `notice` letting the user/model know the\n * one-time download happened. Cleared once the notice has been\n * delivered (handed to the reply once, then forgotten).\n */\n pendingInstallNotice: string | null;\n}\n\nasync function ensurePlaywright(\n state: SidecarState,\n opts: { browser?: BrowserKind; headless?: boolean },\n): Promise<PlaywrightHandle> {\n if (state.handle) return state.handle;\n const pw = await importPlaywright();\n const which = opts.browser ?? 'chromium';\n const browserType = pw[which];\n const { handle, installNotice } = await launchWithAutoInstall(browserType, which, opts.headless ?? true);\n state.handle = handle;\n if (installNotice) state.pendingInstallNotice = installNotice;\n return state.handle;\n}\n\nexport async function teardown(state: SidecarState): Promise<void> {\n if (!state.handle) return;\n try {\n await state.handle.context.close();\n await state.handle.browser.close();\n } catch {\n /* ignore */\n }\n state.handle = null;\n}\n\nexport async function dispatch(state: SidecarState, req: Req): Promise<Reply> {\n try {\n return await dispatchInner(state, req);\n } catch (err) {\n return {\n id: req.id,\n ok: false,\n error: { message: errMsg(err), kind: err instanceof SidecarError ? err.kind : 'unknown' },\n };\n }\n}\n\nasync function dispatchInner(state: SidecarState, req: Req): Promise<Reply> {\n switch (req.method) {\n case 'init': {\n const opts = (req.params ?? {}) as { browser?: BrowserKind; headless?: boolean };\n await ensurePlaywright(state, opts);\n return { id: req.id, ok: true, result: { ready: true } };\n }\n case 'goto': {\n const { url, waitUntil, timeoutMs } = (req.params ?? {}) as {\n url: string;\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';\n timeoutMs?: number;\n };\n if (!url) throw badParams('url is required');\n // Defence-in-depth: the parent already runs the full SSRF guard before\n // sending this RPC, but the sidecar is a distinct process driven over\n // JSON-RPC, so re-check here rather than trust the caller to have\n // validated. Blocks file:// / javascript: schemes AND loopback/private/\n // link-local (incl. 169.254.169.254 metadata)/CGNAT targets, resolving\n // hostnames. Runs BEFORE ensurePlaywright so a blocked URL never\n // launches (or auto-installs) a browser.\n try {\n // fail-closed: the browser resolves names with Chromium's own resolver,\n // so a name node:dns can't vet must not pass through un-checked.\n await assertPublicUrl(url, 'goto', { failClosed: true });\n } catch (err) {\n return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'navigation' } };\n }\n const h = await ensurePlaywright(state, {});\n try {\n await h.page.goto(url, { waitUntil: waitUntil ?? 'domcontentloaded', timeout: timeoutMs ?? 30_000 });\n } catch (err) {\n return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'navigation' } };\n }\n return { id: req.id, ok: true, result: { url: h.page.url() } };\n }\n case 'click': {\n const h = await ensurePlaywright(state, {});\n const { selector, timeoutMs } = (req.params ?? {}) as { selector: string; timeoutMs?: number };\n if (!selector) throw badParams('selector is required');\n await h.page.click(selector, { timeout: timeoutMs ?? 10_000 });\n return { id: req.id, ok: true };\n }\n case 'fill': {\n const h = await ensurePlaywright(state, {});\n const { selector, value, timeoutMs } = (req.params ?? {}) as {\n selector: string;\n value: string;\n timeoutMs?: number;\n };\n if (!selector) throw badParams('selector is required');\n await h.page.fill(selector, value ?? '', { timeout: timeoutMs ?? 10_000 });\n return { id: req.id, ok: true };\n }\n case 'text': {\n const h = await ensurePlaywright(state, {});\n const { selector } = (req.params ?? {}) as { selector?: string };\n if (selector) {\n const text = await h.page.textContent(selector);\n return { id: req.id, ok: true, result: text ?? '' };\n }\n // Whole-document text via evaluate\n const text = (await h.page.evaluate('document.body ? document.body.innerText : \"\"')) as string;\n return { id: req.id, ok: true, result: text };\n }\n case 'html': {\n const h = await ensurePlaywright(state, {});\n const html = await h.page.content();\n return { id: req.id, ok: true, result: html };\n }\n case 'screenshot': {\n const h = await ensurePlaywright(state, {});\n const { fullPage } = (req.params ?? {}) as { fullPage?: boolean };\n const buf = await h.page.screenshot({ fullPage: fullPage ?? false, timeout: SCREENSHOT_TIMEOUT_MS });\n return { id: req.id, ok: true, result: { mediaType: 'image/png', base64: buf.toString('base64') } };\n }\n case 'frame': {\n // Combined live-view frame for the browser SURFACE: a JPEG screenshot\n // plus the current url + viewport size, so the renderer can map clicks\n // back onto the page. One round-trip per frame.\n const h = await ensurePlaywright(state, {});\n // quality 70 (was 55) + the context's deviceScaleFactor:2 = legible text in\n // the live view. Reports the CSS viewport size (the image is 2× that) so the\n // renderer keeps mapping clicks in CSS coords.\n const buf = await h.page.screenshot({ type: 'jpeg', quality: 70, timeout: SCREENSHOT_TIMEOUT_MS });\n const vp = h.page.viewportSize() ?? { width: 1280, height: 720 };\n return {\n id: req.id,\n ok: true,\n result: {\n mediaType: 'image/jpeg',\n base64: buf.toString('base64'),\n url: h.page.url(),\n width: vp.width,\n height: vp.height,\n },\n };\n }\n case 'mouse': {\n const { x, y, count } = (req.params ?? {}) as { x: number; y: number; count?: number };\n // Parity with `key`/`eval`: validate before launching/driving the page so\n // a malformed surface message (missing/NaN coords) surfaces a clean\n // `badParams` instead of an opaque Playwright throw from click(undefined).\n if (!Number.isFinite(x) || !Number.isFinite(y)) throw badParams('x and y must be finite numbers');\n const h = await ensurePlaywright(state, {});\n await h.page.mouse.click(x, y, { clickCount: Math.min(3, Math.max(1, count ?? 1)) });\n return { id: req.id, ok: true, result: { url: h.page.url() } };\n }\n case 'mousemove': {\n // Hover: drives the page's pointer so :hover styles / tooltips render in\n // the polled frame. Cheap; the surface throttles how often it sends these.\n const { x, y } = (req.params ?? {}) as { x: number; y: number };\n if (!Number.isFinite(x) || !Number.isFinite(y)) throw badParams('x and y must be finite numbers');\n const h = await ensurePlaywright(state, {});\n await h.page.mouse.move(x, y);\n return { id: req.id, ok: true };\n }\n case 'setviewport': {\n // Resize the page to the pane so the live view fills the container instead\n // of being letterboxed at the default 1280×720.\n const { width, height } = (req.params ?? {}) as { width: number; height: number };\n if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) {\n throw badParams('width and height must be positive finite numbers');\n }\n // Clamp to Chromium's max so a malformed surface message (width:1e9) can't\n // trigger a multi-GB allocation / opaque Playwright throw.\n if (width > MAX_DIMENSION || height > MAX_DIMENSION) {\n throw badParams(`width and height must be <= ${MAX_DIMENSION}`);\n }\n const h = await ensurePlaywright(state, {});\n await h.page.setViewportSize({ width: Math.round(width), height: Math.round(height) });\n return { id: req.id, ok: true };\n }\n case 'back':\n case 'forward':\n case 'reload': {\n const h = await ensurePlaywright(state, {});\n try {\n if (req.method === 'back') await h.page.goBack();\n else if (req.method === 'forward') await h.page.goForward();\n else await h.page.reload();\n } catch (err) {\n // No history to go to is not an error worth failing the surface over.\n return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'navigation' } };\n }\n return { id: req.id, ok: true, result: { url: h.page.url() } };\n }\n case 'key': {\n const h = await ensurePlaywright(state, {});\n const { key } = (req.params ?? {}) as { key: string };\n if (!key) throw badParams('key is required');\n // A single printable char is typed (inserts it); a named key is pressed.\n if (key.length === 1) await h.page.keyboard.type(key);\n else await h.page.keyboard.press(key);\n return { id: req.id, ok: true };\n }\n case 'scroll': {\n const h = await ensurePlaywright(state, {});\n const { dy } = (req.params ?? {}) as { dy: number };\n await h.page.mouse.wheel(0, dy ?? 0);\n return { id: req.id, ok: true };\n }\n case 'zoom': {\n // Page zoom for the surface (⌘+/⌘−). CSS `zoom` is the cheapest faithful\n // way to scale a screenshot-streamed page; clamped to a sane range.\n const { factor } = (req.params ?? {}) as { factor: number };\n const f = Number.isFinite(factor) ? Math.min(5, Math.max(0.25, factor)) : 1;\n const h = await ensurePlaywright(state, {});\n await h.page.evaluate(`document.documentElement.style.zoom=String(${f})`);\n return { id: req.id, ok: true };\n }\n case 'capture': {\n // Sharp PNG of a region the user dragged — attached to the chat composer so\n // the agent SEES the area (\"change this to …\"). Coords are CSS px; the\n // context's deviceScaleFactor:2 makes the PNG 2× → crisp.\n const { x, y, width, height } = (req.params ?? {}) as {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n if (![x, y, width, height].every((n) => Number.isFinite(n)) || width < 1 || height < 1) {\n throw badParams('x, y, width, height must be finite; width/height positive');\n }\n // Bound the clip so an enormous (or hostile, viewport-multiplied) region\n // can't request a multi-GB screenshot allocation.\n if (width > MAX_DIMENSION || height > MAX_DIMENSION) {\n throw badParams(`clip width and height must be <= ${MAX_DIMENSION}`);\n }\n const h = await ensurePlaywright(state, {});\n const buf = await h.page.screenshot({ type: 'png', clip: { x, y, width, height }, timeout: SCREENSHOT_TIMEOUT_MS });\n return { id: req.id, ok: true, result: { mediaType: 'image/png', base64: buf.toString('base64') } };\n }\n case 'pick': {\n // Identify the element at (x,y) so the user can hand it to the agent\n // (\"change this XXX to YYY\"). Returns a best-effort CSS selector + a short\n // text snippet; the agent's browser_session tool can act on the selector.\n const { x, y } = (req.params ?? {}) as { x: number; y: number };\n if (!Number.isFinite(x) || !Number.isFinite(y)) throw badParams('x and y must be finite numbers');\n const h = await ensurePlaywright(state, {});\n const expr =\n `(() => { const x=${x}, y=${y}; const el=document.elementFromPoint(x,y);` +\n ` if(!el) return null;` +\n ` const sel=(e)=>{ if(e.id) return '#'+CSS.escape(e.id); const p=[]; let n=e;` +\n ` while(n && n.nodeType===1 && n!==document.body){ let s=n.tagName.toLowerCase();` +\n ` if(n.classList && n.classList.length) s+='.'+Array.from(n.classList).slice(0,2).map(c=>CSS.escape(c)).join('.');` +\n ` const par=n.parentElement; if(par){ const same=Array.from(par.children).filter(c=>c.tagName===n.tagName);` +\n ` if(same.length>1) s+=':nth-of-type('+(same.indexOf(n)+1)+')'; } p.unshift(s); n=n.parentElement; }` +\n ` return p.join(' > '); };` +\n ` return { selector: sel(el), tag: el.tagName.toLowerCase(), text: (el.textContent||'').replace(/\\\\s+/g,' ').trim().slice(0,140) }; })()`;\n const info = await h.page.evaluate(expr);\n return { id: req.id, ok: true, result: info };\n }\n case 'eval': {\n const h = await ensurePlaywright(state, {});\n const { expression } = (req.params ?? {}) as { expression: string };\n if (!expression) throw badParams('expression is required');\n const value = await h.page.evaluate(expression);\n return { id: req.id, ok: true, result: value };\n }\n case 'url': {\n const h = await ensurePlaywright(state, {});\n return { id: req.id, ok: true, result: h.page.url() };\n }\n case 'close': {\n await teardown(state);\n return { id: req.id, ok: true };\n }\n default:\n return {\n id: req.id,\n ok: false,\n error: { message: `unknown method: ${req.method}`, kind: 'runtime' },\n };\n }\n}\n","#!/usr/bin/env node\n/**\n * Playwright sidecar — owns a single browser context. Speaks newline-delimited\n * JSON-RPC over stdio so the parent (`browser_session` tool) doesn't load\n * Playwright into its own process. Crash isolation, lazy install: the parent\n * only spawns this when the heavy tier is actually needed.\n *\n * Wire format (one line per message):\n * { \"id\": \"uuid\", \"method\": \"goto\"|\"click\"|\"fill\"|\"text\"|\"html\"|\"screenshot\"|\"eval\"|\"close\", \"params\": {...} }\n * { \"id\": \"uuid\", \"ok\": true, \"result\": ... }\n * { \"id\": \"uuid\", \"ok\": false, \"error\": { \"message\": \"...\", \"kind\": \"init\"|\"navigation\"|\"runtime\"|\"timeout\" } }\n *\n * Run with `node dist/sidecar.js` (the package's `bin` entry is\n * `moxxy-browser-sidecar`). Parent terminates by closing stdin or sending\n * `{method:'close'}`.\n */\n\nimport * as readline from 'node:readline';\nimport { fileURLToPath } from 'node:url';\nimport { dispatch, teardown, type SidecarState } from './sidecar/dispatch.js';\nimport { errMsg, type Reply, type Req } from './sidecar/types.js';\n\nconst state: SidecarState = { handle: null, pendingInstallNotice: null };\n\nfunction write(reply: Reply): void {\n // Drain the install-notice flag into the first reply that goes out\n // after the install completed, then clear it. Errors get the notice\n // too — sometimes the launch retry surfaces a different problem and\n // the user still wants to know we tried to install.\n if (state.pendingInstallNotice) {\n if (reply.ok) {\n reply = { ...reply, notice: state.pendingInstallNotice };\n } else {\n reply = {\n ...reply,\n error: {\n ...reply.error,\n message: `${state.pendingInstallNotice} Then: ${reply.error.message}`,\n },\n };\n }\n state.pendingInstallNotice = null;\n }\n process.stdout.write(JSON.stringify(reply) + '\\n');\n}\n\n/**\n * Tears down the browser context AND exits the process. Used both by\n * the explicit `close` RPC path and the parent-loss / stdin-close\n * paths so all cleanup goes through one routine.\n */\nasync function shutdownAndExit(code: number): Promise<void> {\n await teardown(state);\n process.exit(code);\n}\n\n/**\n * Parent watchdog: if the moxxy process that spawned this sidecar\n * disappears (crash, SIGKILL, terminal hangup), there's no stdin EOF\n * to rely on — orphan Chromium would keep running and chew CPU/memory\n * until the user notices. Poll the parent PID every few seconds and\n * self-terminate when it goes away.\n *\n * `process.kill(pid, 0)` is the POSIX trick for \"does this PID\n * exist?\" — no signal is actually delivered. Throws ESRCH when the\n * process is gone (or EPERM when it exists but we can't signal it —\n * still alive, so don't treat as gone).\n */\nfunction startParentWatchdog(): void {\n const parentPid = process.ppid;\n if (!parentPid || parentPid <= 1) return; // already orphaned (init), nothing to watch\n const interval = setInterval(() => {\n try {\n process.kill(parentPid, 0);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ESRCH') {\n clearInterval(interval);\n void shutdownAndExit(0);\n }\n // EPERM means the process exists but we can't signal it — still alive.\n }\n }, 2000);\n interval.unref?.(); // never block the event loop from exiting\n}\n\nlet queue: Promise<void> = Promise.resolve();\n\n/**\n * Parse one input line and chain its dispatch onto the serial request queue.\n * `out` is the sink for replies (real run: {@link write}; tests inject a stub).\n * Exported so a test can drive the queue without spinning up the whole stdio\n * loop. Returns the queue tail so callers can await it.\n *\n * The `.catch` on the chained link is load-bearing: a throw from `out` (e.g. a\n * broken stdout pipe) would otherwise reject `queue` permanently and strand\n * EVERY subsequent request, since each link chains off the previous one.\n * Swallow it here — after emitting a best-effort error reply — so the next\n * request still serves.\n */\nexport function enqueueLine(line: string, out: (reply: Reply) => void): Promise<void> {\n if (!line.trim()) return queue;\n let req: Req;\n try {\n req = JSON.parse(line) as Req;\n } catch {\n // Wholly unparseable — there's no id to recover, so the parent can't match\n // this reply; it's best-effort diagnostic output only.\n out({ id: 'unknown', ok: false, error: { message: 'invalid JSON', kind: 'runtime' } });\n return queue;\n }\n if (!req.id || !req.method) {\n // Echo the request's real id whenever it round-tripped (only `method` was\n // missing), so the parent can SETTLE the matching pending call instead of\n // dropping the reply and stranding the caller until its timeout. Fall back\n // to 'unknown' only when the id genuinely can't be recovered.\n out({\n id: typeof req.id === 'string' && req.id ? req.id : 'unknown',\n ok: false,\n error: { message: 'request requires { id, method }', kind: 'runtime' },\n });\n return queue;\n }\n // Sequentially serve requests on the single page. Parent can pipeline by\n // sending more requests; we serialize them inside the sidecar so a goto\n // doesn't race a click.\n queue = queue\n .then(async () => {\n const reply = await dispatch(state, req);\n out(reply);\n })\n .catch((err) => {\n try {\n out({ id: req.id, ok: false, error: { message: errMsg(err), kind: 'runtime' } });\n } catch {\n /* stdout is gone too — nothing left to do but keep the queue alive */\n }\n });\n return queue;\n}\n\nasync function main(): Promise<void> {\n startParentWatchdog();\n const rl = readline.createInterface({ input: process.stdin });\n rl.on('line', (line) => {\n void enqueueLine(line, write);\n });\n rl.once('close', () => {\n void shutdownAndExit(0);\n });\n // Defensive: if our stdout pipe breaks (parent died mid-write), Node\n // would otherwise throw an uncaught EPIPE on the next write. Treat\n // it as a signal to clean up gracefully instead.\n process.stdout.on('error', (err) => {\n if ((err as NodeJS.ErrnoException).code === 'EPIPE') {\n void shutdownAndExit(0);\n }\n });\n}\n\n// Auto-run only when executed as the bin script — not when imported (tests).\nif (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {\n main().catch((err) => {\n process.stderr.write(`sidecar fatal: ${errMsg(err)}\\n`);\n void shutdownAndExit(1);\n });\n}\n"]}
|