@mind-harness/mh-browser 0.2.2 → 0.2.3
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/extension/manifest.json +1 -1
- package/extension/welcome.css +94 -0
- package/extension/welcome.html +40 -0
- package/extension/welcome.js +15 -0
- package/lib/index.js +81 -17
- package/lib/typert.host.js +3 -3
- package/lib/typert.remote-client.js +1 -1
- package/lib/types/service/browser.d.ts +11 -0
- package/lib/types/service/extension.d.ts +5 -1
- package/lib/types/service/service.d.ts +4 -1
- package/lib/types/service/test-hooks.d.ts +4 -0
- package/package.json +1 -1
package/extension/manifest.json
CHANGED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* Keep in step with the sign-in callback page in packages/mh-cloud/src/page.ts. */
|
|
2
|
+
:root {
|
|
3
|
+
--bg: #f4f4f6;
|
|
4
|
+
--card: #fff;
|
|
5
|
+
--stroke: rgba(0, 0, 0, 0.08);
|
|
6
|
+
--text: #18181b;
|
|
7
|
+
--muted: #6b6b73;
|
|
8
|
+
--accent: #4d6bfe;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media (prefers-color-scheme: dark) {
|
|
12
|
+
:root {
|
|
13
|
+
--bg: #161618;
|
|
14
|
+
--card: #202023;
|
|
15
|
+
--stroke: rgba(255, 255, 255, 0.08);
|
|
16
|
+
--text: #ededf0;
|
|
17
|
+
--muted: #9a9aa3;
|
|
18
|
+
--accent: #7f95ff;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
html,
|
|
27
|
+
body {
|
|
28
|
+
height: 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
margin: 0;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
padding: 24px;
|
|
37
|
+
background: var(--bg);
|
|
38
|
+
color: var(--text);
|
|
39
|
+
font:
|
|
40
|
+
15px/1.6 -apple-system,
|
|
41
|
+
BlinkMacSystemFont,
|
|
42
|
+
'Segoe UI',
|
|
43
|
+
'PingFang SC',
|
|
44
|
+
'Hiragino Sans GB',
|
|
45
|
+
'Microsoft YaHei',
|
|
46
|
+
sans-serif;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.card {
|
|
50
|
+
width: 100%;
|
|
51
|
+
max-width: 420px;
|
|
52
|
+
padding: 36px 32px 28px;
|
|
53
|
+
border: 1px solid var(--stroke);
|
|
54
|
+
border-radius: 20px;
|
|
55
|
+
background: var(--card);
|
|
56
|
+
text-align: center;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.product {
|
|
60
|
+
margin: 0 0 24px;
|
|
61
|
+
font-size: 12px;
|
|
62
|
+
letter-spacing: 0.06em;
|
|
63
|
+
text-transform: uppercase;
|
|
64
|
+
color: var(--muted);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.mark {
|
|
68
|
+
width: 56px;
|
|
69
|
+
height: 56px;
|
|
70
|
+
margin: 0 auto 16px;
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
color: var(--accent);
|
|
76
|
+
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
h1 {
|
|
80
|
+
margin: 0 0 8px;
|
|
81
|
+
font-size: 20px;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.line {
|
|
86
|
+
margin: 0 0 4px;
|
|
87
|
+
color: var(--muted);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.hint {
|
|
91
|
+
margin: 12px 0 0;
|
|
92
|
+
font-size: 13px;
|
|
93
|
+
color: var(--muted);
|
|
94
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
7
|
+
<title data-text="tab">Mind Harness browser</title>
|
|
8
|
+
<link rel="stylesheet" href="welcome.css" />
|
|
9
|
+
<script src="welcome.js" defer></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<main class="card">
|
|
13
|
+
<p class="product">Mind Harness</p>
|
|
14
|
+
<div class="mark" aria-hidden="true">
|
|
15
|
+
<svg
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
width="28"
|
|
18
|
+
height="28"
|
|
19
|
+
fill="none"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
stroke-width="2.2"
|
|
22
|
+
stroke-linecap="round"
|
|
23
|
+
stroke-linejoin="round"
|
|
24
|
+
>
|
|
25
|
+
<rect x="3.5" y="5" width="17" height="14" rx="2.5" />
|
|
26
|
+
<path d="M3.5 9.5h17" />
|
|
27
|
+
</svg>
|
|
28
|
+
</div>
|
|
29
|
+
<h1 data-text="title">This is the Mind Harness browser</h1>
|
|
30
|
+
<p class="line" data-text="what">
|
|
31
|
+
The agent opens pages, reads them, and acts on them here. Sites you sign in to here stay
|
|
32
|
+
signed in for the agent.
|
|
33
|
+
</p>
|
|
34
|
+
<p class="line" data-text="keep">
|
|
35
|
+
Please keep this browser running. When the agent needs you, the conversation will tell you.
|
|
36
|
+
</p>
|
|
37
|
+
<p class="hint" data-text="closes">This window closes when the agent opens its first page.</p>
|
|
38
|
+
</main>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const ZH = {
|
|
2
|
+
tab: 'Mind Harness 浏览器',
|
|
3
|
+
title: '这是 Mind\u00a0Harness 的浏览器',
|
|
4
|
+
what: 'Agent 在这里替你打开网页、读取内容、完成操作。你在这里登录过的网站,Agent 之后也能用上。',
|
|
5
|
+
keep: '请不要退出这个浏览器。需要你来接手时,对话里会提示你。',
|
|
6
|
+
closes: 'Agent 打开第一个网页后,这个窗口会自动关闭。',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (navigator.language.toLowerCase().startsWith('zh')) {
|
|
10
|
+
document.documentElement.lang = 'zh-CN'
|
|
11
|
+
for (const node of document.querySelectorAll('[data-text]')) {
|
|
12
|
+
const text = ZH[node.getAttribute('data-text')]
|
|
13
|
+
if (text !== undefined) node.textContent = text
|
|
14
|
+
}
|
|
15
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { assertSupportedJsonSchema, defineTool, validateJsonSchemaValue } from "
|
|
|
7
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import { parse } from "yaml";
|
|
9
9
|
import { constants, readFileSync } from "node:fs";
|
|
10
|
-
import { lstat, mkdir, open, readFile, realpath, unlink, writeFile } from "node:fs/promises";
|
|
10
|
+
import { lstat, mkdir, open, readFile, readlink, realpath, unlink, writeFile } from "node:fs/promises";
|
|
11
11
|
import { z as z$1 } from "zod";
|
|
12
12
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
13
13
|
import { chromium } from "playwright";
|
|
@@ -2979,6 +2979,50 @@ async function launchBrowser(options) {
|
|
|
2979
2979
|
throw error;
|
|
2980
2980
|
}
|
|
2981
2981
|
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Process id of the browser's main process. Playwright gives none for a
|
|
2984
|
+
* persistent context. Asks the browser first; on POSIX the profile's
|
|
2985
|
+
* `SingletonLock` link names it as `<host>-<pid>`.
|
|
2986
|
+
*/
|
|
2987
|
+
async function browserPid(context, profileDir) {
|
|
2988
|
+
const browser = context.browser();
|
|
2989
|
+
if (browser !== null) try {
|
|
2990
|
+
const session = await browser.newBrowserCDPSession();
|
|
2991
|
+
try {
|
|
2992
|
+
const main = (await session.send("SystemInfo.getProcessInfo")).processInfo.find((item) => item.type === "browser");
|
|
2993
|
+
if (main !== void 0 && main.id > 0) return main.id;
|
|
2994
|
+
} finally {
|
|
2995
|
+
await session.detach().catch(() => void 0);
|
|
2996
|
+
}
|
|
2997
|
+
} catch {}
|
|
2998
|
+
if (process.platform === "win32") return void 0;
|
|
2999
|
+
try {
|
|
3000
|
+
const target = await readlink(join(profileDir, "SingletonLock"));
|
|
3001
|
+
const pid = Number(target.slice(target.lastIndexOf("-") + 1));
|
|
3002
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
3003
|
+
} catch {
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Ask the browser to close; kill `pid` when that fails or takes longer than
|
|
3009
|
+
* `waitMs`, so a stuck browser does not keep the profile locked.
|
|
3010
|
+
*/
|
|
3011
|
+
async function closeOrKill(close, pid, waitMs = 1e3, kill = (target) => {
|
|
3012
|
+
process.kill(target, "SIGKILL");
|
|
3013
|
+
}) {
|
|
3014
|
+
let timer;
|
|
3015
|
+
const outcome = await Promise.race([close().then(() => "closed", () => "failed"), new Promise((resolve) => {
|
|
3016
|
+
timer = setTimeout(() => resolve("timeout"), waitMs);
|
|
3017
|
+
})]);
|
|
3018
|
+
clearTimeout(timer);
|
|
3019
|
+
if (outcome === "closed") return "closed";
|
|
3020
|
+
if (pid === void 0) return "stuck";
|
|
3021
|
+
try {
|
|
3022
|
+
kill(pid);
|
|
3023
|
+
} catch {}
|
|
3024
|
+
return "killed";
|
|
3025
|
+
}
|
|
2982
3026
|
//#endregion
|
|
2983
3027
|
//#region lib/types/service/control.js
|
|
2984
3028
|
/**
|
|
@@ -4121,7 +4165,7 @@ const REPLIED = "继续";
|
|
|
4121
4165
|
//#endregion
|
|
4122
4166
|
//#region lib/types/service/extension.js
|
|
4123
4167
|
const PACKAGE = "@mind-harness/mh-browser";
|
|
4124
|
-
const WORKER_MS =
|
|
4168
|
+
const WORKER_MS = 15e3;
|
|
4125
4169
|
/**
|
|
4126
4170
|
* Directory of the unpacked extension shipped with this package.
|
|
4127
4171
|
* Walks up from this module: sources live in `src/`, the bundle in `lib/`.
|
|
@@ -4156,6 +4200,10 @@ var Extension = class Extension {
|
|
|
4156
4200
|
const worker = await findWorker(context, loaded.id);
|
|
4157
4201
|
return new Extension(context, loaded.id, worker);
|
|
4158
4202
|
}
|
|
4203
|
+
/** Address of a page shipped in the extension directory. */
|
|
4204
|
+
pageUrl(name) {
|
|
4205
|
+
return `chrome-extension://${this.id}/${name}`;
|
|
4206
|
+
}
|
|
4159
4207
|
/** `chrome.tabs.create` in the named window. Returns the extension's tab id. */
|
|
4160
4208
|
async createTab(windowId, url) {
|
|
4161
4209
|
return (await this.worker()).evaluate(async (arg) => {
|
|
@@ -4231,13 +4279,21 @@ var Extension = class Extension {
|
|
|
4231
4279
|
return worker;
|
|
4232
4280
|
}
|
|
4233
4281
|
};
|
|
4234
|
-
|
|
4235
|
-
|
|
4282
|
+
/** The extension's service worker. Looks once more after the wait times out. */
|
|
4283
|
+
async function findWorker(context, id, timeoutMs = WORKER_MS) {
|
|
4284
|
+
const find = () => context.serviceWorkers().find((worker) => worker.url().includes(id));
|
|
4285
|
+
const open = find();
|
|
4236
4286
|
if (open !== void 0) return open;
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4287
|
+
try {
|
|
4288
|
+
return await context.waitForEvent("serviceworker", {
|
|
4289
|
+
predicate: (worker) => worker.url().includes(id),
|
|
4290
|
+
timeout: timeoutMs
|
|
4291
|
+
});
|
|
4292
|
+
} catch (error) {
|
|
4293
|
+
const late = find();
|
|
4294
|
+
if (late !== void 0) return late;
|
|
4295
|
+
throw new Error(`the browser extension did not start within ${String(timeoutMs / 1e3)} s`, { cause: error });
|
|
4296
|
+
}
|
|
4241
4297
|
}
|
|
4242
4298
|
//#endregion
|
|
4243
4299
|
//#region lib/types/service/windows.js
|
|
@@ -4319,6 +4375,8 @@ var Windows = class {
|
|
|
4319
4375
|
this.browserSession = await browser.newBrowserCDPSession();
|
|
4320
4376
|
if (options.headed) this.extension = await Extension.load(context, this.browserSession);
|
|
4321
4377
|
const launched = context.pages().filter((page) => page.url() === "about:blank");
|
|
4378
|
+
const extension = this.extension;
|
|
4379
|
+
if (extension !== void 0) for (const page of launched) await page.goto(extension.pageUrl("welcome.html")).catch(() => void 0);
|
|
4322
4380
|
context.on("page", (page) => {
|
|
4323
4381
|
for (const blank of launched.splice(0)) blank.close().catch(() => void 0);
|
|
4324
4382
|
this.ingest(page);
|
|
@@ -5066,6 +5124,7 @@ let MhBrowserService = (() => {
|
|
|
5066
5124
|
windows = (__runInitializers(this, _instanceExtraInitializers), new Windows());
|
|
5067
5125
|
launchOptions;
|
|
5068
5126
|
context;
|
|
5127
|
+
pids = /* @__PURE__ */ new WeakMap();
|
|
5069
5128
|
ready = Promise.resolve();
|
|
5070
5129
|
closing = false;
|
|
5071
5130
|
/** The browser is not running. The next call that needs a page starts it. */
|
|
@@ -5123,7 +5182,8 @@ let MhBrowserService = (() => {
|
|
|
5123
5182
|
this.agentViewOrigins.add(parsed.origin);
|
|
5124
5183
|
} catch {}
|
|
5125
5184
|
}
|
|
5126
|
-
async open(owner, items, signal) {
|
|
5185
|
+
async open(owner, items, signal, options) {
|
|
5186
|
+
const reveal = options?.reveal === true;
|
|
5127
5187
|
return this.exclusive(owner, async () => {
|
|
5128
5188
|
await this.up(owner === PERSON_OWNER);
|
|
5129
5189
|
const views = [];
|
|
@@ -5135,7 +5195,7 @@ let MhBrowserService = (() => {
|
|
|
5135
5195
|
});
|
|
5136
5196
|
continue;
|
|
5137
5197
|
}
|
|
5138
|
-
views.push(await this.openAddress(owner, item, signal));
|
|
5198
|
+
views.push(await this.openAddress(owner, item, signal, reveal));
|
|
5139
5199
|
}
|
|
5140
5200
|
await this.enforceCap(owner);
|
|
5141
5201
|
return views;
|
|
@@ -5298,7 +5358,9 @@ let MhBrowserService = (() => {
|
|
|
5298
5358
|
await (this.context?.browser())?.close();
|
|
5299
5359
|
},
|
|
5300
5360
|
page: (owner, tab) => this.windows.get(owner, tab)?.page,
|
|
5301
|
-
running: () => this.context !== void 0
|
|
5361
|
+
running: () => this.context !== void 0,
|
|
5362
|
+
pages: () => this.context?.pages() ?? [],
|
|
5363
|
+
pid: () => this.context === void 0 ? void 0 : this.pids.get(this.context)
|
|
5302
5364
|
};
|
|
5303
5365
|
}
|
|
5304
5366
|
async act(owner, tab, op, signal) {
|
|
@@ -5503,7 +5565,7 @@ let MhBrowserService = (() => {
|
|
|
5503
5565
|
* A readable Agent View found on a tab this call opened is returned without
|
|
5504
5566
|
* the tab, and that tab is not listed as closed.
|
|
5505
5567
|
*/
|
|
5506
|
-
async openAddress(owner, item, signal) {
|
|
5568
|
+
async openAddress(owner, item, signal, reveal = false) {
|
|
5507
5569
|
if (!openableAddress(item.url)) return {
|
|
5508
5570
|
url: item.url,
|
|
5509
5571
|
reason: OPEN_ADDRESS_REASON
|
|
@@ -5517,7 +5579,7 @@ let MhBrowserService = (() => {
|
|
|
5517
5579
|
};
|
|
5518
5580
|
if (via !== void 0) return via;
|
|
5519
5581
|
}
|
|
5520
|
-
const opened = await this.openOne(owner, item.url, item.waitFor, signal, item.snapshot === true);
|
|
5582
|
+
const opened = await this.openOne(owner, item.url, item.waitFor, signal, item.snapshot === true, reveal);
|
|
5521
5583
|
this.keepAgentView(opened);
|
|
5522
5584
|
if (!wantsTab && opened.fresh && this.closable(owner, opened.live) && "representation" in opened.view && opened.view.representation.kind === "agent-view") {
|
|
5523
5585
|
await this.windows.closeTab(opened.live);
|
|
@@ -5686,12 +5748,13 @@ let MhBrowserService = (() => {
|
|
|
5686
5748
|
return body;
|
|
5687
5749
|
}
|
|
5688
5750
|
}
|
|
5689
|
-
async openOne(owner, url, waitFor, signal, snapshot = false) {
|
|
5751
|
+
async openOne(owner, url, waitFor, signal, snapshot = false, reveal = false) {
|
|
5690
5752
|
assertWaitFor(waitFor);
|
|
5691
5753
|
const reused = this.idleTabOn(owner, url);
|
|
5692
5754
|
const live = reused ?? await this.windows.openBlank(owner);
|
|
5693
5755
|
const fresh = reused === void 0;
|
|
5694
5756
|
const linked = this.control.link(owner, signal, live.id);
|
|
5757
|
+
if (reveal && !linked.signal.aborted) await live.page.bringToFront().catch(() => void 0);
|
|
5695
5758
|
const nextDialog = this.windows.nextDialog(live);
|
|
5696
5759
|
try {
|
|
5697
5760
|
let seenDialog;
|
|
@@ -6365,6 +6428,8 @@ let MhBrowserService = (() => {
|
|
|
6365
6428
|
}
|
|
6366
6429
|
async start() {
|
|
6367
6430
|
const context = await launchBrowser(this.launchOptions);
|
|
6431
|
+
const pid = await browserPid(context, this.launchOptions.profileDir);
|
|
6432
|
+
if (pid !== void 0) this.pids.set(context, pid);
|
|
6368
6433
|
if (this.closing) {
|
|
6369
6434
|
await this.closeContext(context);
|
|
6370
6435
|
return;
|
|
@@ -6423,9 +6488,8 @@ let MhBrowserService = (() => {
|
|
|
6423
6488
|
}
|
|
6424
6489
|
/** Close a context the service is done with, and kill it if close stalls. */
|
|
6425
6490
|
async closeContext(context) {
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
(browser?.process?.())?.kill("SIGKILL");
|
|
6491
|
+
await closeOrKill(() => context.close(), this.pids.get(context));
|
|
6492
|
+
this.pids.delete(context);
|
|
6429
6493
|
}
|
|
6430
6494
|
refuseIfPerson(owner, tab) {
|
|
6431
6495
|
if (this.control.ofTab(owner, tab) !== "person") return;
|
package/lib/typert.host.js
CHANGED
|
@@ -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":
|
|
47
|
+
sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":624,"column":9},
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
}, z)
|
|
@@ -75,7 +75,7 @@ export const TYPERT = {
|
|
|
75
75
|
{
|
|
76
76
|
"kind": "method",
|
|
77
77
|
"name": "open",
|
|
78
|
-
"signature": "async open( owner: string, items: { url: string; waitFor?: WaitFor; snapshot?: boolean }[], signal?: AbortSignal, ): Promise<(PageView | PageError)[]>"
|
|
78
|
+
"signature": "async open( owner: string, items: { url: string; waitFor?: WaitFor; snapshot?: boolean }[], signal?: AbortSignal, options?: { reveal?: boolean }, ): Promise<(PageView | PageError)[]>"
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
"kind": "method",
|
|
@@ -359,7 +359,7 @@ export const TYPERT = {
|
|
|
359
359
|
},
|
|
360
360
|
{
|
|
361
361
|
"name": "TestHooks",
|
|
362
|
-
"declaration": "export interface TestHooks {\n crash(): Promise<void>;\n page(owner: string, tab: string): unknown;\n running(): boolean;\n}"
|
|
362
|
+
"declaration": "export interface TestHooks {\n crash(): Promise<void>;\n page(owner: string, tab: string): unknown;\n running(): boolean;\n pages(): unknown[];\n pid(): number | undefined;\n}"
|
|
363
363
|
},
|
|
364
364
|
{
|
|
365
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":
|
|
47
|
+
sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":624,"column":9},
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
}, z)
|
|
@@ -20,4 +20,15 @@ export declare function chromiumSandboxEnabled(browser: BrowserName, platform?:
|
|
|
20
20
|
* A missing browser is reported and not downloaded.
|
|
21
21
|
*/
|
|
22
22
|
export declare function launchBrowser(options: LaunchOptions): Promise<BrowserContext>;
|
|
23
|
+
/**
|
|
24
|
+
* Process id of the browser's main process. Playwright gives none for a
|
|
25
|
+
* persistent context. Asks the browser first; on POSIX the profile's
|
|
26
|
+
* `SingletonLock` link names it as `<host>-<pid>`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function browserPid(context: BrowserContext, profileDir: string): Promise<number | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* Ask the browser to close; kill `pid` when that fails or takes longer than
|
|
31
|
+
* `waitMs`, so a stuck browser does not keep the profile locked.
|
|
32
|
+
*/
|
|
33
|
+
export declare function closeOrKill(close: () => Promise<void>, pid: number | undefined, waitMs?: number, kill?: (target: number) => void): Promise<'closed' | 'killed' | 'stuck'>;
|
|
23
34
|
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BrowserContext, CDPSession } from 'playwright';
|
|
1
|
+
import type { BrowserContext, CDPSession, Worker } from 'playwright';
|
|
2
2
|
/**
|
|
3
3
|
* Directory of the unpacked extension shipped with this package.
|
|
4
4
|
* Walks up from this module: sources live in `src/`, the bundle in `lib/`.
|
|
@@ -14,6 +14,8 @@ export declare class Extension {
|
|
|
14
14
|
private workerPage;
|
|
15
15
|
private constructor();
|
|
16
16
|
static load(context: BrowserContext, browserSession: CDPSession): Promise<Extension>;
|
|
17
|
+
/** Address of a page shipped in the extension directory. */
|
|
18
|
+
pageUrl(name: string): string;
|
|
17
19
|
/** `chrome.tabs.create` in the named window. Returns the extension's tab id. */
|
|
18
20
|
createTab(windowId: number, url: string): Promise<number>;
|
|
19
21
|
tabIdsIn(windowId: number): Promise<number[]>;
|
|
@@ -27,4 +29,6 @@ export declare class Extension {
|
|
|
27
29
|
ungroup(tabId: number): Promise<void>;
|
|
28
30
|
private worker;
|
|
29
31
|
}
|
|
32
|
+
/** The extension's service worker. Looks once more after the wait times out. */
|
|
33
|
+
export declare function findWorker(context: BrowserContext, id: string, timeoutMs?: number): Promise<Worker>;
|
|
30
34
|
//# sourceMappingURL=extension.d.ts.map
|
|
@@ -34,6 +34,7 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
34
34
|
private readonly windows;
|
|
35
35
|
private readonly launchOptions;
|
|
36
36
|
private context;
|
|
37
|
+
private readonly pids;
|
|
37
38
|
private ready;
|
|
38
39
|
private closing;
|
|
39
40
|
/** The browser is not running. The next call that needs a page starts it. */
|
|
@@ -58,7 +59,9 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
58
59
|
url: string;
|
|
59
60
|
waitFor?: WaitFor;
|
|
60
61
|
snapshot?: boolean;
|
|
61
|
-
}[], signal?: AbortSignal
|
|
62
|
+
}[], signal?: AbortSignal, options?: {
|
|
63
|
+
reveal?: boolean;
|
|
64
|
+
}): Promise<(PageView | PageError)[]>;
|
|
62
65
|
view(owner: string, tab: string, signal?: AbortSignal): Promise<PageView | PageError>;
|
|
63
66
|
/** Pages whose current representation is already in the model's presence. */
|
|
64
67
|
rememberPresence(owner: string, urls: string[]): void;
|
|
@@ -5,5 +5,9 @@ export interface TestHooks {
|
|
|
5
5
|
page(owner: string, tab: string): unknown;
|
|
6
6
|
/** A browser process is up. */
|
|
7
7
|
running(): boolean;
|
|
8
|
+
/** Every page of the running browser, launch page included. */
|
|
9
|
+
pages(): unknown[];
|
|
10
|
+
/** The main browser process id recorded at start. */
|
|
11
|
+
pid(): number | undefined;
|
|
8
12
|
}
|
|
9
13
|
//# 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.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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": [
|