@mind-harness/mh-browser 0.1.3 → 0.1.5

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Mind Harness",
4
- "version": "0.1.3",
4
+ "version": "0.1.5",
5
5
  "permissions": ["tabGroups"],
6
6
  "background": { "service_worker": "sw.js" }
7
7
  }
package/lib/index.js CHANGED
@@ -2964,7 +2964,7 @@ async function launchBrowser(options) {
2964
2964
  acceptDownloads: true,
2965
2965
  ...options.headless ? {} : {
2966
2966
  ignoreDefaultArgs: ["--disable-extensions"],
2967
- args: ["--enable-unsafe-extension-debugging"]
2967
+ args: ["--enable-unsafe-extension-debugging", "--keep-alive-for-test"]
2968
2968
  },
2969
2969
  ...options.browser === "chromium" ? {} : { channel: options.browser },
2970
2970
  ...options.executablePath !== void 0 ? { executablePath: options.executablePath } : {}
@@ -4297,8 +4297,9 @@ var Windows = class {
4297
4297
  if (browser === null) throw new Error("persistent context has no browser");
4298
4298
  this.browserSession = await browser.newBrowserCDPSession();
4299
4299
  if (options.headed) this.extension = await Extension.load(context, this.browserSession);
4300
- for (const page of context.pages()) if (page.url() === "about:blank") await page.close().catch(() => void 0);
4300
+ const launched = context.pages().filter((page) => page.url() === "about:blank");
4301
4301
  context.on("page", (page) => {
4302
+ for (const blank of launched.splice(0)) blank.close().catch(() => void 0);
4302
4303
  this.ingest(page);
4303
4304
  });
4304
4305
  }
@@ -4995,7 +4996,7 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
4995
4996
  const NAVIGATION_MS = 1e4;
4996
4997
  /** How long an open that started a download waits for the download event. */
4997
4998
  const DOWNLOAD_EVENT_MS = 3e3;
4998
- /** More crashes than this within the window and the browser stays down. */
4999
+ /** More exits than this within the window and the browser is not started again until one ages out. */
4999
5000
  const RELAUNCH_LIMIT = 3;
5000
5001
  const RELAUNCH_WINDOW_MS = 6e4;
5001
5002
  const STOP_WAIT_MS = 5e3;
@@ -5043,8 +5044,9 @@ let MhBrowserService = (() => {
5043
5044
  context;
5044
5045
  ready;
5045
5046
  closing = false;
5046
- relaunching;
5047
- crashes = [];
5047
+ /** The browser exited. The next call that needs a page starts it again. */
5048
+ exited = false;
5049
+ exits = [];
5048
5050
  /** One queue per owner: a slow wait in one Session does not hold up another. */
5049
5051
  queues = /* @__PURE__ */ new Map();
5050
5052
  shown = /* @__PURE__ */ new Map();
@@ -5099,7 +5101,7 @@ let MhBrowserService = (() => {
5099
5101
  }
5100
5102
  async open(owner, items, signal) {
5101
5103
  return this.exclusive(owner, async () => {
5102
- await this.ready;
5104
+ await this.up(owner === PERSON_OWNER);
5103
5105
  const views = [];
5104
5106
  for (const item of items) {
5105
5107
  if (signal?.aborted) {
@@ -5159,7 +5161,7 @@ let MhBrowserService = (() => {
5159
5161
  async presenceCheck(owner, which, opts) {
5160
5162
  if (this.book.entries(owner).length === 0 && this.book.peekRemovals(owner).length === 0) return;
5161
5163
  return this.exclusive(owner, async () => {
5162
- const down = await this.ready.then(() => void 0, (error) => error instanceof Error ? error.message : String(error));
5164
+ const down = await this.up().then(() => void 0, (error) => error instanceof Error ? error.message : String(error));
5163
5165
  const pages = this.book.entries(owner);
5164
5166
  const removals = this.book.peekRemovals(owner);
5165
5167
  const wanted = new Set(which === "all" ? pages.map((page) => page.url) : which.map(canonical));
@@ -5215,7 +5217,7 @@ let MhBrowserService = (() => {
5215
5217
  }
5216
5218
  async fetch(url, opts) {
5217
5219
  try {
5218
- await this.ready;
5220
+ await this.up();
5219
5221
  const context = this.context;
5220
5222
  if (context === void 0) return {
5221
5223
  kind: "error",
@@ -5261,14 +5263,18 @@ let MhBrowserService = (() => {
5261
5263
  async started() {
5262
5264
  await this.ready;
5263
5265
  }
5266
+ /** The person asked for the browser: start it if it exited, past the exit limit. */
5267
+ startForPerson() {
5268
+ return this.up(true);
5269
+ }
5264
5270
  [testHooks]() {
5265
5271
  return {
5266
5272
  crash: async () => {
5267
5273
  this.windows.suppressPersonInput();
5268
5274
  await (this.context?.browser())?.close();
5269
- await this.relaunching;
5270
5275
  },
5271
- page: (owner, tab) => this.windows.get(owner, tab)?.page
5276
+ page: (owner, tab) => this.windows.get(owner, tab)?.page,
5277
+ running: () => this.context !== void 0
5272
5278
  };
5273
5279
  }
5274
5280
  async act(owner, tab, op, signal) {
@@ -5356,7 +5362,7 @@ let MhBrowserService = (() => {
5356
5362
  }
5357
5363
  invoke(owner, _page, call, opts) {
5358
5364
  return this.exclusive(owner, async () => {
5359
- await this.ready;
5365
+ await this.up();
5360
5366
  if (opts?.signal?.aborted) throw new MhBrowserError(INTERRUPTED, WAITING_STOPPED);
5361
5367
  const signal = opts?.signal;
5362
5368
  const sent = await runInvoke({
@@ -5376,7 +5382,7 @@ let MhBrowserService = (() => {
5376
5382
  * Session's other calls. `root` is the directory the session may write.
5377
5383
  */
5378
5384
  async download(_owner, url, root, signal) {
5379
- await this.ready;
5385
+ await this.up();
5380
5386
  return this.saveResponse(url, root, signal);
5381
5387
  }
5382
5388
  controlOf(owner) {
@@ -5395,7 +5401,7 @@ let MhBrowserService = (() => {
5395
5401
  */
5396
5402
  handover(owner, req, signal) {
5397
5403
  return this.exclusive(owner, async () => {
5398
- await this.ready;
5404
+ await this.up();
5399
5405
  if (signal?.aborted === true) throw new MhBrowserError(INTERRUPTED, WAITING_STOPPED);
5400
5406
  if (req.tab !== void 0) {
5401
5407
  const live = this.windows.get(owner, req.tab);
@@ -6320,27 +6326,39 @@ let MhBrowserService = (() => {
6320
6326
  this.context = context;
6321
6327
  await this.windows.attach(context, this.launchOptions.profileDir, { headed: !this.launchOptions.headless });
6322
6328
  context.browser()?.on("disconnected", () => {
6323
- if (this.closing) return;
6324
- this.relaunching = this.relaunch();
6329
+ if (this.closing || this.context !== context) return;
6330
+ this.onExit();
6325
6331
  });
6326
6332
  }
6327
- async relaunch() {
6333
+ /** No window opens on exit: the person may have quit it. Calls on old tabs find none. */
6334
+ onExit() {
6328
6335
  this.windows.suppressPersonInput();
6329
6336
  this.windows.dropPages();
6330
6337
  this.context = void 0;
6338
+ this.exits.push(Date.now());
6339
+ this.exited = true;
6340
+ this.ready = Promise.resolve();
6341
+ }
6342
+ /**
6343
+ * The browser for a call that needs a page. One that exited starts again
6344
+ * here. The exit limit stops unattended restarts; a person's request is
6345
+ * one start per click and skips it.
6346
+ */
6347
+ up(byPerson = false) {
6348
+ if (!this.exited || this.closing) return this.ready;
6331
6349
  const now = Date.now();
6332
- while (this.crashes.length > 0 && now - (this.crashes[0] ?? now) > RELAUNCH_WINDOW_MS) this.crashes.shift();
6333
- this.crashes.push(now);
6334
- if (this.crashes.length > RELAUNCH_LIMIT) {
6335
- const down = Promise.reject(new MhBrowserError(BROWSER_UNAVAILABLE, `the browser exited ${String(this.crashes.length)} times within ${String(RELAUNCH_WINDOW_MS / 1e3)} s and was not restarted`));
6336
- down.catch(() => void 0);
6337
- this.ready = down;
6338
- this.ctx.logger.warn("browser keeps exiting; not restarting it");
6339
- return;
6350
+ while (this.exits.length > 0 && now - (this.exits[0] ?? now) > RELAUNCH_WINDOW_MS) this.exits.shift();
6351
+ if (!byPerson && this.exits.length > RELAUNCH_LIMIT) {
6352
+ this.ctx.logger.warn("browser keeps exiting; not starting it again yet");
6353
+ return Promise.reject(new MhBrowserError(BROWSER_UNAVAILABLE, `the browser exited ${String(this.exits.length)} times within ${String(RELAUNCH_WINDOW_MS / 1e3)} s; try again in a minute`));
6340
6354
  }
6341
- this.ready = this.launch();
6342
- await this.ready.catch(() => void 0);
6343
- this.windows.resumePersonInput();
6355
+ this.exited = false;
6356
+ const started = this.launch();
6357
+ started.then(() => this.windows.resumePersonInput(), () => {
6358
+ this.exited = true;
6359
+ });
6360
+ this.ready = started;
6361
+ return started;
6344
6362
  }
6345
6363
  async stop() {
6346
6364
  this.windows.suppressPersonInput();
@@ -44,7 +44,7 @@ const $remote = createTypertRemoteInfo({
44
44
  create: _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema,
45
45
  jsonSchema: {"type":"object","properties":{"status":{"type":"string","enum":["revealed","not-handing-over","no-tab"]}},"required":["status"],"additionalProperties":true},
46
46
  },
47
- sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":610,"column":9},
47
+ sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":616,"column":9},
48
48
  },
49
49
  },
50
50
  }, z)
@@ -160,6 +160,13 @@ export const TYPERT = {
160
160
  "name": "started",
161
161
  "signature": "async started(): Promise<void>"
162
162
  },
163
+ {
164
+ "kind": "method",
165
+ "name": "startForPerson",
166
+ "signature": "startForPerson(): Promise<void>",
167
+ "summary": "The person asked for the browser: start it if it exited, past the exit limit.",
168
+ "jsDoc": "/** The person asked for the browser: start it if it exited, past the exit limit. */"
169
+ },
163
170
  {
164
171
  "kind": "method",
165
172
  "name": "[testHooks]",
@@ -352,7 +359,7 @@ export const TYPERT = {
352
359
  },
353
360
  {
354
361
  "name": "TestHooks",
355
- "declaration": "export interface TestHooks {\n crash(): Promise<void>;\n page(owner: string, tab: string): unknown;\n}"
362
+ "declaration": "export interface TestHooks {\n crash(): Promise<void>;\n page(owner: string, tab: string): unknown;\n running(): boolean;\n}"
356
363
  },
357
364
  {
358
365
  "name": "WaitFor",
@@ -44,7 +44,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
44
44
  create: _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema,
45
45
  jsonSchema: {"type":"object","properties":{"status":{"type":"string","enum":["revealed","not-handing-over","no-tab"]}},"required":["status"],"additionalProperties":true},
46
46
  },
47
- sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":610,"column":9},
47
+ sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":616,"column":9},
48
48
  },
49
49
  },
50
50
  }, z)
@@ -36,8 +36,9 @@ export declare class MhBrowserService extends TypertRemoteService {
36
36
  private context;
37
37
  private ready;
38
38
  private closing;
39
- private relaunching;
40
- private readonly crashes;
39
+ /** The browser exited. The next call that needs a page starts it again. */
40
+ private exited;
41
+ private readonly exits;
41
42
  /** One queue per owner: a slow wait in one Session does not hold up another. */
42
43
  private readonly queues;
43
44
  private readonly shown;
@@ -105,6 +106,8 @@ export declare class MhBrowserService extends TypertRemoteService {
105
106
  }>;
106
107
  pushPath(owner: string, tab: string, path: string): Promise<void>;
107
108
  started(): Promise<void>;
109
+ /** The person asked for the browser: start it if it exited, past the exit limit. */
110
+ startForPerson(): Promise<void>;
108
111
  [testHooks](): TestHooks;
109
112
  act(owner: string, tab: string, op: PageOp, signal?: AbortSignal): Promise<PageView>;
110
113
  holdHandover(owner: string, tab: string): void;
@@ -219,7 +222,14 @@ export declare class MhBrowserService extends TypertRemoteService {
219
222
  /** A failed launch is logged here; calls that need the browser see it as `BROWSER_UNAVAILABLE`. */
220
223
  private launch;
221
224
  private start;
222
- private relaunch;
225
+ /** No window opens on exit: the person may have quit it. Calls on old tabs find none. */
226
+ private onExit;
227
+ /**
228
+ * The browser for a call that needs a page. One that exited starts again
229
+ * here. The exit limit stops unattended restarts; a person's request is
230
+ * one start per click and skips it.
231
+ */
232
+ private up;
223
233
  private stop;
224
234
  private refuseIfPerson;
225
235
  private takenOver;
@@ -3,5 +3,7 @@ export declare const testHooks: unique symbol;
3
3
  export interface TestHooks {
4
4
  crash(): Promise<void>;
5
5
  page(owner: string, tab: string): unknown;
6
+ /** A browser process is up. */
7
+ running(): boolean;
6
8
  }
7
9
  //# sourceMappingURL=test-hooks.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mind-harness/mh-browser",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Opens pages in a real browser for the agent, keeps chosen Agent Views in context, and hands the browser to you when a person is needed.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "files": [