@mind-harness/mh-browser 0.2.6 → 0.2.7

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/lib/client.cjs CHANGED
@@ -4,302 +4,92 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let zod = require("zod");
8
7
  let react = require("react");
9
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.2.2-rc.1_@deepseek-ai+cordis@4.1.0/node_modules/@deepseek-ai/dsh-typert-protocol/lib/types/remote-info.js
10
- /** Lazy assembly of generated Remote definitions using caller-supplied Zod; no runtime imports. */
8
+ //#region src/client/call.ts
9
+ const PACKAGE = "mh-browser";
11
10
  /**
12
- * Retain one generated definition per endpoint without constructing parsers or projections.
13
- * The class instance builds its shared descriptor/method index on first access;
14
- * input schemas and JSON Schema projections are cached only when requested.
15
- * @param spec - compiler-produced method facts and Zod factories.
16
- * @param zod - the generated artifact's Zod runtime for named-input parsers.
17
- * @returns a lazy, mountable info instance with no registration or transport effects.
11
+ * `document.baseURI` keeps a mounted page on its prefix. dsh's file upload
12
+ * resolves the same way.
18
13
  */
19
- function createTypertRemoteInfo(spec, zod) {
20
- return new RemoteInfo(spec, zod);
21
- }
22
- var RemoteInfo = class {
23
- spec;
24
- zod;
25
- index;
26
- constructor(spec, zod) {
27
- this.spec = spec;
28
- this.zod = zod;
29
- }
30
- get package() {
31
- return this.spec.package;
32
- }
33
- get descriptors() {
34
- return this.resolve().descriptors;
35
- }
36
- get methods() {
37
- return this.resolve().methods;
14
+ async function call(segment, init, signal) {
15
+ const url = new URL(`${PACKAGE}/api/${segment}`, document.baseURI);
16
+ if (init.query !== void 0) for (const [key, value] of Object.entries(init.query)) url.searchParams.set(key, value);
17
+ const request = {
18
+ method: init.method,
19
+ credentials: "same-origin",
20
+ cache: "no-store"
21
+ };
22
+ if (signal !== void 0) request.signal = signal;
23
+ if (init.method === "POST") {
24
+ request.headers = { "content-type": "application/json" };
25
+ request.body = JSON.stringify(init.body ?? {});
38
26
  }
39
- resolve() {
40
- if (this.index !== void 0) return this.index;
41
- const zod = this.zod;
42
- const descriptors = [];
43
- const methods = Object.create(null);
44
- for (const [endpoint, definition] of Object.entries(this.spec.methods)) {
45
- const separator = endpoint.indexOf("/");
46
- const namespace = endpoint.slice(0, separator);
47
- const method = endpoint.slice(separator + 1);
48
- const parameters = definition.parameters.map((parameter) => ({
49
- ...parameter,
50
- wire: parameter.wire ?? parameter.name
51
- }));
52
- const descriptor = {
53
- ...definition,
54
- id: `${this.spec.package}#${endpoint}`,
55
- namespace,
56
- method,
57
- parameters,
58
- result: definition.result.description === void 0 ? definition.result : new ResultValue(definition.result)
59
- };
60
- if (definition.invocation.kind === "direct") {
61
- const inputs = [];
62
- methods[endpoint] = Object.assign(descriptor, {
63
- input(options) {
64
- const strict = options?.strict === true;
65
- return inputs[strict ? 1 : 0] ??= new NamedInput(parameters, strict, zod);
66
- },
67
- invoke(remote, input, signal) {
68
- const named = input;
69
- const values = parameters.map((parameter) => named[parameter.wire]);
70
- if (definition.cancellation !== void 0) values.push(signal);
71
- const receiver = Reflect.get(remote, namespace);
72
- return Reflect.get(receiver, method).apply(receiver, values);
73
- }
74
- });
75
- }
76
- descriptors.push(descriptor);
77
- }
78
- return this.index = {
79
- descriptors,
80
- methods
81
- };
27
+ try {
28
+ const payload = await (await fetch(url, request)).json();
29
+ if (isCallResult(payload)) return payload;
30
+ return fail("unreachable", "response was not understood");
31
+ } catch (error) {
32
+ if (signal?.aborted === true) return fail("cancelled", "cancelled");
33
+ return fail("unreachable", error instanceof Error ? error.message : "request failed");
82
34
  }
83
- };
84
- function optional(parameter) {
85
- return parameter.source === "json" && parameter.acceptsUndefined === true;
86
35
  }
87
- var NamedInput = class {
88
- parameters;
89
- strict;
90
- zod;
91
- schema;
92
- projection;
93
- constructor(parameters, strict, zod) {
94
- this.parameters = parameters;
95
- this.strict = strict;
96
- this.zod = zod;
97
- }
98
- codec(parameter) {
99
- return this.strict ? parameter.strictCodec : parameter.codec;
100
- }
101
- create = () => this.schema ??= this.zod.strictObject(Object.fromEntries(this.parameters.map((parameter) => {
102
- const value = this.codec(parameter).create();
103
- return [parameter.wire, optional(parameter) ? value.optional() : value.nonoptional()];
104
- })));
105
- get jsonSchema() {
106
- return this.project().jsonSchema;
107
- }
108
- get jsonSchemaError() {
109
- return this.project().jsonSchemaError;
110
- }
111
- project() {
112
- if (this.projection !== void 0) return this.projection;
113
- const unavailable = this.parameters.find((parameter) => this.codec(parameter).jsonSchema === void 0);
114
- if (unavailable !== void 0) return this.projection = { jsonSchemaError: this.codec(unavailable).jsonSchemaError ?? `No JSON Schema for parameter ${unavailable.wire}` };
115
- return this.projection = { jsonSchema: {
116
- type: "object",
117
- properties: Object.fromEntries(this.parameters.map((parameter) => [parameter.wire, {
118
- ...this.codec(parameter).jsonSchema,
119
- ...parameter.description === void 0 ? {} : { description: parameter.description }
120
- }])),
121
- required: this.parameters.filter((parameter) => !optional(parameter)).map((parameter) => parameter.wire),
122
- additionalProperties: false
123
- } };
124
- }
125
- };
126
- var ResultValue = class {
127
- spec;
128
- projection;
129
- constructor(spec) {
130
- this.spec = spec;
131
- }
132
- get mode() {
133
- return this.spec.mode;
134
- }
135
- get typeSymbol() {
136
- return this.spec.typeSymbol;
137
- }
138
- get create() {
139
- return this.spec.create;
140
- }
141
- get decode() {
142
- return this.spec.decode;
143
- }
144
- get encode() {
145
- return this.spec.encode;
146
- }
147
- get description() {
148
- return this.spec.description;
149
- }
150
- get jsonSchemaError() {
151
- return this.spec.jsonSchemaError;
152
- }
153
- get jsonSchema() {
154
- if (this.projection === void 0) {
155
- const schema = this.spec.jsonSchema;
156
- this.projection = { value: schema === void 0 ? void 0 : {
157
- ...schema,
158
- description: this.spec.description
159
- } };
160
- }
161
- return this.projection.value;
162
- }
163
- };
164
- //#endregion
165
- //#region lib/typert.remote-client.js
166
- let _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0$schema$value;
167
- const _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0$schema = () => _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0$schema$value ??= zod.z.union([
168
- zod.z.literal("chrome"),
169
- zod.z.literal("msedge"),
170
- zod.z.literal("chromium")
171
- ]);
172
- let _mind_harness_mh_browser_mhBrowser_locateFor_result$schema$value;
173
- const _mind_harness_mh_browser_mhBrowser_locateFor_result$schema = () => _mind_harness_mh_browser_mhBrowser_locateFor_result$schema$value ??= zod.z.union([zod.z.object({ "found": zod.z.string() }), zod.z.object({ "missing": zod.z.array(zod.z.object({
174
- "path": zod.z.string(),
175
- "reason": zod.z.string()
176
- })) })]);
177
- let _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0$schema$value;
178
- const _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0$schema = () => _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0$schema$value ??= zod.z.string();
179
- let _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema$value;
180
- const _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema = () => _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema$value ??= zod.z.object({ "status": zod.z.union([
181
- zod.z.literal("revealed"),
182
- zod.z.literal("not-handing-over"),
183
- zod.z.literal("no-tab")
184
- ]) });
185
- let _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0_strict$schema$value;
186
- const _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0_strict$schema = () => _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0_strict$schema$value ??= zod.z.union([
187
- zod.z.literal("chrome"),
188
- zod.z.literal("msedge"),
189
- zod.z.literal("chromium")
190
- ]);
191
- let _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0_strict$schema$value;
192
- const _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0_strict$schema = () => _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0_strict$schema$value ??= zod.z.string();
193
- const TYPERT_REMOTE = createTypertRemoteInfo({
194
- package: "@mind-harness/mh-browser",
195
- methods: {
196
- "mhBrowser/locateFor": {
197
- service: "mhBrowser",
198
- description: "Where `browser` would be launched from when no path is configured. The configured path is not consulted: the answer is what an empty path uses.",
199
- invocation: { kind: "direct" },
200
- parameters: [{
201
- name: "browser",
202
- source: "json",
203
- codec: {
204
- mode: "strict",
205
- typeSymbol: "@mind-harness/mh-browser/location#BrowserName",
206
- create: _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0$schema,
207
- jsonSchema: {
208
- "type": "string",
209
- "enum": [
210
- "chrome",
211
- "msedge",
212
- "chromium"
213
- ]
214
- }
215
- },
216
- strictCodec: {
217
- mode: "strict",
218
- typeSymbol: "@mind-harness/mh-browser/location#BrowserName",
219
- create: _mind_harness_mh_browser_mhBrowser_locateFor_parameter_0_strict$schema,
220
- jsonSchema: {
221
- "type": "string",
222
- "enum": [
223
- "chrome",
224
- "msedge",
225
- "chromium"
226
- ]
227
- }
228
- }
229
- }],
230
- cancellation: { parameter: "signal" },
231
- result: {
232
- mode: "strict",
233
- typeSymbol: "@mind-harness/mh-browser/location#BrowserLocation",
234
- create: _mind_harness_mh_browser_mhBrowser_locateFor_result$schema,
235
- jsonSchemaError: "typert tool schema: selected union branches overlap or have no required discriminator"
236
- },
237
- sourceLocation: {
238
- "file": "packages/mh-browser/src/service/service.ts",
239
- "line": 474,
240
- "column": 9
241
- }
242
- },
243
- "mhBrowser/revealHandover": {
244
- service: "mhBrowser",
245
- description: "The person asked to see the page this Session handed over.",
246
- invocation: { kind: "direct" },
247
- parameters: [{
248
- name: "sessionId",
249
- source: "json",
250
- codec: {
251
- mode: "strict",
252
- typeSymbol: "@mind-harness/mh-browser#mhBrowser/revealHandover:sessionId",
253
- create: _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0$schema,
254
- jsonSchema: { "type": "string" }
255
- },
256
- strictCodec: {
257
- mode: "strict",
258
- typeSymbol: "@mind-harness/mh-browser#mhBrowser/revealHandover:sessionId",
259
- create: _mind_harness_mh_browser_mhBrowser_revealHandover_parameter_0_strict$schema,
260
- jsonSchema: { "type": "string" }
261
- }
262
- }],
263
- cancellation: { parameter: "signal" },
264
- result: {
265
- mode: "strict",
266
- typeSymbol: "@mind-harness/mh-browser#mhBrowser/revealHandover:result",
267
- create: _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema,
268
- jsonSchema: {
269
- "type": "object",
270
- "properties": { "status": {
271
- "type": "string",
272
- "enum": [
273
- "revealed",
274
- "not-handing-over",
275
- "no-tab"
276
- ]
277
- } },
278
- "required": ["status"],
279
- "additionalProperties": true
280
- }
281
- },
282
- sourceLocation: {
283
- "file": "packages/mh-browser/src/service/service.ts",
284
- "line": 734,
285
- "column": 9
286
- }
36
+ function fail(code, message) {
37
+ return {
38
+ ok: false,
39
+ error: {
40
+ code: `${PACKAGE}/${code}`,
41
+ message
287
42
  }
288
- }
289
- }, zod.z);
43
+ };
44
+ }
45
+ function isCallResult(value) {
46
+ if (typeof value !== "object" || value === null || !("ok" in value)) return false;
47
+ if (value.ok === true) return "value" in value;
48
+ if (value.ok !== false || !("error" in value)) return false;
49
+ const error = value.error;
50
+ if (typeof error !== "object" || error === null) return false;
51
+ return "code" in error && typeof error.code === "string" && "message" in error && typeof error.message === "string";
52
+ }
290
53
  //#endregion
291
54
  //#region src/client/card.ts
292
- /** Host theme tokens only; the rules unmount with the row. */
55
+ /**
56
+ * Host theme tokens only; the rules unmount with the row.
57
+ * The button copies the host primary control at its default size, and the
58
+ * right-up outline that control uses for leaving the app. This row loads no
59
+ * host client package.
60
+ */
293
61
  const STYLE$1 = `
294
62
  .mh-br { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; color: var(--dsw-alias-label-primary); font-size: 14px; line-height: 20px; }
295
63
  .mh-br-line { margin: 0; }
296
64
  .mh-br-status { margin: 0; color: var(--dsw-alias-label-secondary); }
297
- .mh-br-button { padding: 4px 10px; border: 0.5px solid var(--dsw-alias-border-l3); border-radius: var(--dsw-radius-md); background: transparent; color: var(--dsw-alias-label-primary); font: inherit; cursor: pointer; }
65
+ .mh-br-button { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 4px; height: 36px; padding: 0 14px; border: none; border-radius: var(--dsw-radius-md); background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); font-size: 14px; line-height: 22px; cursor: pointer; }
66
+ @media (hover: hover) { .mh-br-button:hover { background: var(--dsw-alias-button-primary-hover); } }
67
+ .mh-br-icon { display: inline-flex; width: 16px; height: 16px; align-items: center; justify-content: center; }
298
68
  `;
299
- /** The open handover: the reason and the button that raises its window. */
69
+ /** Corner of the host right-up outline, 16px viewBox, 1px stroke. */
70
+ const RIGHT_UP_CORNER = "M11.7256 2.77441C12.5538 2.77469 13.2256 3.44616 13.2256 4.27441V10.1416H12.2256V4.27441C12.2256 3.99844 12.0015 3.77469 11.7256 3.77441H5.7207V2.77441H11.7256Z";
71
+ /** Stem of the host right-up outline. */
72
+ const RIGHT_UP_STEM = "M2.77441 13.2255L12.3756 3.62427";
73
+ function revealIcon() {
74
+ return (0, react.createElement)("span", { className: "mh-br-icon" }, (0, react.createElement)("svg", {
75
+ width: 14,
76
+ height: 14,
77
+ viewBox: "0 0 16 16",
78
+ fill: "none",
79
+ strokeWidth: 1,
80
+ "aria-hidden": true
81
+ }, (0, react.createElement)("path", {
82
+ d: RIGHT_UP_CORNER,
83
+ fill: "currentColor"
84
+ }), (0, react.createElement)("path", {
85
+ d: RIGHT_UP_STEM,
86
+ stroke: "currentColor"
87
+ })));
88
+ }
89
+ /** The open handover: a short line and the button that raises its window. */
300
90
  function HandoverRow(props) {
301
91
  const [note, setNote] = (0, react.useState)(void 0);
302
- return (0, react.createElement)("section", { className: "mh-br" }, (0, react.createElement)("style", null, STYLE$1), (0, react.createElement)("p", { className: "mh-br-line" }, props.t("waiting", { reason: props.reason })), (0, react.createElement)("button", {
92
+ return (0, react.createElement)("section", { className: "mh-br" }, (0, react.createElement)("style", null, STYLE$1), (0, react.createElement)("p", { className: "mh-br-line" }, props.t("waiting")), (0, react.createElement)("button", {
303
93
  type: "button",
304
94
  className: "mh-br-button",
305
95
  onClick: () => {
@@ -309,7 +99,7 @@ window.__ModuleLoader__.load({
309
99
  setNote(props.t("failed"));
310
100
  });
311
101
  }
312
- }, props.t("reveal")), note !== void 0 ? (0, react.createElement)("p", { className: "mh-br-status" }, note) : null);
102
+ }, revealIcon(), props.t("reveal")), note !== void 0 ? (0, react.createElement)("p", { className: "mh-br-status" }, note) : null);
313
103
  }
314
104
  /** `revealed` adds no status line. */
315
105
  function noteFor(result, t) {
@@ -322,8 +112,8 @@ window.__ModuleLoader__.load({
322
112
  /** Dictionary namespace for the handover row. */
323
113
  const NS = "mh-browser";
324
114
  const zh = {
325
- waiting: "浏览器交给你了:{reason}",
326
- reveal: "去浏览器",
115
+ waiting: "这一步要你在浏览器里做",
116
+ reveal: "打开浏览器里的页面",
327
117
  notHandingOver: "这次交接已经结束",
328
118
  noTab: "没有可以去的页面",
329
119
  failed: "没能切到浏览器",
@@ -338,6 +128,10 @@ window.__ModuleLoader__.load({
338
128
  pathDoesNotExist: "不存在",
339
129
  pathIsDirectory: "是目录",
340
130
  pathDenied: "没有权限({code})",
131
+ auto: "自动",
132
+ browserUsing: "当前使用:{name}。",
133
+ browserNone: "没有找到 Chrome 或 Edge。",
134
+ installChrome: "安装 Chrome",
341
135
  chrome: "Google Chrome",
342
136
  msedge: "Microsoft Edge",
343
137
  chromium: "Chromium",
@@ -351,8 +145,8 @@ window.__ModuleLoader__.load({
351
145
  discard: "放弃修改"
352
146
  };
353
147
  const en = {
354
- waiting: "The browser is yours: {reason}",
355
- reveal: "Go to the browser",
148
+ waiting: "This step needs you in the browser",
149
+ reveal: "Open the page in the browser",
356
150
  notHandingOver: "This handover has already ended",
357
151
  noTab: "There is no page to open",
358
152
  failed: "Could not switch to the browser",
@@ -367,6 +161,10 @@ window.__ModuleLoader__.load({
367
161
  pathDoesNotExist: "does not exist",
368
162
  pathIsDirectory: "is a directory",
369
163
  pathDenied: "permission denied ({code})",
164
+ auto: "Automatic",
165
+ browserUsing: "In use: {name}.",
166
+ browserNone: "Chrome and Edge were not found.",
167
+ installChrome: "Install Chrome",
370
168
  chrome: "Google Chrome",
371
169
  msedge: "Microsoft Edge",
372
170
  chromium: "Chromium",
@@ -388,6 +186,7 @@ window.__ModuleLoader__.load({
388
186
  */
389
187
  const HANDOVER_KIND = "browser-handover";
390
188
  const HANDOVER_TOOL = "browser_handover";
189
+ const ROW_DATA = {};
391
190
  function isRecord$1(value) {
392
191
  return typeof value === "object" && value !== null && !Array.isArray(value);
393
192
  }
@@ -395,22 +194,11 @@ window.__ModuleLoader__.load({
395
194
  if (!isRecord$1(data) || typeof data.turn !== "number") return void 0;
396
195
  return data.turn;
397
196
  }
398
- function reasonOf(value) {
399
- if (typeof value !== "string") return "";
400
- try {
401
- const parsed = JSON.parse(value);
402
- if (!isRecord$1(parsed) || typeof parsed.reason !== "string") return "";
403
- return parsed.reason;
404
- } catch {
405
- return "";
406
- }
407
- }
408
197
  function callOf(data) {
409
198
  if (!isRecord$1(data) || typeof data.callId !== "string" || typeof data.name !== "string") return void 0;
410
199
  return {
411
200
  callId: data.callId,
412
- name: data.name,
413
- arguments: data.arguments
201
+ name: data.name
414
202
  };
415
203
  }
416
204
  /** A result names its call on `message.source` and again on `message.toolCallId`. */
@@ -424,7 +212,7 @@ window.__ModuleLoader__.load({
424
212
  return {
425
213
  ...current,
426
214
  visibility: "hidden",
427
- data: { reason: "" }
215
+ data: ROW_DATA
428
216
  };
429
217
  }
430
218
  /**
@@ -445,7 +233,7 @@ window.__ModuleLoader__.load({
445
233
  location: context.start?.location ?? { kind: "unresolved" },
446
234
  visibility: open === void 0 ? "hidden" : "visible",
447
235
  groupTree: "turn",
448
- data: { reason: open?.reason ?? "" }
236
+ data: ROW_DATA
449
237
  };
450
238
  }
451
239
  const handoverRowDefinition = {
@@ -483,7 +271,6 @@ window.__ModuleLoader__.load({
483
271
  return {
484
272
  open: {
485
273
  callId: call.callId,
486
- reason: reasonOf(call.arguments),
487
274
  seq: event.seq
488
275
  },
489
276
  anchorSeq: event.seq
@@ -723,6 +510,7 @@ window.__ModuleLoader__.load({
723
510
  .mh-browser-chevron { position: absolute; top: 50%; right: 12px; transform: translateY(-50%); color: var(--dsw-alias-label-tertiary); pointer-events: none; }
724
511
  .mh-browser-invalid { margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-state-error-primary); }
725
512
  .mh-browser-hint { margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }
513
+ .mh-browser-hint a { color: var(--dsw-alias-label-secondary); }
726
514
  @media (hover: hover) {
727
515
  .mh-browser-help-button:hover { background: var(--dsw-alias-bg-layer-4); color: var(--dsw-alias-label-secondary); }
728
516
  .mh-browser-reset:hover:not(:disabled) { color: var(--dsw-alias-label-primary); }
@@ -874,10 +662,13 @@ window.__ModuleLoader__.load({
874
662
  /** `plugins.row.config` key: the bundle package, then the row id. */
875
663
  const ROW_KEY = "@mind-harness/mh-bundle#mh-browser";
876
664
  const BROWSER_CHOICES = [
665
+ "auto",
877
666
  "chrome",
878
667
  "msedge",
879
668
  "chromium"
880
669
  ];
670
+ /** Google's download page. Edge is not offered: its developer-mode prompt can disable the agent extension. */
671
+ const CHROME_INSTALL = "https://www.google.com/chrome/";
881
672
  const LOADING = { status: "loading" };
882
673
  /** Empty text clears the field. Anything else must be absolute, or start with `~`. */
883
674
  function parseExecutable(text) {
@@ -896,7 +687,15 @@ window.__ModuleLoader__.load({
896
687
  function pathPlaceholder(lookup, browser, t) {
897
688
  if (lookup.status === "loading") return t("pathSearching");
898
689
  if (lookup.status === "found") return lookup.path;
899
- if (lookup.status === "missing") return t("pathMissing", { name: t(browser) });
690
+ if (lookup.status === "missing" && browser !== "auto") return t("pathMissing", { name: t(browser) });
691
+ }
692
+ /** The sentence under the browser menu: which browser automatic selection is using. */
693
+ function browserStatus(lookup, browser, t) {
694
+ const restart = t("browserHint");
695
+ if (browser !== "auto") return restart;
696
+ if (lookup.status === "found" && lookup.browser !== void 0) return `${t("browserUsing", { name: t(lookup.browser) })} ${restart}`;
697
+ if (lookup.status === "missing") return `${t("browserNone")} ${restart}`;
698
+ return restart;
900
699
  }
901
700
  /** The full path, or every place that was tried. Absent while looking or after a failed lookup. */
902
701
  function pathHelpText(lookup, t) {
@@ -987,7 +786,10 @@ window.__ModuleLoader__.load({
987
786
  }
988
787
  };
989
788
  function registerSettings(ctx) {
990
- const form = new BrowserForm(ctx.configForms.get(ENTRY_ID), (browser, signal) => ctx.remote.mhBrowser.locateFor(browser, signal));
789
+ const form = new BrowserForm(ctx.configForms.get(ENTRY_ID), (browser, signal) => call("locate-for", {
790
+ method: "POST",
791
+ body: { browser }
792
+ }, signal));
991
793
  ctx.effect(() => () => {
992
794
  form.dispose();
993
795
  });
@@ -1017,8 +819,9 @@ window.__ModuleLoader__.load({
1017
819
  };
1018
820
  }, [props.form]);
1019
821
  const disabled = !state.writable || state.saving;
1020
- const help = helpOf(lookup, props.t);
822
+ const help = helpOf(lookup, browser, props.t);
1021
823
  const placeholder = state.executablePath.text === "" ? pathPlaceholder(lookup, browser, props.t) : void 0;
824
+ const offerChrome = browser === "auto" && lookup.status === "missing";
1022
825
  return (0, react.createElement)(SettingsFrame, {
1023
826
  labels: formLabels(props.t),
1024
827
  state,
@@ -1027,7 +830,7 @@ window.__ModuleLoader__.load({
1027
830
  }, (0, react.createElement)(ChoiceField, {
1028
831
  id: "mh-browser-name",
1029
832
  label: props.t("browserLabel"),
1030
- hint: props.t("browserHint"),
833
+ hint: browserStatus(lookup, browser, props.t),
1031
834
  ...state.browser,
1032
835
  text: browser,
1033
836
  options: BROWSER_CHOICES.map((name) => ({
@@ -1039,7 +842,11 @@ window.__ModuleLoader__.load({
1039
842
  disabled,
1040
843
  onEdit: (value) => props.form.edit("browser", value),
1041
844
  onReset: () => props.form.reset("browser")
1042
- }), (0, react.createElement)(TextField, {
845
+ }), offerChrome ? (0, react.createElement)("p", { className: "mh-browser-hint" }, (0, react.createElement)("a", {
846
+ href: CHROME_INSTALL,
847
+ target: "_blank",
848
+ rel: "noopener noreferrer"
849
+ }, props.t("installChrome"))) : null, (0, react.createElement)(TextField, {
1043
850
  id: "mh-browser-path",
1044
851
  label: props.t("pathLabel"),
1045
852
  hint: props.t("pathHint"),
@@ -1057,7 +864,7 @@ window.__ModuleLoader__.load({
1057
864
  function browserField() {
1058
865
  return {
1059
866
  field: "browser",
1060
- format: (value) => typeof value === "string" && isChoice(value) ? value : "chrome",
867
+ format: (value) => typeof value === "string" && isChoice(value) ? value : "auto",
1061
868
  parse: (text) => isChoice(text) ? {
1062
869
  kind: "set",
1063
870
  value: text
@@ -1081,7 +888,7 @@ window.__ModuleLoader__.load({
1081
888
  };
1082
889
  }
1083
890
  function choiceOf(text) {
1084
- return isChoice(text) ? text : "chrome";
891
+ return isChoice(text) ? text : "auto";
1085
892
  }
1086
893
  function isChoice(value) {
1087
894
  return BROWSER_CHOICES.includes(value);
@@ -1090,16 +897,25 @@ window.__ModuleLoader__.load({
1090
897
  if (!result.ok) return { status: "failed" };
1091
898
  if ("found" in result.value) return {
1092
899
  status: "found",
1093
- path: result.value.found
900
+ path: result.value.found,
901
+ ...result.value.browser !== void 0 ? { browser: result.value.browser } : {}
1094
902
  };
1095
903
  return {
1096
904
  status: "missing",
1097
905
  tried: result.value.missing
1098
906
  };
1099
907
  }
1100
- function helpOf(lookup, t) {
908
+ function helpOf(lookup, browser, t) {
1101
909
  const text = pathHelpText(lookup, t);
1102
910
  if (text === void 0) return void 0;
911
+ if (browser === "auto" && lookup.status === "missing") return {
912
+ label: t("pathHelp"),
913
+ content: (0, react.createElement)("span", null, text, " ", (0, react.createElement)("a", {
914
+ href: CHROME_INSTALL,
915
+ target: "_blank",
916
+ rel: "noopener noreferrer"
917
+ }, t("installChrome")))
918
+ };
1103
919
  return {
1104
920
  label: t("pathHelp"),
1105
921
  content: text
@@ -1116,50 +932,58 @@ window.__ModuleLoader__.load({
1116
932
  //#region src/client/client.ts
1117
933
  /**
1118
934
  * Cordis services this client reads. `dsh.client.inject` is package load
1119
- * order, not this list. `remote.mhBrowser` is absent until `$mount`.
935
+ * order, not this list.
1120
936
  */
1121
937
  const inject = [
1122
938
  "slots",
1123
939
  "locale",
1124
- "remote",
1125
940
  "uiConversation",
1126
941
  "configForms"
1127
942
  ];
1128
- const namespaceInject = [...inject, "remote.mhBrowser"];
943
+ /** Host calls used by the handover row and the settings lookup. */
944
+ function browserApi() {
945
+ return {
946
+ revealHandover: (sessionId, signal) => call("reveal-handover", {
947
+ method: "POST",
948
+ body: { sessionId }
949
+ }, signal),
950
+ locateFor: (browser, signal) => call("locate-for", {
951
+ method: "POST",
952
+ body: { browser }
953
+ }, signal)
954
+ };
955
+ }
1129
956
  async function apply(ctx) {
1130
- const unmount = await ctx.remote.$mount(TYPERT_REMOTE);
1131
957
  let release = () => void 0;
1132
- await ctx.inject(namespaceInject, (inner) => {
958
+ await ctx.inject([...inject], (inner) => {
1133
959
  release = start(inner);
1134
960
  registerSettings(inner);
1135
961
  });
1136
962
  return async () => {
1137
963
  release();
1138
- await unmount();
1139
964
  };
1140
965
  }
1141
966
  /** Register the handover row. The tool call itself keeps the generic tool row. */
1142
- function start(ctx) {
967
+ function start(ctx, api = browserApi()) {
1143
968
  ctx.effect(() => ctx.locale.register(NS, {
1144
969
  zh,
1145
970
  en
1146
971
  }));
1147
972
  ctx.effect(() => ctx.uiConversation.events.register(handoverRowDefinition));
1148
- const remote = ctx.remote.mhBrowser;
1149
973
  ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
1150
974
  name: "conversation.chat.node",
1151
975
  key: HANDOVER_KIND,
1152
976
  locale: NS
1153
977
  }, (props) => (0, react.createElement)(HandoverRow, {
1154
978
  sessionId: props.sessionId,
1155
- reason: props.node.data.reason,
1156
979
  t: props.t,
1157
- reveal: (sessionId, signal) => remote.revealHandover(sessionId, signal)
980
+ reveal: (sessionId, signal) => api.revealHandover(sessionId, signal)
1158
981
  })));
1159
982
  return () => void 0;
1160
983
  }
1161
984
  //#endregion
1162
985
  exports.apply = apply;
986
+ exports.browserApi = browserApi;
1163
987
  exports.inject = inject;
1164
988
  exports.start = start;
1165
989
  return module.exports;