@agentproto/browser-process 0.1.0-alpha.0 → 0.1.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/index.d.ts +18 -0
- package/dist/index.mjs +32 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -21,10 +21,28 @@ interface BrowserProcessSpec {
|
|
|
21
21
|
timeoutMs?: number;
|
|
22
22
|
intervalMs?: number;
|
|
23
23
|
log?: (msg: string) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Opt-in non-blocking cold start. When set, `ensureBrowserProcess` polls
|
|
26
|
+
* health for only this many milliseconds after `launch()`. If the service
|
|
27
|
+
* becomes healthy in that window it returns `{ healthy: true }` as usual;
|
|
28
|
+
* if not, it kicks off a detached background `waitHealthy` (up to
|
|
29
|
+
* `timeoutMs`) so the booting process keeps converging, and returns
|
|
30
|
+
* IMMEDIATELY with `{ healthy: false }`. When unset (default), behaviour is
|
|
31
|
+
* unchanged — blocking wait up to `timeoutMs`.
|
|
32
|
+
*/
|
|
33
|
+
initialWaitMs?: number;
|
|
24
34
|
}
|
|
25
35
|
interface BrowserProcessResult {
|
|
26
36
|
baseUrl: string;
|
|
27
37
|
wasAlreadyRunning: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the service is confirmed healthy at return time. Always true on
|
|
40
|
+
* the warm path (`wasAlreadyRunning: true`) and on the blocking cold path.
|
|
41
|
+
* Only `false` when `spec.initialWaitMs` was set and the service had not
|
|
42
|
+
* become healthy within that bounded window — convergence continues in the
|
|
43
|
+
* background.
|
|
44
|
+
*/
|
|
45
|
+
healthy: boolean;
|
|
28
46
|
pid?: number;
|
|
29
47
|
stop?: () => Promise<void>;
|
|
30
48
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -42,9 +42,39 @@ async function waitHealthy(healthUrl, opts) {
|
|
|
42
42
|
async function ensureBrowserProcess(spec) {
|
|
43
43
|
const baseUrl = new URL(spec.healthUrl).origin;
|
|
44
44
|
if (await probe(spec.healthUrl)) {
|
|
45
|
-
return { baseUrl, wasAlreadyRunning: true };
|
|
45
|
+
return { baseUrl, wasAlreadyRunning: true, healthy: true };
|
|
46
46
|
}
|
|
47
47
|
const child = spec.launch();
|
|
48
|
+
if (spec.initialWaitMs !== void 0) {
|
|
49
|
+
try {
|
|
50
|
+
const res2 = await waitHealthy(spec.healthUrl, {
|
|
51
|
+
timeoutMs: spec.initialWaitMs,
|
|
52
|
+
intervalMs: spec.intervalMs,
|
|
53
|
+
log: spec.log
|
|
54
|
+
});
|
|
55
|
+
await res2.body?.cancel().catch(() => {
|
|
56
|
+
});
|
|
57
|
+
return { baseUrl, pid: child?.pid, wasAlreadyRunning: false, healthy: true };
|
|
58
|
+
} catch {
|
|
59
|
+
spec.log?.(
|
|
60
|
+
`[${spec.kind}] not healthy within ${spec.initialWaitMs}ms \u2014 continuing health-wait in background`
|
|
61
|
+
);
|
|
62
|
+
void waitHealthy(spec.healthUrl, {
|
|
63
|
+
timeoutMs: spec.timeoutMs,
|
|
64
|
+
intervalMs: spec.intervalMs,
|
|
65
|
+
log: spec.log
|
|
66
|
+
}).then(async (res2) => {
|
|
67
|
+
await res2.body?.cancel().catch(() => {
|
|
68
|
+
});
|
|
69
|
+
spec.log?.(`[${spec.kind}] became healthy (background)`);
|
|
70
|
+
}).catch((err) => {
|
|
71
|
+
spec.log?.(
|
|
72
|
+
`[${spec.kind}] background health-wait gave up: ${err instanceof Error ? err.message : String(err)}`
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
return { baseUrl, pid: child?.pid, wasAlreadyRunning: false, healthy: false };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
48
78
|
const res = await waitHealthy(spec.healthUrl, {
|
|
49
79
|
timeoutMs: spec.timeoutMs,
|
|
50
80
|
intervalMs: spec.intervalMs,
|
|
@@ -52,7 +82,7 @@ async function ensureBrowserProcess(spec) {
|
|
|
52
82
|
});
|
|
53
83
|
await res.body?.cancel().catch(() => {
|
|
54
84
|
});
|
|
55
|
-
return { baseUrl, pid: child?.pid, wasAlreadyRunning: false };
|
|
85
|
+
return { baseUrl, pid: child?.pid, wasAlreadyRunning: false, healthy: true };
|
|
56
86
|
}
|
|
57
87
|
|
|
58
88
|
export { ensureBrowserProcess, waitHealthy };
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["res"],"mappings":"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","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 * Opt-in non-blocking cold start. When set, `ensureBrowserProcess` polls\n * health for only this many milliseconds after `launch()`. If the service\n * becomes healthy in that window it returns `{ healthy: true }` as usual;\n * if not, it kicks off a detached background `waitHealthy` (up to\n * `timeoutMs`) so the booting process keeps converging, and returns\n * IMMEDIATELY with `{ healthy: false }`. When unset (default), behaviour is\n * unchanged — blocking wait up to `timeoutMs`.\n */\n initialWaitMs?: number\n}\n\nexport interface BrowserProcessResult {\n baseUrl: string\n wasAlreadyRunning: boolean\n /**\n * Whether the service is confirmed healthy at return time. Always true on\n * the warm path (`wasAlreadyRunning: true`) and on the blocking cold path.\n * Only `false` when `spec.initialWaitMs` was set and the service had not\n * become healthy within that bounded window — convergence continues in the\n * background.\n */\n healthy: 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, healthy: true }\n }\n\n const child = spec.launch()\n\n // ── Non-blocking cold start (opt-in via spec.initialWaitMs) ─────────────────\n if (spec.initialWaitMs !== undefined) {\n try {\n const res = await waitHealthy(spec.healthUrl, {\n timeoutMs: spec.initialWaitMs,\n intervalMs: spec.intervalMs,\n log: spec.log,\n })\n await res.body?.cancel().catch(() => {})\n return { baseUrl, pid: child?.pid, wasAlreadyRunning: false, healthy: true }\n } catch {\n // Not healthy within the bounded window — keep converging in the\n // background (fire-and-forget) so the booting process isn't abandoned,\n // and return promptly with healthy: false.\n spec.log?.(\n `[${spec.kind}] not healthy within ${spec.initialWaitMs}ms — continuing health-wait in background`\n )\n void waitHealthy(spec.healthUrl, {\n timeoutMs: spec.timeoutMs,\n intervalMs: spec.intervalMs,\n log: spec.log,\n })\n .then(async res => {\n await res.body?.cancel().catch(() => {})\n spec.log?.(`[${spec.kind}] became healthy (background)`)\n })\n .catch(err => {\n spec.log?.(\n `[${spec.kind}] background health-wait gave up: ${\n err instanceof Error ? err.message : String(err)\n }`\n )\n })\n return { baseUrl, pid: child?.pid, wasAlreadyRunning: false, healthy: false }\n }\n }\n\n // ── Blocking cold start (default) ───────────────────────────────────────────\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, healthy: true }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/browser-process",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|