@anweat/dsh-browser 0.1.8 → 0.1.9

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.
Files changed (55) hide show
  1. package/README.md +56 -6
  2. package/lib/approval-policy.js +19 -0
  3. package/lib/approval-policy.js.map +1 -1
  4. package/lib/auth-profiles.js +10 -1
  5. package/lib/auth-profiles.js.map +1 -1
  6. package/lib/automation-assets-rpc.d.ts +5 -0
  7. package/lib/automation-assets-rpc.js +63 -0
  8. package/lib/automation-assets-rpc.js.map +1 -0
  9. package/lib/automation-assets.d.ts +139 -0
  10. package/lib/automation-assets.js +372 -0
  11. package/lib/automation-assets.js.map +1 -0
  12. package/lib/automation-development.d.ts +13 -0
  13. package/lib/automation-development.js +37 -0
  14. package/lib/automation-development.js.map +1 -0
  15. package/lib/automation-execution.d.ts +14 -0
  16. package/lib/automation-execution.js +55 -0
  17. package/lib/automation-execution.js.map +1 -0
  18. package/lib/browser-service.d.ts +5 -0
  19. package/lib/browser-service.js +66 -14
  20. package/lib/browser-service.js.map +1 -1
  21. package/lib/client/SettingsCard.js +50 -2
  22. package/lib/client/SettingsCard.js.map +1 -1
  23. package/lib/client/automation-assets-client.d.ts +38 -0
  24. package/lib/client/automation-assets-client.js +83 -0
  25. package/lib/client/automation-assets-client.js.map +1 -0
  26. package/lib/client/form.d.ts +1 -1
  27. package/lib/client/form.js +26 -0
  28. package/lib/client/form.js.map +1 -1
  29. package/lib/client/index.d.ts +11 -0
  30. package/lib/client/index.js +11 -1
  31. package/lib/client/index.js.map +1 -1
  32. package/lib/client/locales.d.ts +28 -0
  33. package/lib/client/locales.js +10 -0
  34. package/lib/client/locales.js.map +1 -1
  35. package/lib/client/styles.d.ts +11 -0
  36. package/lib/client/styles.js +4 -1
  37. package/lib/client/styles.js.map +1 -1
  38. package/lib/client.js +533 -4
  39. package/lib/client.js.map +1 -1
  40. package/lib/config.d.ts +4 -0
  41. package/lib/config.js +21 -0
  42. package/lib/config.js.map +1 -1
  43. package/lib/freedom.d.ts +4 -1
  44. package/lib/freedom.js +10 -0
  45. package/lib/freedom.js.map +1 -1
  46. package/lib/index.d.ts +3 -1
  47. package/lib/index.js +7 -2
  48. package/lib/index.js.map +1 -1
  49. package/lib/scripts.d.ts +1 -1
  50. package/lib/scripts.js +3 -2
  51. package/lib/scripts.js.map +1 -1
  52. package/lib/tools.d.ts +2 -1
  53. package/lib/tools.js +149 -12
  54. package/lib/tools.js.map +1 -1
  55. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -61,6 +61,55 @@ window.__ModuleLoader__.load({
61
61
  backoffBaseMs: [1, 6e4],
62
62
  cooldownMs: [100, 3e5]
63
63
  };
64
+ const ASSET_POLICY_KEYS = /* @__PURE__ */ new Set([
65
+ "enabled",
66
+ "directory",
67
+ "persistenceMode",
68
+ "activationMode",
69
+ "minSuccessfulRuns",
70
+ "minDistinctSessions",
71
+ "successWindowDays",
72
+ "minSuccessRate",
73
+ "maxCandidates",
74
+ "candidateTtlDays",
75
+ "maxSuggestionsPerDay",
76
+ "maxDrafts",
77
+ "maxActiveAssets",
78
+ "retrievalTopK",
79
+ "catalogTokenBudget",
80
+ "modelDevelopmentEnabled",
81
+ "maxModelDraftWritesPerSession"
82
+ ]);
83
+ function validAssetPolicy(value) {
84
+ if (Object.keys(value).some((key) => !ASSET_POLICY_KEYS.has(key))) return false;
85
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean") return false;
86
+ if (value.directory !== void 0 && typeof value.directory !== "string") return false;
87
+ if (value.modelDevelopmentEnabled !== void 0 && typeof value.modelDevelopmentEnabled !== "boolean") return false;
88
+ if (value.persistenceMode !== void 0 && ![
89
+ "off",
90
+ "manual",
91
+ "suggest",
92
+ "auto-draft"
93
+ ].includes(String(value.persistenceMode))) return false;
94
+ if (value.activationMode !== void 0 && !["manual", "auto-tested"].includes(String(value.activationMode))) return false;
95
+ return Object.entries(value).every(([key, entry]) => {
96
+ if (![
97
+ "minSuccessfulRuns",
98
+ "minDistinctSessions",
99
+ "successWindowDays",
100
+ "minSuccessRate",
101
+ "maxCandidates",
102
+ "candidateTtlDays",
103
+ "maxSuggestionsPerDay",
104
+ "maxDrafts",
105
+ "maxActiveAssets",
106
+ "retrievalTopK",
107
+ "catalogTokenBudget",
108
+ "maxModelDraftWritesPerSession"
109
+ ].includes(key)) return true;
110
+ return typeof entry === "number" && Number.isFinite(entry) && entry >= 0;
111
+ });
112
+ }
64
113
  function validUsagePolicy(value) {
65
114
  if (Object.keys(value).some((key) => !(key in POLICY_BOUNDS))) return false;
66
115
  return Object.entries(POLICY_BOUNDS).every(([key, [min, max]]) => {
@@ -81,6 +130,7 @@ window.__ModuleLoader__.load({
81
130
  booleanField("headless"),
82
131
  booleanField("opencliEnabled"),
83
132
  jsonField("usagePolicy", validUsagePolicy),
133
+ jsonField("automationAssets", validAssetPolicy),
84
134
  booleanField("autoInstall"),
85
135
  textField("storageStatePath"),
86
136
  jsonField("authProfiles"),
@@ -316,7 +366,18 @@ window.__ModuleLoader__.load({
316
366
  failed: "dsb-failed",
317
367
  actions: "dsb-actions",
318
368
  primary: "dsb-primary",
319
- secondary: "dsb-secondary"
369
+ secondary: "dsb-secondary",
370
+ assetGroup: "dsb-asset-group",
371
+ assetRow: "dsb-asset-row",
372
+ assetToolbar: "dsb-asset-toolbar",
373
+ assetLayout: "dsb-asset-layout",
374
+ assetList: "dsb-asset-list",
375
+ assetItem: "dsb-asset-item",
376
+ assetSelected: "dsb-asset-selected",
377
+ assetTest: "dsb-asset-test",
378
+ assetTestForm: "dsb-asset-test-form",
379
+ assetEditor: "dsb-asset-editor",
380
+ invalidInput: "dsb-invalid-input"
320
381
  };
321
382
  function ensureStyles() {
322
383
  if (document.getElementById("dsh-browser-settings-styles")) return;
@@ -331,7 +392,9 @@ window.__ModuleLoader__.load({
331
392
  .dsb-input{box-sizing:border-box;width:100%;min-width:0;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);font:inherit;font-size:13px;color:var(--dsw-alias-label-primary)}.dsb-input:focus-visible{outline:none;border-color:var(--dsw-alias-brand-primary)}.dsb-input:disabled{opacity:.55}.dsb-invalid .dsb-input{border-color:var(--dsw-alias-label-error)}.dsb-textarea{height:auto;padding:9px 10px;resize:vertical;line-height:1.45}.dsb-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px}.dsb-reset{appearance:none;border:0;background:none;padding:0;color:var(--dsw-alias-brand-primary);font:inherit;font-size:11px;cursor:pointer}
332
393
  .dsb-toggle{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding-top:2px}.dsb-toggle-label{display:flex;align-items:flex-start;gap:9px;cursor:pointer}.dsb-check{width:16px;height:16px;flex:none;margin:2px 0 0;accent-color:var(--dsw-alias-brand-primary)}.dsb-advanced{padding:16px 0;border-top:1px solid var(--dsw-alias-border-l2)}.dsb-advanced>summary{cursor:pointer;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
333
394
  .dsb-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 0 4px;border-top:1px solid var(--dsw-alias-border-l2)}.dsb-status,.dsb-failed{margin:0;font-size:12px}.dsb-status{color:var(--dsw-alias-label-tertiary)}.dsb-failed{color:var(--dsw-alias-label-error)}.dsb-actions{display:flex;gap:8px}.dsb-primary,.dsb-secondary{appearance:none;border-radius:8px;padding:6px 14px;font:inherit;font-size:13px;cursor:pointer}.dsb-primary{border:1px solid transparent;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.dsb-secondary{border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsb-primary:disabled,.dsb-secondary:disabled,.dsb-reset:disabled{opacity:.4;cursor:default}
334
- @media(max-width:720px){.dsb-grid{grid-template-columns:minmax(0,1fr)}.dsb-footer{align-items:stretch;flex-direction:column}.dsb-actions{justify-content:flex-end}}@media(max-width:420px){.dsb-body{margin:0 12px}.dsb-actions{display:grid;grid-template-columns:1fr 1fr}.dsb-primary,.dsb-secondary{width:100%}}
395
+ .dsb-asset-group{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.dsb-asset-group h4{margin:0;font-size:13px}.dsb-asset-row,.dsb-asset-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px}.dsb-asset-row p,.dsb-asset-toolbar p{margin:3px 0 0;font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsb-asset-toolbar{margin-bottom:10px;border:0;padding:0}.dsb-asset-layout{display:grid;grid-template-columns:minmax(180px,.7fr) minmax(0,1.3fr);gap:12px}.dsb-asset-list{display:flex;flex-direction:column;gap:6px;max-height:500px;overflow:auto}.dsb-asset-item{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer}.dsb-asset-item span:first-child{display:flex;min-width:0;flex-direction:column;gap:3px}.dsb-asset-item small,.dsb-asset-test{font-size:10px;color:var(--dsw-alias-label-tertiary)}.dsb-asset-selected{border-color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 8%,var(--dsw-alias-bg-layer-3))}.dsb-asset-editor{display:flex;min-width:0;flex-direction:column;gap:8px}.dsb-asset-editor .dsb-actions{justify-content:flex-end;flex-wrap:wrap}.dsb-invalid-input{border-color:var(--dsw-alias-label-error)}
396
+ .dsb-asset-test-form{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px}.dsb-asset-test-form .dsb-textarea{min-height:64px}
397
+ @media(max-width:720px){.dsb-grid,.dsb-asset-layout{grid-template-columns:minmax(0,1fr)}.dsb-footer,.dsb-asset-row,.dsb-asset-toolbar{align-items:stretch;flex-direction:column}.dsb-actions{justify-content:flex-end}}@media(max-width:420px){.dsb-body{margin:0 12px}.dsb-actions{display:grid;grid-template-columns:1fr 1fr}.dsb-primary,.dsb-secondary{width:100%}}
335
398
  `;
336
399
  document.head.append(style);
337
400
  }
@@ -538,7 +601,10 @@ window.__ModuleLoader__.load({
538
601
  className: styles.sectionHead,
539
602
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("usageHint") })]
540
603
  }),
541
- json("usagePolicy", "usagePolicy", "usagePolicyHint", 10),
604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
605
+ className: styles.grid,
606
+ children: [json("usagePolicy", "usagePolicy", "usagePolicyHint", 10), json("automationAssets", "automationAssets", "automationAssetsHint", 14)]
607
+ }),
542
608
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
543
609
  className: styles.notice,
544
610
  role: "note",
@@ -546,6 +612,7 @@ window.__ModuleLoader__.load({
546
612
  })
547
613
  ]
548
614
  }),
615
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutomationAssetsPanel, { ...props }),
549
616
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
550
617
  className: styles.advanced,
551
618
  children: [
@@ -595,6 +662,254 @@ window.__ModuleLoader__.load({
595
662
  }) : null]
596
663
  });
597
664
  }
665
+ function AutomationAssetsPanel(props) {
666
+ const { t } = props;
667
+ const state = props.useAutomationAssets((snapshot) => snapshot);
668
+ const [draft, setDraft] = (0, react.useState)("");
669
+ const [draftError, setDraftError] = (0, react.useState)(false);
670
+ const [testUrl, setTestUrl] = (0, react.useState)("");
671
+ const [testInputs, setTestInputs] = (0, react.useState)("{}");
672
+ const selected = state.selected;
673
+ (0, react.useEffect)(() => {
674
+ if (selected) setDraft(JSON.stringify(selected, null, 2));
675
+ }, [selected?.id, selected?.revision]);
676
+ const newAsset = (kind) => {
677
+ props.selectAutomationAsset(void 0);
678
+ setDraft(JSON.stringify(kind === "recipe" ? {
679
+ kind,
680
+ name: "New recipe",
681
+ description: "",
682
+ domains: [],
683
+ tags: [],
684
+ inputNames: [],
685
+ recipe: [{
686
+ type: "extract",
687
+ selector: "main",
688
+ mode: "text",
689
+ limit: 20
690
+ }]
691
+ } : {
692
+ kind,
693
+ name: "New userscript",
694
+ description: "",
695
+ domains: [],
696
+ tags: [],
697
+ inputNames: [],
698
+ source: "// ==UserScript==\n// @name New userscript\n// @match https://example.com/*\n// @grant none\n// ==/UserScript==\nreturn { title: document.title }"
699
+ }, null, 2));
700
+ setDraftError(false);
701
+ };
702
+ const save = async () => {
703
+ try {
704
+ const value = JSON.parse(draft);
705
+ if (selected?.id) value.id = selected.id;
706
+ await props.saveAutomationAsset(value);
707
+ setDraftError(false);
708
+ } catch {
709
+ setDraftError(true);
710
+ }
711
+ };
712
+ const runTest = async () => {
713
+ if (!selected || !testUrl.trim()) {
714
+ setDraftError(true);
715
+ return;
716
+ }
717
+ try {
718
+ const inputs = JSON.parse(testInputs);
719
+ await props.testAutomationAsset(selected.id, testUrl.trim(), inputs);
720
+ setDraftError(false);
721
+ } catch {
722
+ setDraftError(true);
723
+ }
724
+ };
725
+ const candidates = state.snapshot?.candidates.filter((candidate) => candidate.suggestedAt && !candidate.dismissedAt) ?? [];
726
+ const assets = state.snapshot?.assets ?? [];
727
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
728
+ className: styles.section,
729
+ "data-dsh-browser-assets": true,
730
+ children: [
731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
732
+ className: styles.sectionHead,
733
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("assetLibrary") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("assetLibraryHint") })]
734
+ }),
735
+ state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
736
+ className: styles.notice,
737
+ children: t("assetLoading")
738
+ }) : null,
739
+ state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
740
+ className: styles.failed,
741
+ role: "alert",
742
+ children: state.error || t("assetFailed")
743
+ }) : null,
744
+ candidates.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
745
+ className: styles.assetGroup,
746
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("assetSuggestions") }), candidates.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
747
+ className: styles.assetRow,
748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: candidate.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
749
+ candidate.domain,
750
+ " · ",
751
+ candidate.successfulRuns,
752
+ " ",
753
+ t("assetRuns"),
754
+ " · ",
755
+ candidate.distinctSessions,
756
+ " ",
757
+ t("assetSessions")
758
+ ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
759
+ className: styles.actions,
760
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
761
+ type: "button",
762
+ className: styles.secondary,
763
+ disabled: state.busy,
764
+ onClick: () => props.dismissAutomationCandidate(candidate.id),
765
+ children: t("assetDismiss")
766
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
767
+ type: "button",
768
+ className: styles.primary,
769
+ disabled: state.busy,
770
+ onClick: () => props.summarizeAutomationCandidate(candidate.id),
771
+ children: t("assetSummarize")
772
+ })]
773
+ })]
774
+ }, candidate.id))]
775
+ }) : null,
776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
777
+ className: styles.assetToolbar,
778
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("assetScripts") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
779
+ className: styles.hint,
780
+ children: t("assetScriptsHint")
781
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
782
+ className: styles.actions,
783
+ children: [
784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
785
+ type: "button",
786
+ className: styles.secondary,
787
+ onClick: () => newAsset("recipe"),
788
+ children: t("assetNewRecipe")
789
+ }),
790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
791
+ type: "button",
792
+ className: styles.secondary,
793
+ onClick: () => newAsset("userscript"),
794
+ children: t("assetNewScript")
795
+ }),
796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
797
+ type: "button",
798
+ className: styles.secondary,
799
+ onClick: props.refreshAutomationAssets,
800
+ children: t("assetRefresh")
801
+ })
802
+ ]
803
+ })]
804
+ }),
805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
806
+ className: styles.assetLayout,
807
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
808
+ className: styles.assetList,
809
+ children: assets.length ? assets.map((asset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
810
+ type: "button",
811
+ className: `${styles.assetItem} ${selected?.id === asset.id ? styles.assetSelected : ""}`,
812
+ onClick: () => props.selectAutomationAsset(asset.id),
813
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: asset.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("small", { children: [
814
+ asset.kind,
815
+ " · ",
816
+ asset.status,
817
+ " · r",
818
+ asset.revision
819
+ ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
820
+ className: styles.assetTest,
821
+ children: asset.testStatus
822
+ })]
823
+ }, asset.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
824
+ className: styles.hint,
825
+ children: t("assetEmpty")
826
+ })
827
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
828
+ className: styles.assetEditor,
829
+ children: [
830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
831
+ className: styles.label,
832
+ htmlFor: "dsh-browser-asset-editor",
833
+ children: t("assetEditor")
834
+ }),
835
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
836
+ id: "dsh-browser-asset-editor",
837
+ className: `${styles.input} ${styles.textarea} ${styles.code} ${draftError ? styles.invalidInput : ""}`,
838
+ rows: 18,
839
+ value: draft,
840
+ spellCheck: false,
841
+ placeholder: t("assetEditorHint"),
842
+ onChange: (event) => {
843
+ setDraft(event.currentTarget.value);
844
+ setDraftError(false);
845
+ }
846
+ }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
848
+ className: styles.hint,
849
+ children: draftError ? t("assetInvalid") : t("assetSourceBoundary")
850
+ }),
851
+ selected?.status === "draft" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
852
+ className: styles.assetTestForm,
853
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
854
+ className: styles.input,
855
+ value: testUrl,
856
+ placeholder: t("assetTestUrl"),
857
+ onChange: (event) => setTestUrl(event.currentTarget.value)
858
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
859
+ className: `${styles.input} ${styles.textarea} ${styles.code}`,
860
+ rows: 3,
861
+ value: testInputs,
862
+ spellCheck: false,
863
+ "aria-label": t("assetTestInputs"),
864
+ onChange: (event) => setTestInputs(event.currentTarget.value)
865
+ })]
866
+ }) : null,
867
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
868
+ className: styles.actions,
869
+ children: [
870
+ selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
871
+ type: "button",
872
+ className: styles.secondary,
873
+ disabled: state.busy,
874
+ onClick: () => props.validateAutomationAsset(selected.id),
875
+ children: t("assetValidate")
876
+ }) : null,
877
+ selected?.status === "draft" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
878
+ type: "button",
879
+ className: styles.secondary,
880
+ disabled: state.busy || !testUrl.trim(),
881
+ onClick: () => void runTest(),
882
+ children: t("assetTest")
883
+ }) : null,
884
+ selected?.status === "draft" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
885
+ type: "button",
886
+ className: styles.secondary,
887
+ disabled: state.busy || selected.testStatus !== "passed",
888
+ onClick: () => props.setAutomationAssetStatus(selected.id, "active"),
889
+ children: t("assetActivate")
890
+ }) : null,
891
+ selected && selected.status !== "archived" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
892
+ type: "button",
893
+ className: styles.secondary,
894
+ disabled: state.busy,
895
+ onClick: () => props.setAutomationAssetStatus(selected.id, "archived"),
896
+ children: t("assetArchive")
897
+ }) : null,
898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
899
+ type: "button",
900
+ className: styles.primary,
901
+ disabled: state.busy || !draft || selected?.status === "active",
902
+ onClick: () => void save(),
903
+ children: t("assetSaveDraft")
904
+ })
905
+ ]
906
+ })
907
+ ]
908
+ })]
909
+ })
910
+ ]
911
+ });
912
+ }
598
913
  //#endregion
599
914
  //#region src/client/locales.ts
600
915
  const zh = {
@@ -626,6 +941,34 @@ window.__ModuleLoader__.load({
626
941
  opencliEnabledHint: "站点 adapter 和 Chrome Browser Bridge 总开关。",
627
942
  usagePolicy: "调用缓冲 JSON",
628
943
  usagePolicyHint: "minDelayMs、maxConcurrency、burst、maxPagesPerRun、maxDepth、retryLimit、backoffBaseMs、cooldownMs。",
944
+ automationAssets: "自动化资产策略 JSON",
945
+ automationAssetsHint: "候选阈值、持久化模式、激活模式、数量与上下文预算。",
946
+ assetLibrary: "可复用自动化资产(实验性)",
947
+ assetLibraryHint: "实验功能:候选先提示是否总结;草稿真实回放后再手动激活。源码仅在点选编辑时读取。",
948
+ assetLoading: "正在读取本地自动化资产…",
949
+ assetFailed: "自动化资产读取失败。",
950
+ assetSuggestions: "建议总结",
951
+ assetRuns: "次成功",
952
+ assetSessions: "个会话",
953
+ assetDismiss: "暂不总结",
954
+ assetSummarize: "总结为草稿",
955
+ assetScripts: "脚本与 recipe",
956
+ assetScriptsHint: "模型只能检索已激活资产的有界摘要。",
957
+ assetNewRecipe: "新建 recipe",
958
+ assetNewScript: "新建油猴脚本",
959
+ assetRefresh: "刷新",
960
+ assetEmpty: "还没有资产。",
961
+ assetEditor: "资产编辑器(JSON)",
962
+ assetEditorHint: "选择资产或新建草稿。",
963
+ assetInvalid: "JSON、测试 URL 或输入无效。",
964
+ assetSourceBoundary: "不要保存 cookie、token、密码、完整页面内容或聊天记录。",
965
+ assetValidate: "静态校验",
966
+ assetTest: "真实回放",
967
+ assetTestUrl: "测试 URL(必须命中允许域名)",
968
+ assetTestInputs: "测试输入 JSON",
969
+ assetActivate: "激活",
970
+ assetArchive: "归档",
971
+ assetSaveDraft: "保存草稿",
629
972
  autoInstall: "缺失时自动安装 Chromium",
630
973
  autoInstallHint: "可能触发较大下载,日常建议关闭并显式调用 browser_install。",
631
974
  storageStatePath: "全局 storageState 路径",
@@ -683,6 +1026,34 @@ window.__ModuleLoader__.load({
683
1026
  opencliEnabledHint: "Master switch for site adapters and the Chrome Browser Bridge.",
684
1027
  usagePolicy: "Usage buffer JSON",
685
1028
  usagePolicyHint: "minDelayMs, maxConcurrency, burst, maxPagesPerRun, maxDepth, retryLimit, backoffBaseMs, cooldownMs.",
1029
+ automationAssets: "Automation asset policy JSON",
1030
+ automationAssetsHint: "Candidate thresholds, persistence, activation, count, and context budgets.",
1031
+ assetLibrary: "Reusable automation assets (Experimental)",
1032
+ assetLibraryHint: "Experimental: candidates ask before summarization; drafts require runtime replay before manual activation. Source loads only when selected.",
1033
+ assetLoading: "Loading local automation assets…",
1034
+ assetFailed: "Failed to load automation assets.",
1035
+ assetSuggestions: "Summarization suggestions",
1036
+ assetRuns: "successful runs",
1037
+ assetSessions: "sessions",
1038
+ assetDismiss: "Not now",
1039
+ assetSummarize: "Summarize to draft",
1040
+ assetScripts: "Scripts and recipes",
1041
+ assetScriptsHint: "The model can retrieve only bounded summaries of active assets.",
1042
+ assetNewRecipe: "New recipe",
1043
+ assetNewScript: "New userscript",
1044
+ assetRefresh: "Refresh",
1045
+ assetEmpty: "No assets yet.",
1046
+ assetEditor: "Asset editor (JSON)",
1047
+ assetEditorHint: "Select an asset or create a draft.",
1048
+ assetInvalid: "Invalid JSON, test URL, or inputs.",
1049
+ assetSourceBoundary: "Do not store cookies, tokens, passwords, full page content, or conversation transcripts.",
1050
+ assetValidate: "Static validate",
1051
+ assetTest: "Runtime replay",
1052
+ assetTestUrl: "Test URL (must match an allowed domain)",
1053
+ assetTestInputs: "Test inputs JSON",
1054
+ assetActivate: "Activate",
1055
+ assetArchive: "Archive",
1056
+ assetSaveDraft: "Save draft",
686
1057
  autoInstall: "Auto-install Chromium when missing",
687
1058
  autoInstallHint: "May download a large binary; explicit browser_install is safer for daily use.",
688
1059
  storageStatePath: "Global storageState path",
@@ -716,6 +1087,151 @@ window.__ModuleLoader__.load({
716
1087
  /** Settings namespace used by both the Host registry and the card slot key. */
717
1088
  const SETTINGS_NAMESPACE = "browser";
718
1089
  //#endregion
1090
+ //#region src/client/automation-assets-client.ts
1091
+ function localStore(initial) {
1092
+ let snapshot = initial;
1093
+ const listeners = /* @__PURE__ */ new Set();
1094
+ return {
1095
+ getSnapshot: () => snapshot,
1096
+ subscribe(listener) {
1097
+ listeners.add(listener);
1098
+ return () => {
1099
+ listeners.delete(listener);
1100
+ };
1101
+ },
1102
+ set(next) {
1103
+ snapshot = next;
1104
+ for (const listener of listeners) listener();
1105
+ },
1106
+ update(updater) {
1107
+ const draft = structuredClone(snapshot);
1108
+ updater(draft);
1109
+ snapshot = draft;
1110
+ for (const listener of listeners) listener();
1111
+ }
1112
+ };
1113
+ }
1114
+ var AutomationAssetsController = class {
1115
+ rpc;
1116
+ store = localStore({
1117
+ loading: true,
1118
+ busy: false,
1119
+ failed: false
1120
+ });
1121
+ disposed = false;
1122
+ constructor(rpc) {
1123
+ this.rpc = rpc;
1124
+ this.refresh();
1125
+ }
1126
+ inject() {
1127
+ return {
1128
+ hooks: { automationAssets: this.store },
1129
+ refreshAutomationAssets: () => {
1130
+ this.refresh();
1131
+ },
1132
+ selectAutomationAsset: (id) => {
1133
+ this.select(id);
1134
+ },
1135
+ saveAutomationAsset: (asset) => this.mutate("save", { asset }),
1136
+ summarizeAutomationCandidate: (id) => this.mutate("summarize", { id }),
1137
+ dismissAutomationCandidate: (id) => this.mutate("dismiss", { id }),
1138
+ validateAutomationAsset: (id) => this.mutate("validate", { id }),
1139
+ testAutomationAsset: (id, url, inputs) => this.mutate("test", {
1140
+ id,
1141
+ url,
1142
+ inputs
1143
+ }),
1144
+ setAutomationAssetStatus: (id, status) => this.mutate("status", {
1145
+ id,
1146
+ status
1147
+ })
1148
+ };
1149
+ }
1150
+ snapshot() {
1151
+ return this.store.getSnapshot();
1152
+ }
1153
+ dispose() {
1154
+ this.disposed = true;
1155
+ }
1156
+ async refresh() {
1157
+ this.publish({
1158
+ ...this.store.getSnapshot(),
1159
+ loading: true,
1160
+ failed: false,
1161
+ error: void 0
1162
+ });
1163
+ try {
1164
+ const snapshot = await this.call("snapshot", {});
1165
+ this.publish({
1166
+ ...this.store.getSnapshot(),
1167
+ loading: false,
1168
+ snapshot
1169
+ });
1170
+ } catch (error) {
1171
+ this.fail(error);
1172
+ }
1173
+ }
1174
+ async select(id) {
1175
+ if (!id) {
1176
+ this.publish({
1177
+ ...this.store.getSnapshot(),
1178
+ selected: void 0
1179
+ });
1180
+ return;
1181
+ }
1182
+ try {
1183
+ const selected = await this.call("get", { id });
1184
+ this.publish({
1185
+ ...this.store.getSnapshot(),
1186
+ selected: selected ?? void 0,
1187
+ failed: false,
1188
+ error: void 0
1189
+ });
1190
+ } catch (error) {
1191
+ this.fail(error);
1192
+ }
1193
+ }
1194
+ async mutate(endpoint, payload) {
1195
+ this.publish({
1196
+ ...this.store.getSnapshot(),
1197
+ busy: true,
1198
+ failed: false,
1199
+ error: void 0
1200
+ });
1201
+ try {
1202
+ const value = await this.call(endpoint, payload);
1203
+ const selected = value && typeof value === "object" && "id" in value ? value : this.store.getSnapshot().selected;
1204
+ const snapshot = await this.call("snapshot", {});
1205
+ this.publish({
1206
+ loading: false,
1207
+ busy: false,
1208
+ failed: false,
1209
+ snapshot,
1210
+ ...selected ? { selected } : {}
1211
+ });
1212
+ } catch (error) {
1213
+ this.fail(error);
1214
+ }
1215
+ }
1216
+ async call(endpoint, payload) {
1217
+ const result = await this.rpc.call("/dsh-browser-assets", endpoint, payload);
1218
+ if (!result.ok) throw new Error(result.error.message);
1219
+ return result.value;
1220
+ }
1221
+ fail(error) {
1222
+ this.publish({
1223
+ ...this.store.getSnapshot(),
1224
+ loading: false,
1225
+ busy: false,
1226
+ failed: true,
1227
+ error: String(error instanceof Error ? error.message : error).slice(0, 300)
1228
+ });
1229
+ }
1230
+ publish(state) {
1231
+ if (!this.disposed) this.store.set(state);
1232
+ }
1233
+ };
1234
+ //#endregion
719
1235
  //#region src/client/index.ts
720
1236
  const name = "dsh-browser-client";
721
1237
  const inject = [
@@ -732,12 +1248,25 @@ window.__ModuleLoader__.load({
732
1248
  en
733
1249
  }), "dsh-browser: settings dictionaries");
734
1250
  const controller = new BrowserSettingsController(ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE }));
1251
+ const assets = new AutomationAssetsController(ctx.connection.rpc);
735
1252
  ctx.effect(() => () => controller.dispose(), "dsh-browser: settings controller");
1253
+ ctx.effect(() => () => assets.dispose(), "dsh-browser: automation assets controller");
736
1254
  ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
737
1255
  name: "settings.plugin.item",
738
1256
  key: SETTINGS_NAMESPACE,
739
1257
  locale: NS,
740
- inject: () => controller.inject()
1258
+ inject: () => {
1259
+ const settingsProps = controller.inject();
1260
+ const assetProps = assets.inject();
1261
+ return {
1262
+ ...settingsProps,
1263
+ ...assetProps,
1264
+ hooks: {
1265
+ ...settingsProps.hooks,
1266
+ ...assetProps.hooks
1267
+ }
1268
+ };
1269
+ }
741
1270
  }, SettingsCard));
742
1271
  }
743
1272
  //#endregion