dsh-remember 0.2.0 → 0.3.1
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/CHANGELOG.md +53 -0
- package/icon.svg +13 -0
- package/lib/client.js +404 -86
- package/lib/client.js.map +1 -1
- package/lib/index.js +671 -593
- package/lib/typert.host.js +198 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +16 -6
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versions follow [SemVer](https://semver.org/). Release artifacts: [GitHub Releases](https://github.com/Arborsm/dsh-remember/releases).
|
|
4
|
+
|
|
5
|
+
## [0.3.1] - 2026-11-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `memory_export` / `memory_import` declare plain object-rooted JSON Schema parameters; the author-facing shorthand went to the provider verbatim as `type: null`, which made every model request fail with `Invalid schema for function 'memory_export'` ([#1](https://github.com/Arborsm/dsh-remember/issues/1))
|
|
10
|
+
- Dropped the build-time `@deepseek-ai/dsh-tools` alias and its stub, so no tool helper is bundled
|
|
11
|
+
|
|
12
|
+
## [0.3.0] - 2026-10-31
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Plugin-page registration (`plugins.bundle.config`) so the memory page opens from Plugins → dsh-remember, next to the existing settings section
|
|
17
|
+
- `icon.svg` and `locale/{en,zh}.json` plugin metadata: the plugin manager shows the localized title and one-line description on the card and page
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Requires dsh `>=0.2.0-rc.1 <0.3.0-0` (`engines.dsh`); the 0.1.x settings-provider API is gone
|
|
22
|
+
- Host and browser halves now talk over a single `memory` remote service exposed through a hand-written `./typert` manifest and mounted with `ctx.remote.$mount`; the removed `memory-status` / `memory-commands` / `memory-view` settings namespaces are gone
|
|
23
|
+
- Settings-page edits persist to `~/.dsh-memory/config.json` as overrides on the loader config, instead of rewriting the profile patch
|
|
24
|
+
- Export returns the bundle over the remote call and the browser saves it with the same file-picker flow
|
|
25
|
+
- Activation looks `remote.session` up through `ctx.get` and downgrades gracefully when the model catalog is unavailable, so an optional client service can never abort web boot
|
|
26
|
+
- Phase-1/phase-2 sub-agent messages use the v4 producer-owned source kind `dsh-remember`; `plugin` is rejected by session format v4
|
|
27
|
+
|
|
28
|
+
### Removed
|
|
29
|
+
|
|
30
|
+
- `src/settings.ts` (settings provider surface and the four namespaces)
|
|
31
|
+
|
|
32
|
+
## [0.2.0] - 2026-09-11
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Renamed the project and npm package to `dsh-remember`
|
|
37
|
+
- Viewer: records tab merged into the overview (searchable table, most-cited top 5, registry size card); the overview no longer duplicates the summary text
|
|
38
|
+
- Settings: import/export use the native file picker / save dialog instead of typed paths
|
|
39
|
+
- Bundle extension is now plain `.dshmem`
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- CI workflow (build / typecheck / test)
|
|
44
|
+
- npm trusted-publisher pipeline (OIDC, staged publishing) — releases stage automatically, approved on npmjs.com
|
|
45
|
+
|
|
46
|
+
## [0.1.0] - 2026-09-10
|
|
47
|
+
|
|
48
|
+
First public release: background extraction from idle sessions, incremental consolidation into a plain-Markdown memory workspace, summary injection on every prompt, citation feedback loop, one-click translation with segment caching, gzip bundle export/import, sidebar status panel, memory viewer, and a settings page.
|
|
49
|
+
|
|
50
|
+
[0.3.1]: https://github.com/Arborsm/dsh-remember/compare/v0.3.0...v0.3.1
|
|
51
|
+
[0.3.0]: https://github.com/Arborsm/dsh-remember/compare/v0.2.0...v0.3.0
|
|
52
|
+
[0.2.0]: https://github.com/Arborsm/dsh-remember/compare/v0.1.0...v0.2.0
|
|
53
|
+
[0.1.0]: https://github.com/Arborsm/dsh-remember/releases/tag/v0.1.0
|
package/icon.svg
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="dshm" x1="10" y1="12" x2="54" y2="52" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0" stop-color="#3F6DFF"/>
|
|
5
|
+
<stop offset="1" stop-color="#7A5CFF"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="10" y="13" width="44" height="11" rx="5.5" fill="url(#dshm)"/>
|
|
9
|
+
<rect x="10" y="28" width="34" height="11" rx="5.5" fill="url(#dshm)" opacity="0.85"/>
|
|
10
|
+
<rect x="10" y="43" width="22" height="11" rx="5.5" fill="url(#dshm)" opacity="0.6"/>
|
|
11
|
+
<circle cx="43" cy="48.5" r="5.5" fill="url(#dshm)"/>
|
|
12
|
+
<circle cx="43" cy="48.5" r="2" fill="#fff" opacity="0.92"/>
|
|
13
|
+
</svg>
|
package/lib/client.js
CHANGED
|
@@ -583,7 +583,7 @@ function ModelSelect(props) {
|
|
|
583
583
|
style: inputStyle(false)
|
|
584
584
|
}, options);
|
|
585
585
|
}
|
|
586
|
-
/** Upper bound for an import traveling base64 through the
|
|
586
|
+
/** Upper bound for an import traveling base64 through the host remote. */
|
|
587
587
|
const IMPORT_LIMIT_BYTES = 32 * 1024 * 1024;
|
|
588
588
|
/** Save bundle bytes: native save dialog where available, download fallback. */
|
|
589
589
|
async function saveBundleFile(name$1, base64) {
|
|
@@ -607,32 +607,45 @@ async function saveBundleFile(name$1, base64) {
|
|
|
607
607
|
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
608
608
|
}
|
|
609
609
|
/**
|
|
610
|
-
* Import/export entry: export
|
|
611
|
-
* the browser's file dialog; import reads a picked file and
|
|
612
|
-
*
|
|
613
|
-
*
|
|
610
|
+
* Import/export entry: export returns the bundle from the host and saves it
|
|
611
|
+
* through the browser's file dialog; import reads a picked file and hands the
|
|
612
|
+
* bytes back to the host. Outcomes come back as `transfer:`-prefixed status
|
|
613
|
+
* detail lines.
|
|
614
614
|
*/
|
|
615
615
|
function TransferSection(props) {
|
|
616
616
|
const { t, commands } = props;
|
|
617
617
|
const [requested, setRequested] = (0, react.useState)(null);
|
|
618
618
|
const [localError, setLocalError] = (0, react.useState)("");
|
|
619
619
|
const fileInput = (0, react.useRef)(null);
|
|
620
|
-
const
|
|
621
|
-
const send = (action, path = "", payload = "") => {
|
|
620
|
+
const exportBundle = async () => {
|
|
622
621
|
if (commands == null) return;
|
|
623
|
-
(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
await commands.
|
|
627
|
-
|
|
628
|
-
|
|
622
|
+
setLocalError("");
|
|
623
|
+
setRequested("export");
|
|
624
|
+
try {
|
|
625
|
+
const result = await commands.run({ action: "export" });
|
|
626
|
+
if (result.bundle != null && result.bundle.data !== "") {
|
|
627
|
+
await saveBundleFile(result.bundle.name, result.bundle.data);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (!result.ok) setLocalError(result.detail);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
setLocalError(String(error));
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
const importBundle = async (payload) => {
|
|
636
|
+
if (commands == null) return;
|
|
637
|
+
setLocalError("");
|
|
638
|
+
setRequested("import");
|
|
639
|
+
try {
|
|
640
|
+
const result = await commands.run({
|
|
641
|
+
action: "import",
|
|
642
|
+
payload
|
|
643
|
+
});
|
|
644
|
+
if (!result.ok) setLocalError(result.detail);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
setLocalError(String(error));
|
|
647
|
+
}
|
|
629
648
|
};
|
|
630
|
-
(0, react.useEffect)(() => {
|
|
631
|
-
const bundle = props.exportBundle;
|
|
632
|
-
if (bundle == null || bundle.data === "" || bundle.at <= lastExportAt.current) return void 0;
|
|
633
|
-
lastExportAt.current = bundle.at;
|
|
634
|
-
saveBundleFile(bundle.name, bundle.data).finally(() => send("export-done"));
|
|
635
|
-
}, [props.exportBundle?.at ?? 0]);
|
|
636
649
|
const pickImport = (event) => {
|
|
637
650
|
const file = event.target.files?.[0];
|
|
638
651
|
event.target.value = "";
|
|
@@ -641,12 +654,10 @@ function TransferSection(props) {
|
|
|
641
654
|
setLocalError(t("transfer_tooBig"));
|
|
642
655
|
return;
|
|
643
656
|
}
|
|
644
|
-
setLocalError("");
|
|
645
|
-
setRequested("import");
|
|
646
657
|
const reader = new FileReader();
|
|
647
658
|
reader.onload = () => {
|
|
648
659
|
const result = String(reader.result ?? "");
|
|
649
|
-
|
|
660
|
+
importBundle(result.slice(result.indexOf(",") + 1));
|
|
650
661
|
};
|
|
651
662
|
reader.onerror = () => setLocalError(t("transfer_readFailed"));
|
|
652
663
|
reader.readAsDataURL(file);
|
|
@@ -666,7 +677,7 @@ function TransferSection(props) {
|
|
|
666
677
|
onClick: () => {
|
|
667
678
|
setLocalError("");
|
|
668
679
|
setRequested(action);
|
|
669
|
-
if (action === "export")
|
|
680
|
+
if (action === "export") exportBundle();
|
|
670
681
|
else fileInput.current?.click();
|
|
671
682
|
},
|
|
672
683
|
style: primaryButtonStyle(commands == null)
|
|
@@ -738,9 +749,11 @@ function hintText(t, field) {
|
|
|
738
749
|
return hint === `hint_${field}` ? "" : hint;
|
|
739
750
|
}
|
|
740
751
|
/**
|
|
741
|
-
* The dedicated Memory settings page
|
|
742
|
-
*
|
|
743
|
-
*
|
|
752
|
+
* The dedicated Memory settings page: live status banner, divided-list sections
|
|
753
|
+
* with catalog-backed model pickers and toggle switches, transfer actions, and a
|
|
754
|
+
* collapsed advanced section. Rendered both as its own `settings.section` seat
|
|
755
|
+
* and inside the plugin page's `plugins.bundle.config` seat, where `embedded`
|
|
756
|
+
* drops the heading the page already supplies.
|
|
744
757
|
* Render failures surface inline instead of blanking the panel.
|
|
745
758
|
*/
|
|
746
759
|
function MemorySettingsPage(props) {
|
|
@@ -770,13 +783,6 @@ function renderMemoryPage(props) {
|
|
|
770
783
|
} }, t("unavailable"));
|
|
771
784
|
const disabled = !state.writable;
|
|
772
785
|
const status = statusSnapshot?.status === "ready" ? statusSnapshot.value : void 0;
|
|
773
|
-
const viewSnapshot = props.useMemoryView != null ? props.useMemoryView((value) => value) : void 0;
|
|
774
|
-
const viewValue = viewSnapshot?.status === "ready" ? viewSnapshot.value : void 0;
|
|
775
|
-
const exportBundle = viewValue != null && (viewValue.exportData ?? "") !== "" ? {
|
|
776
|
-
name: viewValue.exportName ?? "memory.dshmem",
|
|
777
|
-
data: viewValue.exportData,
|
|
778
|
-
at: viewValue.exportAt ?? 0
|
|
779
|
-
} : void 0;
|
|
780
786
|
const active = status?.phase === "phase1" || status?.phase === "phase2";
|
|
781
787
|
const statusLabel = active ? status?.phase === "phase1" ? t("phase1") : t("phase2") : t("statusIdle");
|
|
782
788
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -823,15 +829,16 @@ function renderMemoryPage(props) {
|
|
|
823
829
|
onReset: () => props.resetField(field.spec.field)
|
|
824
830
|
}))));
|
|
825
831
|
};
|
|
832
|
+
const embedded = props.embedded === true;
|
|
826
833
|
return (0, react.createElement)("div", { style: {
|
|
827
|
-
maxWidth: 640,
|
|
828
|
-
paddingBottom: 32
|
|
829
|
-
} }, (0, react.createElement)("h2", { style: {
|
|
834
|
+
maxWidth: embedded ? void 0 : 640,
|
|
835
|
+
paddingBottom: embedded ? 0 : 32
|
|
836
|
+
} }, embedded ? null : (0, react.createElement)("h2", { style: {
|
|
830
837
|
fontSize: 19,
|
|
831
838
|
fontWeight: 650,
|
|
832
839
|
margin: "0 0 4px",
|
|
833
840
|
letterSpacing: .2
|
|
834
|
-
} }, t("title")), (0, react.createElement)("p", { style: {
|
|
841
|
+
} }, t("title")), embedded ? null : (0, react.createElement)("p", { style: {
|
|
835
842
|
fontSize: 13,
|
|
836
843
|
opacity: .6,
|
|
837
844
|
margin: "0 0 18px",
|
|
@@ -870,8 +877,7 @@ function renderMemoryPage(props) {
|
|
|
870
877
|
} }, status?.updatedAt ? new Date(status.updatedAt).toLocaleTimeString() : "")), fieldSection("general"), fieldSection("models"), fieldSection("schedule"), fieldSection("recall"), (0, react.createElement)(TransferSection, {
|
|
871
878
|
t,
|
|
872
879
|
commands: props.commands,
|
|
873
|
-
statusDetail: status?.detail
|
|
874
|
-
exportBundle
|
|
880
|
+
statusDetail: status?.detail
|
|
875
881
|
}), (0, react.createElement)(AdvancedSection, {
|
|
876
882
|
t,
|
|
877
883
|
fields: groups.get("advanced") ?? [],
|
|
@@ -899,6 +905,293 @@ function renderMemoryPage(props) {
|
|
|
899
905
|
} }, t("readOnly")) : null));
|
|
900
906
|
}
|
|
901
907
|
|
|
908
|
+
//#endregion
|
|
909
|
+
//#region src/client/remote.ts
|
|
910
|
+
/**
|
|
911
|
+
* Result codecs exist only to satisfy the strict-codec contract; the gateway
|
|
912
|
+
* already delivered parsed JSON, so the browser side does no re-parsing.
|
|
913
|
+
*/
|
|
914
|
+
const passthrough = (name$1) => ({
|
|
915
|
+
mode: "strict",
|
|
916
|
+
typeSymbol: `dsh-remember#${name$1}`,
|
|
917
|
+
create: () => ({ parse: (value) => value })
|
|
918
|
+
});
|
|
919
|
+
const jsonParameter = (name$1, typeName) => ({
|
|
920
|
+
name: name$1,
|
|
921
|
+
wire: name$1,
|
|
922
|
+
source: "json",
|
|
923
|
+
codec: passthrough(typeName)
|
|
924
|
+
});
|
|
925
|
+
const descriptor = (method, parameters, resultName) => ({
|
|
926
|
+
id: `dsh-remember#memory/${method}`,
|
|
927
|
+
service: "memory",
|
|
928
|
+
namespace: "memory",
|
|
929
|
+
method,
|
|
930
|
+
invocation: { kind: "direct" },
|
|
931
|
+
parameters,
|
|
932
|
+
result: passthrough(resultName)
|
|
933
|
+
});
|
|
934
|
+
/** Mirrors MEMORY_INVOCATIONS in src/typert.ts (method names and wires must match). */
|
|
935
|
+
const CONTRIBUTION = {
|
|
936
|
+
package: "dsh-remember",
|
|
937
|
+
descriptors: [
|
|
938
|
+
descriptor("getStatus", [], "MemoryStatus"),
|
|
939
|
+
descriptor("getConfig", [], "ConfigSnapshot"),
|
|
940
|
+
descriptor("updateConfig", [jsonParameter("patch", "ConfigPatch")], "ConfigSnapshot"),
|
|
941
|
+
descriptor("unsetConfig", [jsonParameter("field", "ConfigField")], "ConfigSnapshot"),
|
|
942
|
+
descriptor("getView", [], "MemoryView"),
|
|
943
|
+
descriptor("runCommand", [jsonParameter("request", "CommandRequest")], "CommandResult")
|
|
944
|
+
]
|
|
945
|
+
};
|
|
946
|
+
const STATUS_POLL_MS = 2500;
|
|
947
|
+
const CONFIG_POLL_MS = 15e3;
|
|
948
|
+
const VIEW_POLL_MS = 5e3;
|
|
949
|
+
/** A snapshot store with change notification, in the Shape components expect. */
|
|
950
|
+
var BridgeStore = class {
|
|
951
|
+
snapshot;
|
|
952
|
+
listeners = /* @__PURE__ */ new Set();
|
|
953
|
+
/** Fired when the subscriber count crosses zero; used for lazy polling. */
|
|
954
|
+
onActiveChange;
|
|
955
|
+
constructor(initial) {
|
|
956
|
+
this.snapshot = initial;
|
|
957
|
+
}
|
|
958
|
+
getSnapshot = () => this.snapshot;
|
|
959
|
+
subscribe = (listener) => {
|
|
960
|
+
this.listeners.add(listener);
|
|
961
|
+
if (this.listeners.size === 1) this.onActiveChange?.(true);
|
|
962
|
+
return () => {
|
|
963
|
+
this.listeners.delete(listener);
|
|
964
|
+
if (this.listeners.size === 0) this.onActiveChange?.(false);
|
|
965
|
+
};
|
|
966
|
+
};
|
|
967
|
+
set(snapshot) {
|
|
968
|
+
this.snapshot = snapshot;
|
|
969
|
+
for (const listener of this.listeners) listener();
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
const loadingSnapshot = () => ({
|
|
973
|
+
status: "loading",
|
|
974
|
+
value: void 0,
|
|
975
|
+
base: void 0,
|
|
976
|
+
user: void 0,
|
|
977
|
+
revision: void 0,
|
|
978
|
+
writable: false,
|
|
979
|
+
mode: "host"
|
|
980
|
+
});
|
|
981
|
+
const unavailableSnapshot = () => ({
|
|
982
|
+
status: "unavailable",
|
|
983
|
+
value: void 0,
|
|
984
|
+
base: void 0,
|
|
985
|
+
user: void 0,
|
|
986
|
+
revision: void 0,
|
|
987
|
+
writable: false,
|
|
988
|
+
mode: "host"
|
|
989
|
+
});
|
|
990
|
+
function failureDetail(error) {
|
|
991
|
+
if (error == null) return "memory host call failed";
|
|
992
|
+
return `${error.code ?? "error"}: ${error.message ?? "unknown failure"}`;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Owns the mounted namespace plus the snapshot stores. Created synchronously
|
|
996
|
+
* (slots need it immediately) and attached once `$mount` resolves.
|
|
997
|
+
*/
|
|
998
|
+
var MemoryBridge = class {
|
|
999
|
+
remote = null;
|
|
1000
|
+
statusTimer;
|
|
1001
|
+
configTimer;
|
|
1002
|
+
viewTimer;
|
|
1003
|
+
configStore = new BridgeStore(loadingSnapshot());
|
|
1004
|
+
statusStore = new BridgeStore(loadingSnapshot());
|
|
1005
|
+
viewStore = new BridgeStore(loadingSnapshot());
|
|
1006
|
+
/** Settings page scope: `set`/`unset` persist config overrides on the host. */
|
|
1007
|
+
configScope = {
|
|
1008
|
+
getSnapshot: () => this.configStore.getSnapshot(),
|
|
1009
|
+
subscribe: (listener) => this.configStore.subscribe(listener),
|
|
1010
|
+
set: async (field, value) => {
|
|
1011
|
+
await this.updateConfig({ [field]: value });
|
|
1012
|
+
},
|
|
1013
|
+
unset: async (field) => {
|
|
1014
|
+
await this.unsetConfig(field);
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
/** Selector-hook source for the sidebar pill and the status banner. */
|
|
1018
|
+
statusSource = {
|
|
1019
|
+
getSnapshot: () => this.statusStore.getSnapshot(),
|
|
1020
|
+
subscribe: (listener) => this.statusStore.subscribe(listener)
|
|
1021
|
+
};
|
|
1022
|
+
/** Selector-hook source for the viewer page (polls only while mounted). */
|
|
1023
|
+
viewSource = {
|
|
1024
|
+
getSnapshot: () => this.viewStore.getSnapshot(),
|
|
1025
|
+
subscribe: (listener) => this.viewStore.subscribe(listener)
|
|
1026
|
+
};
|
|
1027
|
+
/** The single action runner every widget uses. */
|
|
1028
|
+
commands = { run: (request) => this.runCommand(request) };
|
|
1029
|
+
constructor() {
|
|
1030
|
+
this.viewStore.onActiveChange = (active) => {
|
|
1031
|
+
if (active) {
|
|
1032
|
+
this.refreshView();
|
|
1033
|
+
this.viewTimer = setInterval(() => {
|
|
1034
|
+
this.refreshView();
|
|
1035
|
+
}, VIEW_POLL_MS);
|
|
1036
|
+
} else {
|
|
1037
|
+
this.stopTimer(this.viewTimer);
|
|
1038
|
+
this.viewTimer = void 0;
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
attach(remote) {
|
|
1043
|
+
this.remote = remote;
|
|
1044
|
+
this.refreshStatus();
|
|
1045
|
+
this.refreshConfig();
|
|
1046
|
+
this.statusTimer = setInterval(() => {
|
|
1047
|
+
this.refreshStatus();
|
|
1048
|
+
}, STATUS_POLL_MS);
|
|
1049
|
+
this.configTimer = setInterval(() => {
|
|
1050
|
+
this.refreshConfig();
|
|
1051
|
+
}, CONFIG_POLL_MS);
|
|
1052
|
+
}
|
|
1053
|
+
detach() {
|
|
1054
|
+
this.stopTimer(this.statusTimer);
|
|
1055
|
+
this.stopTimer(this.configTimer);
|
|
1056
|
+
this.stopTimer(this.viewTimer);
|
|
1057
|
+
this.statusTimer = void 0;
|
|
1058
|
+
this.configTimer = void 0;
|
|
1059
|
+
this.viewTimer = void 0;
|
|
1060
|
+
this.remote = null;
|
|
1061
|
+
}
|
|
1062
|
+
async updateConfig(patch) {
|
|
1063
|
+
const result = await this.requireRemote().updateConfig(patch);
|
|
1064
|
+
if (!result.ok) throw new Error(failureDetail(result.error));
|
|
1065
|
+
this.applyConfig(result.value);
|
|
1066
|
+
this.refreshStatus();
|
|
1067
|
+
}
|
|
1068
|
+
async unsetConfig(field) {
|
|
1069
|
+
const result = await this.requireRemote().unsetConfig(field);
|
|
1070
|
+
if (!result.ok) throw new Error(failureDetail(result.error));
|
|
1071
|
+
this.applyConfig(result.value);
|
|
1072
|
+
}
|
|
1073
|
+
async runCommand(request) {
|
|
1074
|
+
const remote = this.remote;
|
|
1075
|
+
if (remote == null) return {
|
|
1076
|
+
ok: false,
|
|
1077
|
+
detail: "memory host unavailable"
|
|
1078
|
+
};
|
|
1079
|
+
let result;
|
|
1080
|
+
try {
|
|
1081
|
+
result = await remote.runCommand(request);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
return {
|
|
1084
|
+
ok: false,
|
|
1085
|
+
detail: String(error)
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
if (!result.ok) return {
|
|
1089
|
+
ok: false,
|
|
1090
|
+
detail: failureDetail(result.error)
|
|
1091
|
+
};
|
|
1092
|
+
if (result.value.view !== void 0) this.applyView(result.value.view);
|
|
1093
|
+
else this.refreshView();
|
|
1094
|
+
this.refreshStatus();
|
|
1095
|
+
this.refreshConfig();
|
|
1096
|
+
return result.value;
|
|
1097
|
+
}
|
|
1098
|
+
async refreshView() {
|
|
1099
|
+
const remote = this.remote;
|
|
1100
|
+
if (remote == null) return;
|
|
1101
|
+
try {
|
|
1102
|
+
const result = await remote.getView();
|
|
1103
|
+
if (!result.ok) throw new Error(failureDetail(result.error));
|
|
1104
|
+
this.applyView(result.value);
|
|
1105
|
+
} catch {
|
|
1106
|
+
this.viewStore.set(unavailableSnapshot());
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
applyView(view) {
|
|
1110
|
+
this.viewStore.set({
|
|
1111
|
+
status: "ready",
|
|
1112
|
+
value: view,
|
|
1113
|
+
base: void 0,
|
|
1114
|
+
user: void 0,
|
|
1115
|
+
revision: view.updatedAt,
|
|
1116
|
+
writable: false,
|
|
1117
|
+
mode: "host"
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
applyConfig(config) {
|
|
1121
|
+
this.configStore.set({
|
|
1122
|
+
status: "ready",
|
|
1123
|
+
value: config.value,
|
|
1124
|
+
base: config.base,
|
|
1125
|
+
user: config.user,
|
|
1126
|
+
revision: config.revision,
|
|
1127
|
+
writable: true,
|
|
1128
|
+
mode: "host"
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
async refreshConfig() {
|
|
1132
|
+
const remote = this.remote;
|
|
1133
|
+
if (remote == null) return;
|
|
1134
|
+
try {
|
|
1135
|
+
const result = await remote.getConfig();
|
|
1136
|
+
if (!result.ok) throw new Error(failureDetail(result.error));
|
|
1137
|
+
this.applyConfig(result.value);
|
|
1138
|
+
} catch {
|
|
1139
|
+
this.configStore.set(unavailableSnapshot());
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async refreshStatus() {
|
|
1143
|
+
const remote = this.remote;
|
|
1144
|
+
if (remote == null) return;
|
|
1145
|
+
try {
|
|
1146
|
+
const result = await remote.getStatus();
|
|
1147
|
+
if (!result.ok) throw new Error(failureDetail(result.error));
|
|
1148
|
+
const status = result.value;
|
|
1149
|
+
this.statusStore.set({
|
|
1150
|
+
status: "ready",
|
|
1151
|
+
value: status,
|
|
1152
|
+
base: void 0,
|
|
1153
|
+
user: void 0,
|
|
1154
|
+
revision: status.updatedAt,
|
|
1155
|
+
writable: false,
|
|
1156
|
+
mode: "host"
|
|
1157
|
+
});
|
|
1158
|
+
} catch {
|
|
1159
|
+
this.statusStore.set(unavailableSnapshot());
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
requireRemote() {
|
|
1163
|
+
if (this.remote == null) throw new Error("memory host unavailable");
|
|
1164
|
+
return this.remote;
|
|
1165
|
+
}
|
|
1166
|
+
stopTimer(timer) {
|
|
1167
|
+
if (timer !== void 0) clearInterval(timer);
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
/**
|
|
1171
|
+
* Mount the contribution and hand the resulting namespace to the bridge.
|
|
1172
|
+
* Returns the disposer the caller runs from its effect cleanup.
|
|
1173
|
+
*/
|
|
1174
|
+
async function mountMemoryBridge(bridge, ctx) {
|
|
1175
|
+
const remote = ctx.remote;
|
|
1176
|
+
if (remote == null || typeof remote.$mount !== "function") throw new Error("the remote service is not available in this composition");
|
|
1177
|
+
let dispose;
|
|
1178
|
+
try {
|
|
1179
|
+
dispose = await remote.$mount(CONTRIBUTION);
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
throw new Error(`mounting the memory remote contribution failed: ${String(error)}`);
|
|
1182
|
+
}
|
|
1183
|
+
const namespace = ctx.get("remote.memory");
|
|
1184
|
+
if (namespace == null) {
|
|
1185
|
+
await dispose();
|
|
1186
|
+
throw new Error("remote.memory is missing after mounting the contribution");
|
|
1187
|
+
}
|
|
1188
|
+
bridge.attach(namespace);
|
|
1189
|
+
return async () => {
|
|
1190
|
+
bridge.detach();
|
|
1191
|
+
await dispose();
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
902
1195
|
//#endregion
|
|
903
1196
|
//#region src/client/sidebar-action.ts
|
|
904
1197
|
let styleInjected$1 = false;
|
|
@@ -1003,10 +1296,7 @@ function renderAction(props) {
|
|
|
1003
1296
|
const sendCommand = (action) => {
|
|
1004
1297
|
const commands = props.commands;
|
|
1005
1298
|
if (commands == null) return;
|
|
1006
|
-
(
|
|
1007
|
-
await commands.set("action", action);
|
|
1008
|
-
await commands.set("requestedAt", Date.now());
|
|
1009
|
-
})();
|
|
1299
|
+
commands.run({ action });
|
|
1010
1300
|
};
|
|
1011
1301
|
const toggleSetting = (field, value) => {
|
|
1012
1302
|
props.settings?.set(field, value);
|
|
@@ -1450,12 +1740,11 @@ function renderViewer(props) {
|
|
|
1450
1740
|
const sendCommand = (action, arg = "", force = false) => {
|
|
1451
1741
|
const commands = props.commands;
|
|
1452
1742
|
if (commands == null) return;
|
|
1453
|
-
(
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
})();
|
|
1743
|
+
commands.run({
|
|
1744
|
+
action,
|
|
1745
|
+
arg: arg === "" ? void 0 : arg,
|
|
1746
|
+
force: force || void 0
|
|
1747
|
+
});
|
|
1459
1748
|
};
|
|
1460
1749
|
return (0, react.createElement)("div", { className: "dshmv-root" }, (0, react.createElement)("div", { className: "dshmv-tabs" }, ...[
|
|
1461
1750
|
"overview",
|
|
@@ -1605,9 +1894,7 @@ const name = "dsh-remember";
|
|
|
1605
1894
|
const inject = [
|
|
1606
1895
|
"slots",
|
|
1607
1896
|
"locale",
|
|
1608
|
-
"
|
|
1609
|
-
"remote",
|
|
1610
|
-
"remote.session"
|
|
1897
|
+
"remote"
|
|
1611
1898
|
];
|
|
1612
1899
|
const NS = "dsh-memory";
|
|
1613
1900
|
const ZH = {
|
|
@@ -1826,34 +2113,42 @@ const EN = {
|
|
|
1826
2113
|
};
|
|
1827
2114
|
/** Activate the browser half: register the Memory settings page and the status pill. */
|
|
1828
2115
|
function apply(ctx) {
|
|
2116
|
+
try {
|
|
2117
|
+
activate(ctx);
|
|
2118
|
+
} catch (error) {
|
|
2119
|
+
console.error("[dsh-memory] activation failed", error);
|
|
2120
|
+
throw error;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
function activate(ctx) {
|
|
1829
2124
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
1830
2125
|
zh: ZH,
|
|
1831
2126
|
en: EN
|
|
1832
2127
|
}), "dsh-memory: dictionaries");
|
|
1833
2128
|
ctx.effect(() => installCitationStripper(), "dsh-memory: citation stripper");
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2129
|
+
const bridge = new MemoryBridge();
|
|
2130
|
+
ctx.effect(() => {
|
|
2131
|
+
let dispose;
|
|
2132
|
+
let cancelled = false;
|
|
2133
|
+
mountMemoryBridge(bridge, ctx).then((mounted) => {
|
|
2134
|
+
if (cancelled) {
|
|
2135
|
+
mounted();
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
dispose = mounted;
|
|
2139
|
+
}).catch((error) => {
|
|
2140
|
+
console.warn("[dsh-memory] memory remote unavailable:", error);
|
|
2141
|
+
});
|
|
2142
|
+
return () => {
|
|
2143
|
+
cancelled = true;
|
|
2144
|
+
if (dispose != null) dispose();
|
|
2145
|
+
};
|
|
2146
|
+
}, "dsh-memory: memory remote");
|
|
2147
|
+
const card = new MemoryCardController(bridge.configScope, catalogLoader(ctx));
|
|
2148
|
+
const statusSource = bridge.statusSource;
|
|
2149
|
+
const viewSource = bridge.viewSource;
|
|
2150
|
+
const commandsView = bridge.commands;
|
|
2151
|
+
const memoryView = bridge.configScope;
|
|
1857
2152
|
let sidebarRight;
|
|
1858
2153
|
ctx.inject(["sidebarRightTabs", "sidebarRight"], (sctx) => {
|
|
1859
2154
|
sidebarRight = sctx.sidebarRight;
|
|
@@ -1886,21 +2181,29 @@ function apply(ctx) {
|
|
|
1886
2181
|
sidebarRight?.openTab(MEMORY_VIEWER_KIND, {});
|
|
1887
2182
|
} catch {}
|
|
1888
2183
|
};
|
|
2184
|
+
const pageFace = (embedded) => () => ({
|
|
2185
|
+
...card.inject(),
|
|
2186
|
+
commands: commandsView,
|
|
2187
|
+
embedded,
|
|
2188
|
+
hooks: {
|
|
2189
|
+
memoryPage: card.pageStore(),
|
|
2190
|
+
memoryStatus: statusSource,
|
|
2191
|
+
memoryView: viewSource
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
1889
2194
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1890
2195
|
name: "settings.section",
|
|
1891
2196
|
id: "memory",
|
|
1892
2197
|
order: 16,
|
|
1893
2198
|
label: () => t_nav(ctx),
|
|
1894
2199
|
locale: NS,
|
|
1895
|
-
inject: ()
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
}
|
|
1903
|
-
})
|
|
2200
|
+
inject: pageFace(false)
|
|
2201
|
+
}, MemorySettingsPage));
|
|
2202
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
2203
|
+
name: "plugins.bundle.config",
|
|
2204
|
+
key: "dsh-remember",
|
|
2205
|
+
locale: NS,
|
|
2206
|
+
inject: pageFace(true)
|
|
1904
2207
|
}, MemorySettingsPage));
|
|
1905
2208
|
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
1906
2209
|
name: "sidebar.footer.action",
|
|
@@ -1918,12 +2221,27 @@ function apply(ctx) {
|
|
|
1918
2221
|
function t_nav(ctx) {
|
|
1919
2222
|
return ctx.locale.bind(NS)("nav");
|
|
1920
2223
|
}
|
|
2224
|
+
/**
|
|
2225
|
+
* The catalog is optional: `remote.session` is not in `inject`, because a
|
|
2226
|
+
* composition without the session controller must still activate the panel
|
|
2227
|
+
* (users can type `provider/model` by hand). Looking the service up through
|
|
2228
|
+
* `ctx.get` keeps activation independent of the controller's ordering.
|
|
2229
|
+
*/
|
|
2230
|
+
function lookupSession(ctx) {
|
|
2231
|
+
try {
|
|
2232
|
+
const value = ctx.get("remote.session");
|
|
2233
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
2234
|
+
} catch {
|
|
2235
|
+
return null;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
1921
2238
|
/** Query the Host model catalog and flatten it into selectable routes. */
|
|
1922
2239
|
function catalogLoader(ctx) {
|
|
1923
|
-
const
|
|
2240
|
+
const session = lookupSession(ctx);
|
|
2241
|
+
const call = session?.modelCatalog;
|
|
1924
2242
|
if (call == null) return null;
|
|
1925
2243
|
return async () => {
|
|
1926
|
-
const result = await call.call(
|
|
2244
|
+
const result = await call.call(session);
|
|
1927
2245
|
if (!result?.ok || result.value == null) throw new Error("model catalog unavailable");
|
|
1928
2246
|
return result.value.groups.flatMap((group) => group.models.map((model) => ({
|
|
1929
2247
|
provider: group.id,
|