@agentproto/browser-process 0.1.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jeremy André and agentproto contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,48 @@
1
+ import { ChildProcess } from 'node:child_process';
2
+
3
+ /**
4
+ * Shared primitive for launching and health-waiting browser service processes.
5
+ *
6
+ * Two exports:
7
+ * - `waitHealthy` — poll a health URL until it responds OK or timeout.
8
+ * - `ensureBrowserProcess` — idempotent: probe once, skip spawn if already up,
9
+ * else launch via spec.launch() and wait for health.
10
+ */
11
+
12
+ interface BrowserProcessSpec {
13
+ kind: string;
14
+ healthUrl: string;
15
+ /**
16
+ * Spawn the service and return its ChildProcess, OR return null if the
17
+ * process is externally managed (e.g. launchd) — in that case `pid` in the
18
+ * result will be `undefined`.
19
+ */
20
+ launch(): ChildProcess | null;
21
+ timeoutMs?: number;
22
+ intervalMs?: number;
23
+ log?: (msg: string) => void;
24
+ }
25
+ interface BrowserProcessResult {
26
+ baseUrl: string;
27
+ wasAlreadyRunning: boolean;
28
+ pid?: number;
29
+ stop?: () => Promise<void>;
30
+ }
31
+ /**
32
+ * Poll `healthUrl` until it returns an OK response, then return that Response.
33
+ * Throws if `timeoutMs` (default 60 000) elapses before a healthy response.
34
+ */
35
+ declare function waitHealthy(healthUrl: string, opts?: {
36
+ timeoutMs?: number;
37
+ intervalMs?: number;
38
+ log?: (s: string) => void;
39
+ }): Promise<Response>;
40
+ /**
41
+ * Idempotent service launcher.
42
+ * 1. Probes `spec.healthUrl` — if already healthy, returns `{ wasAlreadyRunning: true }`.
43
+ * 2. Calls `spec.launch()` (may return `null` for externally managed services).
44
+ * 3. Polls health until `spec.timeoutMs` — returns `{ pid, wasAlreadyRunning: false }`.
45
+ */
46
+ declare function ensureBrowserProcess(spec: BrowserProcessSpec): Promise<BrowserProcessResult>;
47
+
48
+ export { type BrowserProcessResult, type BrowserProcessSpec, ensureBrowserProcess, waitHealthy };
package/dist/index.mjs ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @agentproto/browser-process v0.1.0-alpha
3
+ * Shared primitive for launching and health-waiting browser service processes.
4
+ */
5
+
6
+ // src/index.ts
7
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
8
+ async function probe(healthUrl) {
9
+ const ac = new AbortController();
10
+ const t = setTimeout(() => ac.abort(), 3e3);
11
+ try {
12
+ const r = await fetch(healthUrl, { signal: ac.signal });
13
+ return r.ok;
14
+ } catch {
15
+ return false;
16
+ } finally {
17
+ clearTimeout(t);
18
+ }
19
+ }
20
+ async function waitHealthy(healthUrl, opts) {
21
+ const timeoutMs = opts?.timeoutMs ?? 6e4;
22
+ const intervalMs = opts?.intervalMs ?? 1e3;
23
+ const log = opts?.log;
24
+ const deadline = Date.now() + timeoutMs;
25
+ while (Date.now() < deadline) {
26
+ await sleep(intervalMs);
27
+ const ac = new AbortController();
28
+ const t = setTimeout(() => ac.abort(), 3e3);
29
+ try {
30
+ const r = await fetch(healthUrl, { signal: ac.signal });
31
+ if (r.ok) return r;
32
+ } catch {
33
+ } finally {
34
+ clearTimeout(t);
35
+ }
36
+ log?.(`waiting for ${healthUrl}\u2026`);
37
+ }
38
+ throw new Error(
39
+ `service at ${healthUrl} did not become healthy within ${timeoutMs / 1e3}s`
40
+ );
41
+ }
42
+ async function ensureBrowserProcess(spec) {
43
+ const baseUrl = new URL(spec.healthUrl).origin;
44
+ if (await probe(spec.healthUrl)) {
45
+ return { baseUrl, wasAlreadyRunning: true };
46
+ }
47
+ const child = spec.launch();
48
+ const res = await waitHealthy(spec.healthUrl, {
49
+ timeoutMs: spec.timeoutMs,
50
+ intervalMs: spec.intervalMs,
51
+ log: spec.log
52
+ });
53
+ await res.body?.cancel().catch(() => {
54
+ });
55
+ return { baseUrl, pid: child?.pid, wasAlreadyRunning: false };
56
+ }
57
+
58
+ export { ensureBrowserProcess, waitHealthy };
59
+ //# sourceMappingURL=index.mjs.map
60
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAWA,IAAM,KAAA,GAAQ,CAAC,EAAA,KAAe,IAAI,QAAc,CAAA,CAAA,KAAK,UAAA,CAAW,CAAA,EAAG,EAAE,CAAC,CAAA;AAuBtE,eAAe,MAAM,SAAA,EAAqC;AACxD,EAAA,MAAM,EAAA,GAAK,IAAI,eAAA,EAAgB;AAC/B,EAAA,MAAM,IAAI,UAAA,CAAW,MAAM,EAAA,CAAG,KAAA,IAAS,GAAI,CAAA;AAC3C,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,MAAM,KAAA,CAAM,SAAA,EAAW,EAAE,MAAA,EAAQ,EAAA,CAAG,QAAQ,CAAA;AACtD,IAAA,OAAO,CAAA,CAAE,EAAA;AAAA,EACX,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,CAAC,CAAA;AAAA,EAChB;AACF;AAMA,eAAsB,WAAA,CACpB,WACA,IAAA,EAKmB;AACnB,EAAA,MAAM,SAAA,GAAY,MAAM,SAAA,IAAa,GAAA;AACrC,EAAA,MAAM,UAAA,GAAa,MAAM,UAAA,IAAc,GAAA;AACvC,EAAA,MAAM,MAAM,IAAA,EAAM,GAAA;AAClB,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAC9B,EAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,QAAA,EAAU;AAC5B,IAAA,MAAM,MAAM,UAAU,CAAA;AACtB,IAAA,MAAM,EAAA,GAAK,IAAI,eAAA,EAAgB;AAC/B,IAAA,MAAM,IAAI,UAAA,CAAW,MAAM,EAAA,CAAG,KAAA,IAAS,GAAI,CAAA;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,CAAA,GAAI,MAAM,KAAA,CAAM,SAAA,EAAW,EAAE,MAAA,EAAQ,EAAA,CAAG,QAAQ,CAAA;AACtD,MAAA,IAAI,CAAA,CAAE,IAAI,OAAO,CAAA;AAAA,IACnB,CAAA,CAAA,MAAQ;AAAA,IAER,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,CAAC,CAAA;AAAA,IAChB;AACA,IAAA,GAAA,GAAM,CAAA,YAAA,EAAe,SAAS,CAAA,MAAA,CAAG,CAAA;AAAA,EACnC;AACA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA,WAAA,EAAc,SAAS,CAAA,+BAAA,EAAkC,SAAA,GAAY,GAAI,CAAA,CAAA;AAAA,GAC3E;AACF;AAQA,eAAsB,qBACpB,IAAA,EAC+B;AAC/B,EAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,IAAA,CAAK,SAAS,CAAA,CAAE,MAAA;AAExC,EAAA,IAAI,MAAM,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA,EAAG;AAC/B,IAAA,OAAO,EAAE,OAAA,EAAS,iBAAA,EAAmB,IAAA,EAAK;AAAA,EAC5C;AAEA,EAAA,MAAM,KAAA,GAAQ,KAAK,MAAA,EAAO;AAE1B,EAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAY,IAAA,CAAK,SAAA,EAAW;AAAA,IAC5C,WAAW,IAAA,CAAK,SAAA;AAAA,IAChB,YAAY,IAAA,CAAK,UAAA;AAAA,IACjB,KAAK,IAAA,CAAK;AAAA,GACX,CAAA;AACD,EAAA,MAAM,GAAA,CAAI,IAAA,EAAM,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,EAAC,CAAC,CAAA;AAEvC,EAAA,OAAO,EAAE,OAAA,EAAS,GAAA,EAAK,KAAA,EAAO,GAAA,EAAK,mBAAmB,KAAA,EAAM;AAC9D","file":"index.mjs","sourcesContent":["/**\n * Shared primitive for launching and health-waiting browser service processes.\n *\n * Two exports:\n * - `waitHealthy` — poll a health URL until it responds OK or timeout.\n * - `ensureBrowserProcess` — idempotent: probe once, skip spawn if already up,\n * else launch via spec.launch() and wait for health.\n */\n\nimport type { ChildProcess } from \"node:child_process\"\n\nconst sleep = (ms: number) => new Promise<void>(r => setTimeout(r, ms))\n\nexport interface BrowserProcessSpec {\n kind: string\n healthUrl: string\n /**\n * Spawn the service and return its ChildProcess, OR return null if the\n * process is externally managed (e.g. launchd) — in that case `pid` in the\n * result will be `undefined`.\n */\n launch(): ChildProcess | null\n timeoutMs?: number\n intervalMs?: number\n log?: (msg: string) => void\n}\n\nexport interface BrowserProcessResult {\n baseUrl: string\n wasAlreadyRunning: boolean\n pid?: number\n stop?: () => Promise<void>\n}\n\nasync function probe(healthUrl: string): Promise<boolean> {\n const ac = new AbortController()\n const t = setTimeout(() => ac.abort(), 3000)\n try {\n const r = await fetch(healthUrl, { signal: ac.signal })\n return r.ok\n } catch {\n return false\n } finally {\n clearTimeout(t)\n }\n}\n\n/**\n * Poll `healthUrl` until it returns an OK response, then return that Response.\n * Throws if `timeoutMs` (default 60 000) elapses before a healthy response.\n */\nexport async function waitHealthy(\n healthUrl: string,\n opts?: {\n timeoutMs?: number\n intervalMs?: number\n log?: (s: string) => void\n }\n): Promise<Response> {\n const timeoutMs = opts?.timeoutMs ?? 60_000\n const intervalMs = opts?.intervalMs ?? 1_000\n const log = opts?.log\n const deadline = Date.now() + timeoutMs\n while (Date.now() < deadline) {\n await sleep(intervalMs)\n const ac = new AbortController()\n const t = setTimeout(() => ac.abort(), 3000)\n try {\n const r = await fetch(healthUrl, { signal: ac.signal })\n if (r.ok) return r\n } catch {\n // not yet healthy — keep polling\n } finally {\n clearTimeout(t)\n }\n log?.(`waiting for ${healthUrl}…`)\n }\n throw new Error(\n `service at ${healthUrl} did not become healthy within ${timeoutMs / 1000}s`\n )\n}\n\n/**\n * Idempotent service launcher.\n * 1. Probes `spec.healthUrl` — if already healthy, returns `{ wasAlreadyRunning: true }`.\n * 2. Calls `spec.launch()` (may return `null` for externally managed services).\n * 3. Polls health until `spec.timeoutMs` — returns `{ pid, wasAlreadyRunning: false }`.\n */\nexport async function ensureBrowserProcess(\n spec: BrowserProcessSpec\n): Promise<BrowserProcessResult> {\n const baseUrl = new URL(spec.healthUrl).origin\n\n if (await probe(spec.healthUrl)) {\n return { baseUrl, wasAlreadyRunning: true }\n }\n\n const child = spec.launch()\n\n const res = await waitHealthy(spec.healthUrl, {\n timeoutMs: spec.timeoutMs,\n intervalMs: spec.intervalMs,\n log: spec.log,\n })\n await res.body?.cancel().catch(() => {})\n\n return { baseUrl, pid: child?.pid, wasAlreadyRunning: false }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@agentproto/browser-process",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "@agentproto/browser-process — shared primitive for launching and health-waiting browser service processes (Camofox, Bureau, Chromium). Idempotent: skips spawn if already healthy. Usable by both agentproto browser adapters and app-layer launchers.",
5
+ "keywords": [
6
+ "agentproto",
7
+ "browser",
8
+ "camofox",
9
+ "health-check",
10
+ "process",
11
+ "launcher"
12
+ ],
13
+ "homepage": "https://agentproto.sh",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/agentproto/ts",
17
+ "directory": "packages/browser-process"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/agentproto/ts/issues"
21
+ },
22
+ "license": "MIT",
23
+ "type": "module",
24
+ "main": "dist/index.mjs",
25
+ "module": "dist/index.mjs",
26
+ "types": "dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.mjs",
31
+ "default": "./dist/index.mjs"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^25.6.2",
45
+ "tsup": "^8.5.1",
46
+ "typescript": "^5.9.3",
47
+ "vitest": "^3.2.4",
48
+ "@agentproto/tooling": "0.1.0-alpha.0"
49
+ },
50
+ "scripts": {
51
+ "dev": "tsup --watch",
52
+ "build": "tsup",
53
+ "clean": "rm -rf dist",
54
+ "check-types": "tsc --noEmit",
55
+ "test": "vitest run --passWithNoTests",
56
+ "test:watch": "vitest"
57
+ }
58
+ }