@mind-harness/mh-browser 0.1.0 → 0.1.2
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/lib/index.js +38 -8
- package/lib/typert.host.js +5 -1
- package/lib/types/config.d.ts +8 -0
- package/lib/types/tools/delivery.d.ts +5 -6
- package/lib/types/types.d.ts +6 -3
- package/package.json +19 -19
package/extension/manifest.json
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
1
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
2
4
|
import { isDeepStrictEqual } from "node:util";
|
|
3
5
|
import { HarnessError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
6
|
import { z as z$1 } from "zod";
|
|
5
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
-
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
7
8
|
import { lstat, mkdir, open, readFile, realpath, unlink, writeFile } from "node:fs/promises";
|
|
8
9
|
import { parse } from "yaml";
|
|
9
10
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
10
|
-
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
11
11
|
import { chromium } from "playwright";
|
|
12
12
|
import { constants, readFileSync } from "node:fs";
|
|
13
13
|
import { assertSupportedJsonSchema, defineTool, validateJsonSchemaValue } from "@deepseek-ai/dsh-tools";
|
|
@@ -21,9 +21,17 @@ const browserConfig = z.object({
|
|
|
21
21
|
"msedge",
|
|
22
22
|
"chromium"
|
|
23
23
|
]).default("chrome").description("Which installed browser to drive. chromium is the one Playwright downloads."),
|
|
24
|
-
profileDir: z.string().description("Browser profile directory, kept between runs. Defaults to mh/browser under the dsh home."),
|
|
24
|
+
profileDir: z.string().description("Browser profile directory, kept between runs. Defaults to mh/profiles/<dsh profile>/browser under the dsh home."),
|
|
25
25
|
headless: z.boolean().default(false).description("Run the browser without a window.")
|
|
26
26
|
});
|
|
27
|
+
/**
|
|
28
|
+
* Browser profile directory when the configuration names none. Each dsh profile gets its own:
|
|
29
|
+
* a browser profile directory admits one running browser, so two dsh profiles sharing one
|
|
30
|
+
* cannot both start.
|
|
31
|
+
*/
|
|
32
|
+
function defaultProfileDir(profile, home = resolveDshHome()) {
|
|
33
|
+
return profile === void 0 ? join(home, "mh", "browser") : join(home, "mh", "profiles", profile.name, "browser");
|
|
34
|
+
}
|
|
27
35
|
//#endregion
|
|
28
36
|
//#region lib/types/presence/record.js
|
|
29
37
|
/** Structured presence record carried on a message source. The model sees the rendered text, not this. */
|
|
@@ -690,7 +698,13 @@ function latestSeen(state, url) {
|
|
|
690
698
|
const latest = matches.filter((item) => item.digest !== void 0).at(-1);
|
|
691
699
|
if (latest === void 0) return { digest: "" };
|
|
692
700
|
if (!latest.inContext) return "not-in-context";
|
|
693
|
-
return
|
|
701
|
+
return seenOf(latest);
|
|
702
|
+
}
|
|
703
|
+
function seenOf(item) {
|
|
704
|
+
return {
|
|
705
|
+
digest: item.digest ?? "",
|
|
706
|
+
...item.version === void 0 ? {} : { version: item.version }
|
|
707
|
+
};
|
|
694
708
|
}
|
|
695
709
|
/**
|
|
696
710
|
* The document `page` names: the one shown at that address, else the one whose
|
|
@@ -726,7 +740,7 @@ function resolvePage(state, page) {
|
|
|
726
740
|
if (chosen === void 0) return void 0;
|
|
727
741
|
return {
|
|
728
742
|
url: chosen.url,
|
|
729
|
-
seen:
|
|
743
|
+
seen: seenOf(chosen)
|
|
730
744
|
};
|
|
731
745
|
}
|
|
732
746
|
function withShown(state, shown) {
|
|
@@ -781,6 +795,7 @@ function fromTool(event) {
|
|
|
781
795
|
const url = readString(row.url);
|
|
782
796
|
if (url === void 0) continue;
|
|
783
797
|
const digest = readString(row.digest);
|
|
798
|
+
const version = readString(row.version);
|
|
784
799
|
const id = readString(row.id);
|
|
785
800
|
deliveries.push({
|
|
786
801
|
url,
|
|
@@ -789,6 +804,7 @@ function fromTool(event) {
|
|
|
789
804
|
inContext: true,
|
|
790
805
|
...callId === void 0 ? {} : { callId },
|
|
791
806
|
...digest === void 0 ? {} : { digest },
|
|
807
|
+
...version === void 0 ? {} : { version },
|
|
792
808
|
...id === void 0 ? {} : { id }
|
|
793
809
|
});
|
|
794
810
|
}
|
|
@@ -810,6 +826,7 @@ function fromPresence(event) {
|
|
|
810
826
|
const url = readString(row.url);
|
|
811
827
|
if (url === void 0) continue;
|
|
812
828
|
const digest = readString(row.digest);
|
|
829
|
+
const version = readString(row.version);
|
|
813
830
|
const id = readString(row.id);
|
|
814
831
|
deliveries.push({
|
|
815
832
|
url,
|
|
@@ -817,6 +834,7 @@ function fromPresence(event) {
|
|
|
817
834
|
kind: "presence",
|
|
818
835
|
inContext: true,
|
|
819
836
|
...digest === void 0 ? {} : { digest },
|
|
837
|
+
...version === void 0 ? {} : { version },
|
|
820
838
|
...id === void 0 ? {} : { id }
|
|
821
839
|
});
|
|
822
840
|
}
|
|
@@ -841,6 +859,7 @@ function attachedPage(seq, data, text, callId) {
|
|
|
841
859
|
const header = text.slice(start, end);
|
|
842
860
|
const url = attrOf(header, "url");
|
|
843
861
|
const digest = attrOf(header, "digest");
|
|
862
|
+
const version = attrOf(header, "version");
|
|
844
863
|
if (url === void 0 || digest === void 0) return void 0;
|
|
845
864
|
const close = text.indexOf("</page>", end);
|
|
846
865
|
const id = agentViewId(text.slice(end + 1, close < 0 ? void 0 : close), url);
|
|
@@ -850,6 +869,7 @@ function attachedPage(seq, data, text, callId) {
|
|
|
850
869
|
seq,
|
|
851
870
|
kind: "tool",
|
|
852
871
|
digest,
|
|
872
|
+
...version === void 0 ? {} : { version },
|
|
853
873
|
inContext: true,
|
|
854
874
|
...callId === void 0 ? {} : { callId },
|
|
855
875
|
...id === void 0 ? {} : { id }
|
|
@@ -2425,9 +2445,8 @@ async function runInvoke(host, pageUrl, call) {
|
|
|
2425
2445
|
const view = await host.idleView(pageUrl) ?? await host.open(pageUrl);
|
|
2426
2446
|
if (!("representation" in view)) throw new MhBrowserError("NOT_DECLARED", view.reason);
|
|
2427
2447
|
const action = declaredAction(view, call.name);
|
|
2428
|
-
const digest = representationDigest(view.representation.text);
|
|
2429
2448
|
if (call.seen === "not-in-context") throw new MhBrowserError("STALE_VIEW", "not-in-context", view);
|
|
2430
|
-
if (call.seen
|
|
2449
|
+
if (viewChanged(call.seen, view)) throw new MhBrowserError("STALE_VIEW", "changed", view);
|
|
2431
2450
|
const doc = readableDoc(view);
|
|
2432
2451
|
const origin = view.url === "" ? pageUrl : view.url;
|
|
2433
2452
|
const plan = planAction(doc, action, origin, call);
|
|
@@ -2469,6 +2488,17 @@ async function runInvoke(host, pageUrl, call) {
|
|
|
2469
2488
|
page: reloaded
|
|
2470
2489
|
};
|
|
2471
2490
|
}
|
|
2491
|
+
/**
|
|
2492
|
+
* A page that declares a `version` changed only when that version did: bytes
|
|
2493
|
+
* that are not the resource, such as links to the rest of the site, move
|
|
2494
|
+
* without making the model's choice stale. The site's own version precondition
|
|
2495
|
+
* in `request` rejects a choice made on an old version.
|
|
2496
|
+
*/
|
|
2497
|
+
function viewChanged(seen, view) {
|
|
2498
|
+
const version = view.representation.kind === "agent-view" ? view.representation.version : void 0;
|
|
2499
|
+
if (seen.version !== void 0 || version !== void 0) return seen.version !== version;
|
|
2500
|
+
return seen.digest !== representationDigest(view.representation.text);
|
|
2501
|
+
}
|
|
2472
2502
|
function declaredAction(view, name) {
|
|
2473
2503
|
if (view.representation.kind !== "agent-view") throw new MhBrowserError("NOT_DECLARED", (view.representation.kind === "snapshot" ? view.representation.agentViewError : void 0) ?? "this page has no readable Agent View", view);
|
|
2474
2504
|
const action = readableDoc(view).actions[name];
|
|
@@ -3946,7 +3976,7 @@ let MhBrowserService = (() => {
|
|
|
3946
3976
|
this.launchOptions = {
|
|
3947
3977
|
browser: config.browser,
|
|
3948
3978
|
headless: config.headless,
|
|
3949
|
-
profileDir: config.profileDir ??
|
|
3979
|
+
profileDir: config.profileDir ?? defaultProfileDir(ctx.get("profileContext"))
|
|
3950
3980
|
};
|
|
3951
3981
|
this.windows.setPersonInput((owner, tab) => {
|
|
3952
3982
|
const live = tab === void 0 ? void 0 : this.windows.get(owner, tab);
|
package/lib/typert.host.js
CHANGED
|
@@ -285,7 +285,7 @@ export const TYPERT = {
|
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
"name": "InvokeCall",
|
|
288
|
-
"declaration": "export interface InvokeCall {\n name: string;\n item?: string;\n input?: Record<string, unknown>;\n seen:
|
|
288
|
+
"declaration": "export interface InvokeCall {\n name: string;\n item?: string;\n input?: Record<string, unknown>;\n seen: SeenView | 'not-in-context';\n}"
|
|
289
289
|
},
|
|
290
290
|
{
|
|
291
291
|
"name": "InvokeSent",
|
|
@@ -323,6 +323,10 @@ export const TYPERT = {
|
|
|
323
323
|
"name": "PresenceSource",
|
|
324
324
|
"declaration": "export type PresenceSource = (typeof PRESENCE_SOURCES)[number];"
|
|
325
325
|
},
|
|
326
|
+
{
|
|
327
|
+
"name": "SeenView",
|
|
328
|
+
"declaration": "export interface SeenView {\n digest: string;\n version?: string;\n}"
|
|
329
|
+
},
|
|
326
330
|
{
|
|
327
331
|
"name": "TabId",
|
|
328
332
|
"declaration": "export type TabId = string;"
|
package/lib/types/config.d.ts
CHANGED
|
@@ -9,4 +9,12 @@ export interface BrowserConfig {
|
|
|
9
9
|
headless: boolean;
|
|
10
10
|
}
|
|
11
11
|
export declare const browserConfig: z<BrowserConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Browser profile directory when the configuration names none. Each dsh profile gets its own:
|
|
14
|
+
* a browser profile directory admits one running browser, so two dsh profiles sharing one
|
|
15
|
+
* cannot both start.
|
|
16
|
+
*/
|
|
17
|
+
export declare function defaultProfileDir(profile: {
|
|
18
|
+
name: string;
|
|
19
|
+
} | undefined, home?: string): string;
|
|
12
20
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { SeenView } from '../types.ts';
|
|
1
2
|
/** A page the session has shown the model, folded from the session log. */
|
|
2
3
|
export interface Delivery {
|
|
3
4
|
url: string;
|
|
4
5
|
seq: number;
|
|
5
6
|
digest?: string;
|
|
7
|
+
/** The `version` its Agent View declares, when it declares one. */
|
|
8
|
+
version?: string;
|
|
6
9
|
/** The `id` its Agent View declares. A log written before it was recorded has none. */
|
|
7
10
|
id?: string;
|
|
8
11
|
callId?: string;
|
|
@@ -39,14 +42,10 @@ export declare function foldDeliveries(events: readonly LogEvent[]): DeliverySta
|
|
|
39
42
|
* The Agent View of this address the model saw last, and whether it is still
|
|
40
43
|
* in context. A page shown only as a snapshot has no Agent View digest.
|
|
41
44
|
*/
|
|
42
|
-
export declare function latestSeen(state: DeliveryState, url: string):
|
|
43
|
-
digest: string;
|
|
44
|
-
} | 'not-in-context' | undefined;
|
|
45
|
+
export declare function latestSeen(state: DeliveryState, url: string): SeenView | 'not-in-context' | undefined;
|
|
45
46
|
export type ResolvedPage = {
|
|
46
47
|
url: string;
|
|
47
|
-
seen:
|
|
48
|
-
digest: string;
|
|
49
|
-
} | 'not-in-context';
|
|
48
|
+
seen: SeenView | 'not-in-context';
|
|
50
49
|
} | {
|
|
51
50
|
ambiguous: string[];
|
|
52
51
|
};
|
package/lib/types/types.d.ts
CHANGED
|
@@ -157,9 +157,12 @@ export interface InvokeCall {
|
|
|
157
157
|
name: string;
|
|
158
158
|
item?: string;
|
|
159
159
|
input?: Record<string, unknown>;
|
|
160
|
-
seen:
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
seen: SeenView | 'not-in-context';
|
|
161
|
+
}
|
|
162
|
+
/** The Agent View the model saw last: its declared `version` when it had one, and its digest. */
|
|
163
|
+
export interface SeenView {
|
|
164
|
+
digest: string;
|
|
165
|
+
version?: string;
|
|
163
166
|
}
|
|
164
167
|
export interface InvokeSent {
|
|
165
168
|
schemaRejected: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mind-harness/mh-browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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": [
|
|
@@ -43,27 +43,27 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"playwright": "1.64.1",
|
|
46
|
+
"playwright": "^1.64.1",
|
|
47
47
|
"yaml": "^2.9.1",
|
|
48
|
-
"zod": "4.
|
|
48
|
+
"zod": "^4.6.6"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@deepseek-ai/cordis": "4.0.5",
|
|
52
|
-
"@deepseek-ai/dsh-agent": "0.2.1-rc.3",
|
|
53
|
-
"@deepseek-ai/dsh-agent-loop": "0.2.1-rc.3",
|
|
54
|
-
"@deepseek-ai/dsh-agent-loop-testkit": "0.2.1-rc.3",
|
|
55
|
-
"@deepseek-ai/dsh-attachment": "0.2.1-rc.3",
|
|
56
|
-
"@deepseek-ai/dsh-browser-use": "0.2.1-rc.3",
|
|
57
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.3",
|
|
58
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.3",
|
|
59
|
-
"@deepseek-ai/dsh-llm-mock-server": "0.2.1-rc.3",
|
|
60
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.3",
|
|
61
|
-
"@deepseek-ai/dsh-session": "0.2.1-rc.3",
|
|
62
|
-
"@deepseek-ai/dsh-session-projection": "0.2.1-rc.3",
|
|
63
|
-
"@deepseek-ai/dsh-tools": "0.2.1-rc.3",
|
|
64
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.3",
|
|
65
|
-
"@deepseek-ai/dsh-user-questions": "0.2.1-rc.3",
|
|
66
|
-
"@deepseek-ai/schemastery": "3.18.5"
|
|
51
|
+
"@deepseek-ai/cordis": "~4.0.5",
|
|
52
|
+
"@deepseek-ai/dsh-agent": "^0.2.1-rc.3",
|
|
53
|
+
"@deepseek-ai/dsh-agent-loop": "^0.2.1-rc.3",
|
|
54
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "^0.2.1-rc.3",
|
|
55
|
+
"@deepseek-ai/dsh-attachment": "^0.2.1-rc.3",
|
|
56
|
+
"@deepseek-ai/dsh-browser-use": "^0.2.1-rc.3",
|
|
57
|
+
"@deepseek-ai/dsh-home-paths": "^0.2.1-rc.3",
|
|
58
|
+
"@deepseek-ai/dsh-llm": "^0.2.1-rc.3",
|
|
59
|
+
"@deepseek-ai/dsh-llm-mock-server": "^0.2.1-rc.3",
|
|
60
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.2.1-rc.3",
|
|
61
|
+
"@deepseek-ai/dsh-session": "^0.2.1-rc.3",
|
|
62
|
+
"@deepseek-ai/dsh-session-projection": "^0.2.1-rc.3",
|
|
63
|
+
"@deepseek-ai/dsh-tools": "^0.2.1-rc.3",
|
|
64
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.2.1-rc.3",
|
|
65
|
+
"@deepseek-ai/dsh-user-questions": "^0.2.1-rc.3",
|
|
66
|
+
"@deepseek-ai/schemastery": "~3.18.5"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@deepseek-ai/cordis": "~4.0.5",
|