@anweat/dsh-browser 0.1.15-alpha.2 → 0.1.15

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.js CHANGED
@@ -4,19 +4,16 @@ 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 _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
7
8
  let react = require("react");
8
9
  let react_jsx_runtime = require("react/jsx-runtime");
9
10
  //#region src/client/form.ts
10
- const textField = (field) => ({
11
- field,
12
- format: (value) => typeof value === "string" ? value : "",
13
- parse(text) {
14
- return text.trim() === "" ? { kind: "clear" } : {
15
- kind: "set",
16
- value: text.trim()
17
- };
18
- }
19
- });
11
+ /** A free-text field that clears when emptied, so blanking the control resets it. */
12
+ const textField = (field) => (0, _deepseek_ai_dsh_client_ui_primitives.settingsTextField)(field);
13
+ /**
14
+ * A boolean field. The card renders a checkbox, so the conversion is explicit
15
+ * rather than relying on the shared model's text round-trip.
16
+ */
20
17
  const booleanField = (field) => ({
21
18
  field,
22
19
  format: (value) => value === true ? "true" : "false",
@@ -25,6 +22,10 @@ window.__ModuleLoader__.load({
25
22
  value: text === "true"
26
23
  } : void 0
27
24
  });
25
+ /**
26
+ * An enumerated field. Accepts only a value on the list, so an unknown string
27
+ * blocks the save instead of writing something the Host would reject.
28
+ */
28
29
  const enumField = (field, values) => ({
29
30
  field,
30
31
  format: (value) => typeof value === "string" ? value : values[0] ?? "",
@@ -33,34 +34,22 @@ window.__ModuleLoader__.load({
33
34
  value: text
34
35
  } : void 0
35
36
  });
36
- const integerField = (field, min, max) => ({
37
- field,
38
- format: (value) => typeof value === "number" && Number.isInteger(value) ? String(value) : "",
39
- parse(text) {
40
- if (text.trim() === "") return { kind: "clear" };
41
- const value = Number(text);
42
- return Number.isInteger(value) && value >= min && value <= max ? {
43
- kind: "set",
44
- value
45
- } : void 0;
46
- }
47
- });
48
- const stringArrayField = (field) => ({
49
- field,
50
- format: (value) => Array.isArray(value) ? JSON.stringify(value, null, 2) : "",
51
- parse(text) {
52
- if (text.trim() === "") return { kind: "clear" };
53
- try {
54
- const value = JSON.parse(text);
55
- return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? {
37
+ /** A whole-number field bounded to a range; an empty draft clears it. */
38
+ const rangedNumberField = (field, min, max) => {
39
+ return {
40
+ field,
41
+ format: (0, _deepseek_ai_dsh_client_ui_primitives.settingsNumberField)(field).format,
42
+ parse(text) {
43
+ if (text.trim() === "") return { kind: "clear" };
44
+ const value = Number(text);
45
+ return Number.isInteger(value) && value >= min && value <= max ? {
56
46
  kind: "set",
57
47
  value
58
48
  } : void 0;
59
- } catch {
60
- return;
61
49
  }
62
- }
63
- });
50
+ };
51
+ };
52
+ /** JSON object field: parses to a record, and an empty draft clears the field. */
64
53
  const jsonField = (field, validate) => ({
65
54
  field,
66
55
  format: (value) => value && typeof value === "object" && !Array.isArray(value) ? JSON.stringify(value, null, 2) : "",
@@ -120,21 +109,22 @@ window.__ModuleLoader__.load({
120
109
  "auto-draft"
121
110
  ].includes(String(value.persistenceMode))) return false;
122
111
  if (value.activationMode !== void 0 && !["manual", "auto-tested"].includes(String(value.activationMode))) return false;
112
+ const numeric = [
113
+ "minSuccessfulRuns",
114
+ "minDistinctSessions",
115
+ "successWindowDays",
116
+ "minSuccessRate",
117
+ "maxCandidates",
118
+ "candidateTtlDays",
119
+ "maxSuggestionsPerDay",
120
+ "maxDrafts",
121
+ "maxActiveAssets",
122
+ "retrievalTopK",
123
+ "catalogTokenBudget",
124
+ "maxModelDraftWritesPerSession"
125
+ ];
123
126
  return Object.entries(value).every(([key, entry]) => {
124
- if (![
125
- "minSuccessfulRuns",
126
- "minDistinctSessions",
127
- "successWindowDays",
128
- "minSuccessRate",
129
- "maxCandidates",
130
- "candidateTtlDays",
131
- "maxSuggestionsPerDay",
132
- "maxDrafts",
133
- "maxActiveAssets",
134
- "retrievalTopK",
135
- "catalogTokenBudget",
136
- "maxModelDraftWritesPerSession"
137
- ].includes(key)) return true;
127
+ if (!numeric.includes(key)) return true;
138
128
  return typeof entry === "number" && Number.isFinite(entry) && entry >= 0;
139
129
  });
140
130
  }
@@ -145,6 +135,11 @@ window.__ModuleLoader__.load({
145
135
  return entry === void 0 || typeof entry === "number" && Number.isInteger(entry) && entry >= min && entry <= max;
146
136
  });
147
137
  }
138
+ /**
139
+ * The single-input section fields the card edits, in display order. Every entry
140
+ * must also be `.volatile()` in the Host Config schema: `volatileForm` drops
141
+ * unmarked fields, and the Host would refuse a write to one.
142
+ */
148
143
  const FIELD_SPECS = [
149
144
  booleanField("enabled"),
150
145
  enumField("automationMode", [
@@ -155,210 +150,52 @@ window.__ModuleLoader__.load({
155
150
  ]),
156
151
  enumField("browserRuntime", ["playwright", "patchright"]),
157
152
  textField("channel"),
158
- integerField("cdpPort", 1, 65535),
159
- stringArrayField("args"),
153
+ rangedNumberField("cdpPort", 1, 65535),
160
154
  booleanField("headless"),
161
155
  booleanField("opencliEnabled"),
162
- jsonField("usagePolicy", validUsagePolicy),
163
- jsonField("automationAssets", validAssetPolicy),
164
156
  booleanField("autoInstall"),
165
157
  textField("storageStatePath"),
166
- jsonField("authProfiles"),
167
158
  textField("defaultAuthProfile"),
168
- jsonField("rulePacks"),
169
159
  textField("executablePath"),
170
160
  textField("snapshotDir"),
171
161
  booleanField("verbose")
172
162
  ];
173
- const SPEC_BY_FIELD = new Map(FIELD_SPECS.map((spec) => [spec.field, spec]));
174
- function stable(value) {
175
- if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
176
- if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stable(entry)}`).join(",")}}`;
177
- return JSON.stringify(value);
178
- }
179
- function same(left, right) {
180
- return stable(left) === stable(right);
181
- }
182
- function createLocalStore(initial) {
183
- let snapshot = initial;
184
- const listeners = /* @__PURE__ */ new Set();
185
- return {
186
- getSnapshot: () => snapshot,
187
- subscribe(listener) {
188
- listeners.add(listener);
189
- return () => {
190
- listeners.delete(listener);
191
- };
192
- },
193
- set(next) {
194
- snapshot = next;
195
- for (const listener of listeners) listener();
196
- },
197
- update(updater) {
198
- const draft = structuredClone(snapshot);
199
- updater(draft);
200
- snapshot = draft;
201
- for (const listener of listeners) listener();
202
- }
203
- };
204
- }
163
+ /** The JSON-shaped section fields the card renders as code editors. */
164
+ const JSON_FIELD_SPECS = [jsonField("usagePolicy", validUsagePolicy), jsonField("automationAssets", validAssetPolicy)];
165
+ /** Section fields rendered as JSON code editors rather than single inputs. */
166
+ const JSON_FIELDS = new Set(JSON_FIELD_SPECS.map((spec) => spec.field));
167
+ /** Every field the card renders, in display order. */
168
+ const ALL_FIELD_SPECS = [...FIELD_SPECS, ...JSON_FIELD_SPECS];
205
169
  var BrowserSettingsController = class {
206
- scope;
207
- staged = /* @__PURE__ */ new Map();
170
+ form;
208
171
  store;
209
- unsubscribe;
210
- saving = false;
211
- failed = false;
212
172
  constructor(scope) {
213
- this.scope = scope;
214
- this.store = createLocalStore(this.project());
215
- this.unsubscribe = scope.subscribe(() => {
216
- this.publish();
217
- });
173
+ this.form = new _deepseek_ai_dsh_client_ui_primitives.SettingsFormModel(scope, [...ALL_FIELD_SPECS]);
174
+ this.store = this.form.bind(() => this.project());
218
175
  }
219
176
  inject() {
220
177
  return {
221
178
  hooks: { browserSettings: this.store },
222
- edit: (field, text) => {
223
- this.edit(field, text);
224
- },
225
- resetField: (field) => {
226
- this.resetField(field);
227
- },
228
- save: () => {
229
- this.save();
230
- },
231
- discard: () => {
232
- this.discard();
233
- }
179
+ ...this.form.actions()
234
180
  };
235
181
  }
236
182
  snapshot() {
237
183
  return this.store.getSnapshot();
238
184
  }
239
- edit(field, text) {
240
- this.staged.set(field, {
241
- text,
242
- clear: false
243
- });
244
- this.failed = false;
245
- this.publish();
246
- }
247
- resetField(field) {
248
- const spec = this.spec(field);
249
- this.staged.set(field, {
250
- text: spec.format(this.baseValue(field)),
251
- clear: true
252
- });
253
- this.failed = false;
254
- this.publish();
255
- }
256
- discard() {
257
- this.staged.clear();
258
- this.failed = false;
259
- this.publish();
260
- }
261
- async save() {
262
- const plan = this.plan();
263
- if (this.saving || plan.some((item) => item.write === void 0) || plan.length === 0) return;
264
- this.saving = true;
265
- this.failed = false;
266
- this.publish();
267
- let landed = true;
268
- try {
269
- for (const item of plan) {
270
- if (!item.write) {
271
- landed = false;
272
- break;
273
- }
274
- if (item.write.kind === "clear") {
275
- await this.scope.unset(item.field);
276
- landed = !this.stored(item.field) && landed;
277
- } else {
278
- await this.scope.set(item.field, item.write.value);
279
- landed = same(this.userLayer()?.[item.field], item.write.value) && landed;
280
- }
281
- }
282
- } catch {
283
- landed = false;
284
- }
285
- if (landed) this.staged.clear();
286
- this.saving = false;
287
- this.failed = !landed;
288
- this.publish();
289
- }
290
185
  dispose() {
291
- this.unsubscribe();
186
+ this.form.dispose();
292
187
  }
293
188
  project() {
294
189
  const fields = {};
295
- for (const spec of FIELD_SPECS) fields[spec.field] = this.field(spec.field);
296
- const plan = this.plan();
297
- return {
298
- available: this.scope.getSnapshot().status === "ready",
299
- writable: this.scope.getSnapshot().writable,
300
- dirty: plan.length > 0,
301
- invalid: plan.some((item) => item.write === void 0),
302
- saving: this.saving,
303
- failed: this.failed,
304
- fields
305
- };
306
- }
307
- field(field) {
308
- const spec = this.spec(field);
309
- const draft = this.staged.get(field);
310
- if (!draft) return {
311
- text: spec.format(this.sectionValue(field)),
312
- overridden: this.stored(field),
313
- invalid: false
314
- };
315
- const write = draft.clear ? { kind: "clear" } : spec.parse(draft.text);
190
+ for (const spec of FIELD_SPECS) fields[spec.field] = this.form.field(spec.field);
191
+ const jsonFields = {};
192
+ for (const spec of JSON_FIELD_SPECS) jsonFields[spec.field] = this.form.field(spec.field);
316
193
  return {
317
- text: draft.text,
318
- overridden: write?.kind === "set",
319
- invalid: write === void 0
194
+ ...this.form.shell(),
195
+ fields,
196
+ jsonFields
320
197
  };
321
198
  }
322
- plan() {
323
- const writes = [];
324
- for (const [field, draft] of this.staged) {
325
- const spec = this.spec(field);
326
- if (draft.clear) {
327
- if (this.stored(field)) writes.push({
328
- field,
329
- write: { kind: "clear" }
330
- });
331
- continue;
332
- }
333
- if (draft.text === spec.format(this.sectionValue(field))) continue;
334
- writes.push({
335
- field,
336
- write: spec.parse(draft.text)
337
- });
338
- }
339
- return writes;
340
- }
341
- spec(field) {
342
- const spec = SPEC_BY_FIELD.get(field);
343
- if (!spec) throw new Error(`unknown browser settings field: ${field}`);
344
- return spec;
345
- }
346
- sectionValue(field) {
347
- return this.scope.getSnapshot().value?.[field];
348
- }
349
- baseValue(field) {
350
- return this.scope.getSnapshot().base?.[field];
351
- }
352
- userLayer() {
353
- return this.scope.getSnapshot().user;
354
- }
355
- stored(field) {
356
- const user = this.userLayer();
357
- return user !== void 0 && Object.hasOwn(user, field);
358
- }
359
- publish() {
360
- this.store.set(this.project());
361
- }
362
199
  };
363
200
  //#endregion
364
201
  //#region src/client/styles.ts
@@ -430,268 +267,126 @@ window.__ModuleLoader__.load({
430
267
  }
431
268
  //#endregion
432
269
  //#region src/client/SettingsCard.tsx
433
- function FieldShell(props) {
434
- const id = `dsh-browser-${props.field}`;
435
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
436
- className: `${styles.field} ${props.state.invalid ? styles.invalid : ""}`,
437
- children: [
438
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
439
- className: styles.fieldHead,
440
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
441
- className: styles.label,
442
- htmlFor: id,
443
- children: props.label
444
- }), props.state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
445
- type: "button",
446
- className: styles.reset,
447
- disabled: props.disabled,
448
- onClick: () => props.onReset(props.field),
449
- children: props.resetLabel
450
- }) : null]
451
- }),
452
- props.children,
453
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
454
- className: styles.hint,
455
- children: props.hint
456
- })
270
+ /**
271
+ * The browser settings card, as the Plugins page renders it.
272
+ *
273
+ * The page owns the card frame — its title button, disclosure, and artwork come
274
+ * from the slot registration — so this component renders only what goes inside:
275
+ * the one-liner for `view: 'summary'`, and the form body for `view: 'page'`.
276
+ * Drawing our own frame here produced a doubled card and an inner header button
277
+ * that swallowed the platform's clicks.
278
+ *
279
+ * The form chrome is the shared `SettingsForm`/`SettingsValueField` pair from
280
+ * `@deepseek-ai/dsh-client-ui-primitives`, the same components every official
281
+ * settings page uses, so the card matches them without restating their markup.
282
+ * @module dsh-browser/client/SettingsCard
283
+ */
284
+ /** The copy the shared form frame renders, from this page's dictionary. */
285
+ function formLabels(t) {
286
+ return {
287
+ unavailable: t("unavailable"),
288
+ readOnly: t("readOnly"),
289
+ saveFailed: t("saveFailed"),
290
+ save: t("save"),
291
+ saving: t("saving")
292
+ };
293
+ }
294
+ const SECTION_LAYOUT = [
295
+ {
296
+ title: "freedom",
297
+ hint: "freedomHint",
298
+ fields: [
299
+ "enabled",
300
+ "automationMode",
301
+ "opencliEnabled"
457
302
  ]
458
- });
303
+ },
304
+ {
305
+ title: "runtime",
306
+ hint: "runtimeHint",
307
+ fields: [
308
+ "browserRuntime",
309
+ "channel",
310
+ "headless",
311
+ "autoInstall",
312
+ "executablePath",
313
+ "cdpPort"
314
+ ]
315
+ },
316
+ {
317
+ title: "advanced",
318
+ hint: "advancedHint",
319
+ fields: [
320
+ "storageStatePath",
321
+ "defaultAuthProfile",
322
+ "snapshotDir",
323
+ "verbose"
324
+ ]
325
+ }
326
+ ];
327
+ /** The section field a spec names, when it is one of the single-input fields. */
328
+ function sectionField(field) {
329
+ return FIELD_SPECS.some((spec) => spec.field === field) ? field : void 0;
459
330
  }
460
331
  function SettingsCard(props) {
461
332
  const { t } = props;
462
333
  const state = props.useBrowserSettings((snapshot) => snapshot);
463
- const [open, setOpen] = (0, react.useState)(false);
464
- if (!state.available) return null;
465
- const disabled = !state.writable || state.saving;
466
- const text = (field, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
467
- field,
468
- state: state.fields[field],
469
- label: t(label),
470
- hint: state.fields[field].invalid ? t("invalid") : t(hint),
471
- disabled,
472
- resetLabel: t("reset"),
473
- onReset: props.resetField,
474
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
475
- id: `dsh-browser-${field}`,
476
- className: styles.input,
477
- value: state.fields[field].text,
478
- disabled,
479
- "aria-invalid": state.fields[field].invalid || void 0,
480
- onChange: (event) => props.edit(field, event.currentTarget.value)
481
- })
482
- });
483
- const select = (field, label, hint, options) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
484
- field,
485
- state: state.fields[field],
486
- label: t(label),
487
- hint: state.fields[field].invalid ? t("invalid") : t(hint),
488
- disabled,
489
- resetLabel: t("reset"),
490
- onReset: props.resetField,
491
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
492
- id: `dsh-browser-${field}`,
493
- className: styles.input,
494
- value: state.fields[field].text,
334
+ if (props.view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("description") });
335
+ const disabled = !state.writable;
336
+ const numeric = /* @__PURE__ */ new Set(["cdpPort"]);
337
+ const field = (spec) => {
338
+ const known = sectionField(spec.field);
339
+ const fieldState = known ? state.fields[known] : state.jsonFields[spec.field];
340
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsValueField, {
341
+ id: `plugin-config-dsh-browser-${spec.field}`,
342
+ label: t(spec.field),
343
+ hint: t(`${spec.field}Hint`),
344
+ overriddenLabel: t("overridden"),
345
+ resetLabel: t("reset"),
346
+ invalidLabel: t(JSON_FIELDS.has(spec.field) ? "invalidJson" : "invalidNumber"),
347
+ numeric: numeric.has(spec.field),
495
348
  disabled,
496
- onChange: (event) => props.edit(field, event.currentTarget.value),
497
- children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
498
- value: option,
499
- children: option
500
- }, option))
501
- })
502
- });
503
- const json = (field, label, hint, rows = 7) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
504
- field,
505
- state: state.fields[field],
506
- label: t(label),
507
- hint: state.fields[field].invalid ? t("invalidJson") : t(hint),
508
- disabled,
509
- resetLabel: t("reset"),
510
- onReset: props.resetField,
511
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
512
- id: `dsh-browser-${field}`,
513
- className: `${styles.input} ${styles.textarea} ${styles.code}`,
514
- rows,
515
- value: state.fields[field].text,
516
- disabled,
517
- spellCheck: false,
518
- "aria-invalid": state.fields[field].invalid || void 0,
519
- onChange: (event) => props.edit(field, event.currentTarget.value)
520
- })
521
- });
522
- const toggle = (field, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
523
- className: styles.toggle,
524
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
525
- className: styles.toggleLabel,
526
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
527
- className: styles.check,
528
- type: "checkbox",
529
- checked: state.fields[field].text === "true",
530
- disabled,
531
- onChange: (event) => props.edit(field, String(event.currentTarget.checked))
532
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
533
- className: styles.label,
534
- children: t(label)
535
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
536
- className: styles.hint,
537
- children: t(hint)
538
- })] })]
539
- }), state.fields[field].overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
540
- type: "button",
541
- className: styles.reset,
542
- disabled,
543
- onClick: () => props.resetField(field),
544
- children: t("reset")
545
- }) : null]
546
- });
547
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
548
- className: `${styles.card} ${open ? styles.open : ""}`,
549
- "data-dsh-browser-settings": true,
550
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
551
- type: "button",
552
- className: styles.header,
553
- "aria-expanded": open,
554
- "aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
555
- onClick: () => setOpen(!open),
556
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
557
- className: styles.head,
558
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
559
- className: styles.titleRow,
560
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
561
- className: styles.name,
562
- children: t("title")
563
- }), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
564
- className: styles.badge,
565
- children: t("unsaved")
566
- }) : null]
567
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
568
- className: styles.description,
569
- children: t("description")
349
+ ...fieldState,
350
+ onEdit: (text) => props.edit(spec.field, text),
351
+ onReset: () => props.resetField(spec.field)
352
+ }, spec.field);
353
+ };
354
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.SettingsForm, {
355
+ labels: formLabels(t),
356
+ state,
357
+ onSave: props.save,
358
+ onDiscard: props.discard,
359
+ children: [
360
+ SECTION_LAYOUT.map((section) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
361
+ className: styles.section,
362
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
363
+ className: styles.sectionHead,
364
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t(section.title) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(section.hint) })]
365
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
366
+ className: styles.grid,
367
+ children: section.fields.map((name) => field(FIELD_SPECS.find((spec) => spec.field === name)))
570
368
  })]
571
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
572
- className: `${styles.chevron} ${open ? styles.chevronOpen : ""}`,
573
- viewBox: "0 0 14 14",
574
- width: "14",
575
- height: "14",
576
- "aria-hidden": "true",
577
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
578
- d: "M3.5 5.5 7 9l3.5-3.5",
579
- fill: "none",
580
- stroke: "currentColor",
581
- strokeWidth: "1.5"
582
- })
583
- })]
584
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
585
- className: styles.body,
586
- children: [
587
- !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
588
- className: styles.notice,
589
- role: "status",
590
- children: t("readOnly")
591
- }) : null,
592
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
593
- className: styles.section,
594
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
369
+ }, section.title)),
370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
371
+ className: styles.section,
372
+ children: [
373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
595
374
  className: styles.sectionHead,
596
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("freedom") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("freedomHint") })]
597
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
375
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("usageHint") })]
376
+ }),
377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
598
378
  className: styles.grid,
599
- children: [
600
- toggle("enabled", "enabled", "enabledHint"),
601
- select("automationMode", "automationMode", "automationModeHint", [
602
- "read-only",
603
- "standard",
604
- "autonomous",
605
- "unrestricted"
606
- ]),
607
- toggle("opencliEnabled", "opencliEnabled", "opencliEnabledHint")
608
- ]
609
- })]
610
- }),
611
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
612
- className: styles.section,
613
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
614
- className: styles.sectionHead,
615
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("runtime") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("runtimeHint") })]
616
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
617
- className: styles.grid,
618
- children: [
619
- select("browserRuntime", "browserRuntime", "browserRuntimeHint", ["playwright", "patchright"]),
620
- text("channel", "channel", "channelHint"),
621
- toggle("headless", "headless", "headlessHint"),
622
- toggle("autoInstall", "autoInstall", "autoInstallHint"),
623
- text("executablePath", "executablePath", "executablePathHint"),
624
- text("cdpPort", "cdpPort", "cdpPortHint"),
625
- json("args", "args", "argsHint", 5)
626
- ]
627
- })]
628
- }),
629
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
630
- className: styles.section,
631
- children: [
632
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
633
- className: styles.sectionHead,
634
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("usageHint") })]
635
- }),
636
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
637
- className: styles.grid,
638
- children: [json("usagePolicy", "usagePolicy", "usagePolicyHint", 10), json("automationAssets", "automationAssets", "automationAssetsHint", 14)]
639
- }),
640
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
641
- className: styles.notice,
642
- role: "note",
643
- children: t("restart")
644
- })
645
- ]
646
- }),
647
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutomationAssetsPanel, { ...props }),
648
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
649
- className: styles.advanced,
650
- children: [
651
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("advanced") }),
652
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
653
- className: styles.hint,
654
- children: t("advancedHint")
655
- }),
656
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
657
- className: styles.grid,
658
- children: [
659
- text("storageStatePath", "storageStatePath", "storageStatePathHint"),
660
- text("defaultAuthProfile", "defaultAuthProfile", "defaultAuthProfileHint"),
661
- json("authProfiles", "authProfiles", "authProfilesHint"),
662
- json("rulePacks", "rulePacks", "rulePacksHint"),
663
- text("snapshotDir", "snapshotDir", "snapshotDirHint"),
664
- toggle("verbose", "verbose", "verboseHint")
665
- ]
666
- })
667
- ]
668
- }),
669
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
670
- className: styles.footer,
671
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
672
- className: state.failed ? styles.failed : styles.status,
673
- role: "status",
674
- "aria-live": "polite",
675
- children: t(state.failed ? "saveFailed" : state.invalid ? "invalidSave" : state.dirty ? "pending" : "saved")
676
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
677
- className: styles.actions,
678
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
679
- type: "button",
680
- className: styles.secondary,
681
- disabled: !state.dirty || state.saving,
682
- onClick: props.discard,
683
- children: t("discard")
684
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
685
- type: "button",
686
- className: styles.primary,
687
- disabled: !state.dirty || state.invalid || state.saving || !state.writable,
688
- onClick: props.save,
689
- children: t(state.saving ? "saving" : "save")
690
- })]
691
- })]
692
- })
693
- ]
694
- }) : null]
379
+ children: JSON_FIELD_SPECS.map((spec) => field(spec))
380
+ }),
381
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
382
+ className: styles.notice,
383
+ role: "note",
384
+ children: t("restart")
385
+ })
386
+ ]
387
+ }),
388
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutomationAssetsPanel, { ...props })
389
+ ]
695
390
  });
696
391
  }
697
392
  function AutomationAssetsPanel(props) {
@@ -1022,6 +717,9 @@ window.__ModuleLoader__.load({
1022
717
  verbose: "详细日志",
1023
718
  verboseHint: "输出启动和诊断信息。",
1024
719
  reset: "恢复部署值",
720
+ overridden: "已覆盖",
721
+ invalidNumber: "请填写数字,留空表示使用默认值。",
722
+ unavailable: "该插件当前未加载,暂时无法配置。",
1025
723
  invalid: "值无效,请检查格式或范围。",
1026
724
  invalidJson: "JSON 或数值范围无效。",
1027
725
  restart: "保存后完整重启 profile,运行时和工具目录才会重新注册。",
@@ -1111,6 +809,9 @@ window.__ModuleLoader__.load({
1111
809
  verbose: "Verbose logs",
1112
810
  verboseHint: "Emit startup and diagnostic details.",
1113
811
  reset: "Restore deployed",
812
+ overridden: "Overridden",
813
+ invalidNumber: "Enter a number, or leave blank to use the default.",
814
+ unavailable: "This plugin is not loaded, so it cannot be configured right now.",
1114
815
  invalid: "Invalid value. Check format and bounds.",
1115
816
  invalidJson: "Invalid JSON or numeric bounds.",
1116
817
  restart: "Fully restart the profile after saving so runtime and tool catalogs are re-registered.",
@@ -1151,6 +852,8 @@ window.__ModuleLoader__.load({
1151
852
  }
1152
853
  };
1153
854
  }
855
+ /** This plugin's private RPC channel, matching the Host half. */
856
+ const CHANNEL = "/dsh-browser-assets";
1154
857
  var AutomationAssetsController = class {
1155
858
  rpc;
1156
859
  store = localStore({
@@ -1254,7 +957,7 @@ window.__ModuleLoader__.load({
1254
957
  }
1255
958
  }
1256
959
  async call(endpoint, payload) {
1257
- const result = await this.rpc.call("/api", `dsh-browser-assets/${endpoint}`, payload);
960
+ const result = await this.rpc.call(CHANNEL, endpoint, payload);
1258
961
  if (!result.ok) throw new Error(result.error.message);
1259
962
  return result.value;
1260
963
  }
@@ -1278,7 +981,7 @@ window.__ModuleLoader__.load({
1278
981
  "slots",
1279
982
  "locale",
1280
983
  "connection",
1281
- "settingsScope"
984
+ "configForms"
1282
985
  ];
1283
986
  const NS = "dsh-browser.card";
1284
987
  function apply(ctx) {
@@ -1287,13 +990,13 @@ window.__ModuleLoader__.load({
1287
990
  zh,
1288
991
  en
1289
992
  }), "dsh-browser: settings dictionaries");
1290
- const controller = new BrowserSettingsController(ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE }));
993
+ const controller = new BrowserSettingsController(ctx.configForms.get(SETTINGS_NAMESPACE));
1291
994
  const assets = new AutomationAssetsController(ctx.connection.rpc);
1292
995
  ctx.effect(() => () => controller.dispose(), "dsh-browser: settings controller");
1293
996
  ctx.effect(() => () => assets.dispose(), "dsh-browser: automation assets controller");
1294
- ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1295
- name: "settings.plugin.item",
1296
- key: SETTINGS_NAMESPACE,
997
+ ctx.effect(() => ctx.configForms.whileServed([SETTINGS_NAMESPACE], () => ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
998
+ name: "plugins.bundle.config",
999
+ key: "@anweat/dsh-browser",
1297
1000
  locale: NS,
1298
1001
  inject: () => {
1299
1002
  const settingsProps = controller.inject();
@@ -1307,7 +1010,7 @@ window.__ModuleLoader__.load({
1307
1010
  }
1308
1011
  };
1309
1012
  }
1310
- }, SettingsCard));
1013
+ }, SettingsCard))), "dsh-browser: settings page");
1311
1014
  }
1312
1015
  //#endregion
1313
1016
  exports.NS = NS;