@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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Mind Harness",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "permissions": ["tabGroups"],
6
6
  "background": { "service_worker": "sw.js" }
7
7
  }
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 { digest: latest.digest ?? "" };
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: { digest: chosen.digest ?? "" }
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.digest !== digest) throw new MhBrowserError("STALE_VIEW", "changed", view);
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 ?? join(resolveDshHome(), "mh", "browser")
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);
@@ -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: { digest: string; } | 'not-in-context';\n}"
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;"
@@ -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
  };
@@ -157,9 +157,12 @@ export interface InvokeCall {
157
157
  name: string;
158
158
  item?: string;
159
159
  input?: Record<string, unknown>;
160
- seen: {
161
- digest: string;
162
- } | 'not-in-context';
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.0",
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.4.3"
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",