@broberg/lens-daemon 0.1.39 → 0.1.41
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/package.json +1 -1
- package/src/browser.ts +16 -2
- package/src/ensure-chromium.ts +31 -9
- package/src/flow-store.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broberg/lens-daemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "The Lens module of the cardmem daemon (flows, critic, coverage, capture sessions, the /lens/* routes and the cardmem-lens MCP) as a package. The daemon mounts createLensRouter(deps) + createLensMcpRouter(deps). Bun runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/browser.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { bindProfileChannel, claimProfile, killOwnBrowsersOn, profileDir, type ProfileChannel } from './profiles.ts';
|
|
14
14
|
import { sweepChromeClones, trackChromeLaunch } from './chrome-clones.ts';
|
|
15
|
-
import { launchWithRepair, type BrowserRepair } from './ensure-chromium.ts';
|
|
15
|
+
import { launchWithRepair, repairInFlight, type BrowserRepair } from './ensure-chromium.ts';
|
|
16
16
|
import {
|
|
17
17
|
chromium,
|
|
18
18
|
webkit,
|
|
@@ -119,6 +119,20 @@ export async function getBrowser(engine: LensEngine = 'chromium'): Promise<Brows
|
|
|
119
119
|
// deadline; on timeout, discard the wedged browser and retry once fresh.
|
|
120
120
|
const WEDGE_TIMEOUT_MS = 20_000;
|
|
121
121
|
|
|
122
|
+
/** F010.1 — the wedge deadline on a warm launch, except while that engine's
|
|
123
|
+
* browser is being reinstalled: then the repair is waited for (it has its own
|
|
124
|
+
* bound) and the deadline applies again after it. Exported for test. */
|
|
125
|
+
export async function launchDeadline<T>(p: Promise<T>, engine: LensEngine, ms: number): Promise<T> {
|
|
126
|
+
try {
|
|
127
|
+
return await withDeadline(p, ms, 'browser launch');
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const repair = repairInFlight(engine);
|
|
130
|
+
if (!repair || !(err instanceof Error && err.message.includes('lens browser wedged'))) throw err;
|
|
131
|
+
await repair;
|
|
132
|
+
return withDeadline(p, ms, 'browser launch');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
function withDeadline<T>(p: Promise<T>, ms: number, what: string): Promise<T> {
|
|
123
137
|
return new Promise<T>((resolve, reject) => {
|
|
124
138
|
const t = setTimeout(
|
|
@@ -546,7 +560,7 @@ export async function withPage<T>(
|
|
|
546
560
|
}
|
|
547
561
|
|
|
548
562
|
const makeContext = async (): Promise<BrowserContext> => {
|
|
549
|
-
const browser = await
|
|
563
|
+
const browser = await launchDeadline(getBrowser(engine), engine, WEDGE_TIMEOUT_MS);
|
|
550
564
|
return withDeadline(
|
|
551
565
|
browser.newContext(contextOptions(opts)),
|
|
552
566
|
WEDGE_TIMEOUT_MS,
|
package/src/ensure-chromium.ts
CHANGED
|
@@ -148,6 +148,8 @@ export function isMissingBrowserError(err: unknown): boolean {
|
|
|
148
148
|
|
|
149
149
|
type Installer = (names: string[]) => Promise<{ ok: boolean; error?: string }>;
|
|
150
150
|
|
|
151
|
+
const INSTALL_TIMEOUT_MS = 10 * 60_000;
|
|
152
|
+
|
|
151
153
|
const spawnInstaller: Installer = (names) =>
|
|
152
154
|
new Promise((resolve) => {
|
|
153
155
|
let cliPath: string;
|
|
@@ -159,8 +161,13 @@ const spawnInstaller: Installer = (names) =>
|
|
|
159
161
|
}
|
|
160
162
|
console.log(`[lens] browser missing — installing ${names.join(', ')} via ${cliPath} …`);
|
|
161
163
|
const child = spawn(process.execPath, [cliPath, 'install', ...names], { stdio: 'inherit', env: process.env });
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
// A download that stalls must not hold every run waiting on it forever.
|
|
165
|
+
const timer = setTimeout(() => {
|
|
166
|
+
child.kill('SIGKILL');
|
|
167
|
+
resolve({ ok: false, error: `playwright install did not finish within ${INSTALL_TIMEOUT_MS / 60_000} min` });
|
|
168
|
+
}, INSTALL_TIMEOUT_MS);
|
|
169
|
+
child.on('error', (err) => { clearTimeout(timer); resolve({ ok: false, error: err.message }); });
|
|
170
|
+
child.on('exit', (code) => { clearTimeout(timer); resolve(code === 0 ? { ok: true } : { ok: false, error: `playwright install exited ${code}` }); });
|
|
164
171
|
});
|
|
165
172
|
|
|
166
173
|
let installer: Installer = spawnInstaller;
|
|
@@ -198,16 +205,31 @@ export async function launchWithRepair<T>(
|
|
|
198
205
|
} catch (err) {
|
|
199
206
|
if (!isMissingBrowserError(err)) throw err;
|
|
200
207
|
const t0 = Date.now();
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
const repair = (async () => {
|
|
209
|
+
const inst = await installBrowsers(engine);
|
|
210
|
+
if (!inst.ok) {
|
|
211
|
+
throw new Error(`${engine} browser is missing and could not be installed: ${inst.error ?? 'unknown'} — original: ${(err as Error).message.split('\n')[0]}`);
|
|
212
|
+
}
|
|
213
|
+
const out = await launch();
|
|
214
|
+
onRepaired?.({ engine, ms: Date.now() - t0 });
|
|
215
|
+
return out;
|
|
216
|
+
})();
|
|
217
|
+
const settled = repair.then(() => {}, () => {});
|
|
218
|
+
repairsInFlight.set(engine, settled);
|
|
219
|
+
void settled.then(() => { if (repairsInFlight.get(engine) === settled) repairsInFlight.delete(engine); });
|
|
220
|
+
return repair;
|
|
208
221
|
}
|
|
209
222
|
}
|
|
210
223
|
|
|
224
|
+
// F010.1 — measured live 25 Oct: the warm launch sits under a 20 s "wedged"
|
|
225
|
+
// deadline, and a firefox download takes longer, so the run failed while the
|
|
226
|
+
// repair was still installing. A caller's deadline asks this first: a repair in
|
|
227
|
+
// flight (install, bounded by INSTALL_TIMEOUT_MS, then one relaunch) is waited for.
|
|
228
|
+
const repairsInFlight = new Map<BrowserEngine, Promise<void>>();
|
|
229
|
+
export function repairInFlight(engine: BrowserEngine): Promise<void> | undefined {
|
|
230
|
+
return repairsInFlight.get(engine);
|
|
231
|
+
}
|
|
232
|
+
|
|
211
233
|
/** For /health: does each engine's browser binary exist where this daemon looks?
|
|
212
234
|
* chromium is reported for both the full build and the headless shell, because
|
|
213
235
|
* the warm headless launch needs the shell (the 25 Oct gap boot never checked). */
|
package/src/flow-store.ts
CHANGED
|
@@ -183,6 +183,12 @@ export async function recordFlowRun(
|
|
|
183
183
|
// F009.128 — always written when the run reported it, empty included.
|
|
184
184
|
...(result.downloads ? { downloads: storedDownloads } : {}),
|
|
185
185
|
...(networkShapes ? { network_shapes: networkShapes } : {}),
|
|
186
|
+
// F009.137 / F010.1 / F010.7 — produced in flow.ts and, until measured live on
|
|
187
|
+
// 25 Oct (run 1a337c03: a mail was consumed, the report said nothing), written
|
|
188
|
+
// nowhere. Counts and engines only; never a code or a mail.
|
|
189
|
+
...(result.mail ? { mail: result.mail } : {}),
|
|
190
|
+
...(result.browser_repaired ? { browser_repaired: result.browser_repaired } : {}),
|
|
191
|
+
...(result.chrome_clones_removed ? { chrome_clones_removed: result.chrome_clones_removed } : {}),
|
|
186
192
|
}),
|
|
187
193
|
};
|
|
188
194
|
|