@modusensus/dsh-mneme 0.1.6 → 0.2.0

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
@@ -1,461 +1,461 @@
1
- window.__ModuleLoader__.load({
2
- id: "@modusensus/dsh-mneme",
3
- factory: (require) => {
4
- var module = { exports: {} };
5
- var exports = module.exports;
6
-
7
- let react = require("react");
8
- let reactDom = require("react-dom");
9
- let { useState, useEffect, useCallback, useRef } = react;
10
- let { createPortal } = reactDom;
11
-
12
- const inject = ["slots", "locale"];
13
-
14
- const NS = "memory";
15
-
16
- const dictionaries = {
17
- zh: {
18
- "memory.panel.title": "记忆库",
19
- "memory.panel.search": "搜索记忆…",
20
- "memory.panel.empty": "暂无记忆条目",
21
- "memory.panel.open": "记忆",
22
- "memory.tab.all": "全部",
23
- "memory.tab.preference": "偏好",
24
- "memory.tab.project": "项目",
25
- "memory.tab.decision": "决策",
26
- "memory.tab.history": "历史",
27
- "memory.settings.open": "设置",
28
- "memory.settings.title": "记忆库设置",
29
- "memory.settings.profile": "用户画像",
30
- "memory.settings.profileHint": "描述你自己(角色、背景、偏好),Agent 会在每轮遵循",
31
- "memory.settings.profileSave": "保存画像",
32
- "memory.settings.profileSaved": "画像已保存",
33
- "memory.settings.rules": "规则",
34
- "memory.settings.rulesHint": "Agent 必须遵守的行为规则,每轮注入",
35
- "memory.settings.ruleAdd": "添加规则",
36
- "memory.settings.rulePlaceholder": "例如:回答时总是先给结论",
37
- "memory.settings.commands": "自定义指令",
38
- "memory.settings.commandsHint": "注册斜杠命令(/名称),触发时把指令内容交给 Agent",
39
- "memory.settings.cmdName": "命令名",
40
- "memory.settings.cmdDesc": "描述",
41
- "memory.settings.cmdInstruction": "指令内容",
42
- "memory.settings.cmdAdd": "添加命令",
43
- "memory.settings.cmdDelete": "删除",
44
- "memory.settings.empty": "暂无内容",
45
- "memory.panel.semantic": "语义",
46
- "memory.settings.vectorTitle": "向量搜索",
47
- "memory.settings.vectorHint": "接入 OpenAI 兼容的 embeddings API 做语义搜索,可匹配字面不同但语义相近的记忆",
48
- "memory.settings.vectorEnabled": "启用向量搜索",
49
- "memory.settings.vectorBaseUrl": "API 地址 (Base URL)",
50
- "memory.settings.vectorApiKey": "API Key",
51
- "memory.settings.vectorModel": "模型名",
52
- "memory.settings.vectorSave": "保存配置",
53
- "memory.settings.vectorSaved": "配置已保存",
54
- "memory.settings.vectorReindex": "重建索引",
55
- "memory.settings.vectorReindexing": "索引中…",
56
- "memory.settings.vectorReindexDone": "已索引 {n} 条"
57
- },
58
- en: {
59
- "memory.panel.title": "Memory",
60
- "memory.panel.search": "Search memories…",
61
- "memory.panel.empty": "No memories yet",
62
- "memory.panel.open": "Memory",
63
- "memory.tab.all": "All",
64
- "memory.tab.preference": "Preferences",
65
- "memory.tab.project": "Projects",
66
- "memory.tab.decision": "Decisions",
67
- "memory.tab.history": "History",
68
- "memory.settings.open": "Settings",
69
- "memory.settings.title": "Memory Settings",
70
- "memory.settings.profile": "User Profile",
71
- "memory.settings.profileHint": "Describe yourself — the agent follows this every turn",
72
- "memory.settings.profileSave": "Save Profile",
73
- "memory.settings.profileSaved": "Profile saved",
74
- "memory.settings.rules": "Rules",
75
- "memory.settings.rulesHint": "Behavior rules the agent must follow every turn",
76
- "memory.settings.ruleAdd": "Add Rule",
77
- "memory.settings.rulePlaceholder": "e.g. Always lead with a conclusion",
78
- "memory.settings.commands": "Custom Commands",
79
- "memory.settings.commandsHint": "Register slash commands (/name) whose instruction is handed to the agent",
80
- "memory.settings.cmdName": "Name",
81
- "memory.settings.cmdDesc": "Description",
82
- "memory.settings.cmdInstruction": "Instruction",
83
- "memory.settings.cmdAdd": "Add Command",
84
- "memory.settings.cmdDelete": "Delete",
85
- "memory.settings.empty": "Nothing yet",
86
- "memory.panel.semantic": "Semantic",
87
- "memory.settings.vectorTitle": "Vector Search",
88
- "memory.settings.vectorHint": "Connect an OpenAI-compatible embeddings API for semantic search by meaning, not just keywords",
89
- "memory.settings.vectorEnabled": "Enable vector search",
90
- "memory.settings.vectorBaseUrl": "Base URL",
91
- "memory.settings.vectorApiKey": "API Key",
92
- "memory.settings.vectorModel": "Model",
93
- "memory.settings.vectorSave": "Save Config",
94
- "memory.settings.vectorSaved": "Config saved",
95
- "memory.settings.vectorReindex": "Reindex",
96
- "memory.settings.vectorReindexing": "Indexing…",
97
- "memory.settings.vectorReindexDone": "Indexed {n} items"
98
- }
99
- };
100
-
101
- function typeLabel(t, type) {
102
- const key = `memory.tab.${type}`;
103
- const label = t(key);
104
- return label && label !== key ? label : String(type);
105
- }
106
-
107
- function formatDate(value) {
108
- if (!value) return "—";
109
- const date = new Date(value);
110
- return Number.isNaN(date.getTime()) ? "—" : date.toLocaleString();
111
- }
112
-
113
- function MemoryPanel({ t, onClose, embedded }) {
114
- const [tab, setTab] = useState("all");
115
- const [query, setQuery] = useState("");
116
- const [semantic, setSemantic] = useState(false);
117
- const [vecEnabled, setVecEnabled] = useState(false);
118
- const [items, setItems] = useState([]);
119
- const [loading, setLoading] = useState(false);
120
- const abortRef = useRef(null);
121
-
122
- useEffect(() => {
123
- fetch("/api/dsh-mneme/vector-config")
124
- .then((res) => res.json())
125
- .then((d) => setVecEnabled(!!d.config?.enabled))
126
- .catch(() => {});
127
- }, []);
128
-
129
- const load = useCallback(async () => {
130
- abortRef.current?.abort();
131
- const controller = new AbortController();
132
- abortRef.current = controller;
133
- setLoading(true);
134
- try {
135
- const params = new URLSearchParams();
136
- if (tab !== "all") params.set("type", tab);
137
- const url = query.trim()
138
- ? `/api/dsh-mneme/search?q=${encodeURIComponent(query.trim())}&mode=${semantic ? "vector" : "auto"}`
139
- : `/api/dsh-mneme/list?${params.toString()}`;
140
- const res = await fetch(url, { signal: controller.signal });
141
- const data = await res.json();
142
- setItems(data.items || []);
143
- } catch (error) {
144
- if (error.name === "AbortError") return;
145
- setItems([]);
146
- } finally {
147
- setLoading(false);
148
- }
149
- }, [tab, query, semantic]);
150
-
151
- useEffect(() => {
152
- load();
153
- return () => abortRef.current?.abort();
154
- }, [load]);
155
-
156
- const tabs = ["all", "preference", "project", "decision", "history"];
157
-
158
- const body = react.createElement("div", { style: embedded ? { ...styles.panel, width: "100%", maxWidth: "100%", maxHeight: "none" } : styles.panel },
159
- !embedded && react.createElement("div", { style: styles.header },
160
- react.createElement("span", { style: styles.title }, t("memory.panel.title")),
161
- react.createElement("button", { style: styles.close, onClick: onClose }, "×")
162
- ),
163
- react.createElement("div", { style: { display: "flex", gap: 6, alignItems: "center", marginBottom: 12 } },
164
- react.createElement("input", {
165
- style: { ...styles.search, flex: 1, marginBottom: 0 },
166
- placeholder: t("memory.panel.search"),
167
- value: query,
168
- onChange: (e) => setQuery(e.target.value)
169
- }),
170
- vecEnabled && react.createElement("button", {
171
- style: { ...styles.tab, ...(semantic ? styles.tabActive : {}) },
172
- title: t("memory.settings.vectorTitle"),
173
- onClick: () => setSemantic(!semantic)
174
- }, t("memory.panel.semantic"))
175
- ),
176
- react.createElement("div", { style: styles.tabs },
177
- tabs.map((key) =>
178
- react.createElement("button", {
179
- key,
180
- style: { ...styles.tab, ...(tab === key ? styles.tabActive : {}) },
181
- onClick: () => setTab(key)
182
- }, t(`memory.tab.${key}`))
183
- )
184
- ),
185
- react.createElement("div", { style: styles.list },
186
- loading
187
- ? react.createElement("div", { style: styles.hint }, "…")
188
- : items.length === 0
189
- ? react.createElement("div", { style: styles.hint }, t("memory.panel.empty"))
190
- : items.map((item) =>
191
- react.createElement("div", { key: item.id, style: styles.card },
192
- react.createElement("div", { style: styles.cardTitle },
193
- react.createElement("span", null, item.title),
194
- react.createElement("span", { style: styles.badge },
195
- `${typeLabel(t, item.type)} · ★${item.importance}`
196
- )
197
- ),
198
- react.createElement("div", { style: styles.cardContent }, item.content),
199
- react.createElement("div", { style: styles.cardMeta },
200
- formatDate(item.updated_at)
201
- )
202
- )
203
- )
204
- )
205
- );
206
- return embedded
207
- ? body
208
- : createPortal(react.createElement("div", { style: styles.overlay }, body), document.body);
209
- }
210
-
211
- const h = react.createElement;
212
-
213
- // --- Settings panel: user profile, rules, custom commands ---
214
- function SettingsPanel({ t, onClose, embedded }) {
215
- const [sectionTab, setSectionTab] = react.useState("settings");
216
- const [profile, setProfile] = react.useState("");
217
- const [rules, setRules] = react.useState([]);
218
- const [commands, setCommands] = react.useState([]);
219
- const [newRule, setNewRule] = react.useState("");
220
- const [newCmd, setNewCmd] = react.useState({ name: "", description: "", instruction: "" });
221
- const [saved, setSaved] = react.useState(false);
222
- const [cmdError, setCmdError] = react.useState("");
223
- const [vector, setVector] = react.useState({ enabled: false, baseUrl: "", apiKey: "", model: "" });
224
- const [vectorSaved, setVectorSaved] = react.useState(false);
225
- const [reindexing, setReindexing] = react.useState(false);
226
- const [reindexMsg, setReindexMsg] = react.useState("");
227
-
228
- const load = react.useCallback(async () => {
229
- try {
230
- const [p, r, c, v] = await Promise.all([
231
- fetch("/api/dsh-mneme/profile").then((res) => res.json()),
232
- fetch("/api/dsh-mneme/rules").then((res) => res.json()),
233
- fetch("/api/dsh-mneme/commands").then((res) => res.json()),
234
- fetch("/api/dsh-mneme/vector-config").then((res) => res.json())
235
- ]);
236
- setProfile(p.profile || "");
237
- setRules(Array.isArray(r.rules) ? r.rules : []);
238
- setCommands(Array.isArray(c.commands) ? c.commands : []);
239
- setVector(v.config || { enabled: false, baseUrl: "", apiKey: "", model: "" });
240
- } catch { /* ignore */ }
241
- }, []);
242
-
243
- react.useEffect(() => { load(); }, [load]);
244
-
245
- async function saveProfile() {
246
- try {
247
- await fetch("/api/dsh-mneme/profile", {
248
- method: "PUT",
249
- headers: { "Content-Type": "application/json" },
250
- body: JSON.stringify({ profile })
251
- });
252
- setSaved(true);
253
- setTimeout(() => setSaved(false), 1500);
254
- } catch { /* ignore */ }
255
- }
256
-
257
- async function putRules(next) {
258
- await fetch("/api/dsh-mneme/rules", {
259
- method: "PUT",
260
- headers: { "Content-Type": "application/json" },
261
- body: JSON.stringify({ rules: next })
262
- });
263
- }
264
-
265
- async function addRule() {
266
- const text = newRule.trim();
267
- if (!text) return;
268
- const next = [...rules, text];
269
- await putRules(next);
270
- setRules(next);
271
- setNewRule("");
272
- }
273
-
274
- async function removeRule(index) {
275
- const next = rules.filter((_, i) => i !== index);
276
- await putRules(next);
277
- setRules(next);
278
- }
279
-
280
- async function addCommand() {
281
- const name = newCmd.name.trim();
282
- const instruction = newCmd.instruction.trim();
283
- if (!name || !instruction) return;
284
- try {
285
- const res = await fetch("/api/dsh-mneme/commands", {
286
- method: "POST",
287
- headers: { "Content-Type": "application/json" },
288
- body: JSON.stringify({ name, description: newCmd.description, instruction })
289
- });
290
- const data = await res.json();
291
- if (!res.ok) { setCmdError(data.error || "failed"); return; }
292
- setCmdError("");
293
- setCommands([...commands, data.command]);
294
- setNewCmd({ name: "", description: "", instruction: "" });
295
- } catch { setCmdError("failed"); }
296
- }
297
-
298
- async function removeCommand(id) {
299
- await fetch(`/api/dsh-mneme/commands?id=${encodeURIComponent(id)}`, { method: "DELETE" });
300
- setCommands(commands.filter((c) => c.id !== id));
301
- }
302
-
303
- async function saveVector() {
304
- try {
305
- await fetch("/api/dsh-mneme/vector-config", {
306
- method: "PUT",
307
- headers: { "Content-Type": "application/json" },
308
- body: JSON.stringify(vector)
309
- });
310
- setVectorSaved(true);
311
- setTimeout(() => setVectorSaved(false), 1500);
312
- } catch { /* ignore */ }
313
- }
314
-
315
- async function reindex() {
316
- setReindexing(true);
317
- setReindexMsg("");
318
- try {
319
- const res = await fetch("/api/dsh-mneme/vector-reindex");
320
- const data = await res.json();
321
- const n = data.indexed ?? 0;
322
- setReindexMsg(t("memory.settings.vectorReindexDone").replace("{n}", String(n)));
323
- } catch { setReindexMsg(""); }
324
- setReindexing(false);
325
- }
326
-
327
- const inputStyle = { ...styles.search, marginBottom: 8 };
328
- const labelStyle = { fontSize: 12, fontWeight: 600, margin: "12px 0 4px" };
329
- const hintStyle = { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)", marginBottom: 8 };
330
- const rowStyle = { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, padding: "6px 0", borderBottom: "1px solid var(--dsw-alias-border-l1, #eee)" };
331
-
332
- const body = h("div", { style: embedded ? { ...styles.panel, width: "100%", maxWidth: "100%", maxHeight: "none" } : styles.panel },
333
- !embedded && h("div", { style: styles.header },
334
- h("span", { style: styles.title }, t("memory.settings.title")),
335
- h("button", { style: styles.close, onClick: onClose }, "×")
336
- ),
337
- h("div", { style: styles.tabs },
338
- h("button", { style: { ...styles.tab, ...(sectionTab === "memory" ? styles.tabActive : {}) }, onClick: () => setSectionTab("memory") }, t("memory.panel.open")),
339
- h("button", { style: { ...styles.tab, ...(sectionTab === "settings" ? styles.tabActive : {}) }, onClick: () => setSectionTab("settings") }, t("memory.settings.title"))
340
- ),
341
- sectionTab === "memory"
342
- ? h(MemoryPanel, { t, embedded: true })
343
- : h("div", { style: { overflowY: "auto" } },
344
- // profile
345
- h("div", { style: labelStyle }, t("memory.settings.profile")),
346
- h("div", { style: hintStyle }, t("memory.settings.profileHint")),
347
- h("textarea", {
348
- style: { ...inputStyle, minHeight: 72, resize: "both", fontFamily: "inherit" },
349
- value: profile,
350
- placeholder: t("memory.settings.profile"),
351
- onChange: (e) => setProfile(e.target.value)
352
- }),
353
- h("div", null,
354
- h("button", { style: styles.footerButton, onClick: saveProfile }, t("memory.settings.profileSave")),
355
- saved && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-success, #2a7)" } }, t("memory.settings.profileSaved"))
356
- ),
357
- // rules
358
- h("div", { style: labelStyle }, t("memory.settings.rules")),
359
- h("div", { style: hintStyle }, t("memory.settings.rulesHint")),
360
- rules.length === 0 && h("div", { style: styles.hint }, t("memory.settings.empty")),
361
- rules.map((rule, i) =>
362
- h("div", { key: i, style: rowStyle },
363
- h("span", { style: { fontSize: 13, flex: 1 } }, rule),
364
- h("button", { style: { ...styles.footerButton, color: "var(--dsw-alias-state-error, #c33)" }, onClick: () => removeRule(i) }, "×")
365
- )
366
- ),
367
- h("div", { style: { display: "flex", gap: 6 } },
368
- h("input", {
369
- style: { ...inputStyle, flex: 1, marginBottom: 0 },
370
- value: newRule,
371
- placeholder: t("memory.settings.rulePlaceholder"),
372
- onChange: (e) => setNewRule(e.target.value)
373
- }),
374
- h("button", { style: styles.footerButton, onClick: addRule }, t("memory.settings.ruleAdd"))
375
- ),
376
- // custom commands
377
- h("div", { style: labelStyle }, t("memory.settings.commands")),
378
- h("div", { style: hintStyle }, t("memory.settings.commandsHint")),
379
- commands.length === 0 && h("div", { style: styles.hint }, t("memory.settings.empty")),
380
- commands.map((cmd) =>
381
- h("div", { key: cmd.id, style: rowStyle },
382
- h("div", { style: { flex: 1 } },
383
- h("div", { style: { fontSize: 13, fontWeight: 600 } }, `/${cmd.name}`),
384
- h("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)" } }, cmd.description || cmd.instruction)
385
- ),
386
- h("button", { style: { ...styles.footerButton, color: "var(--dsw-alias-state-error, #c33)" }, onClick: () => removeCommand(cmd.id) }, t("memory.settings.cmdDelete"))
387
- )
388
- ),
389
- h("div", { style: { display: "grid", gap: 6, marginTop: 6 } },
390
- h("input", { style: { ...inputStyle, marginBottom: 0 }, value: newCmd.name, placeholder: t("memory.settings.cmdName"), onChange: (e) => setNewCmd({ ...newCmd, name: e.target.value }) }),
391
- h("input", { style: { ...inputStyle, marginBottom: 0 }, value: newCmd.description, placeholder: t("memory.settings.cmdDesc"), onChange: (e) => setNewCmd({ ...newCmd, description: e.target.value }) }),
392
- h("textarea", { style: { ...inputStyle, marginBottom: 0, minHeight: 48, resize: "both", fontFamily: "inherit" }, value: newCmd.instruction, placeholder: t("memory.settings.cmdInstruction"), onChange: (e) => setNewCmd({ ...newCmd, instruction: e.target.value }) }),
393
- h("button", { style: styles.footerButton, onClick: addCommand }, t("memory.settings.cmdAdd")),
394
- cmdError && h("div", { style: { fontSize: 12, color: "var(--dsw-alias-state-error, #c33)" } }, cmdError)
395
- ),
396
- // vector search
397
- h("div", { style: { ...labelStyle, marginTop: 20 } }, t("memory.settings.vectorTitle")),
398
- h("div", { style: hintStyle }, t("memory.settings.vectorHint")),
399
- h("label", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 8, fontSize: 13 } },
400
- h("input", { type: "checkbox", checked: !!vector.enabled, onChange: (e) => setVector({ ...vector, enabled: e.target.checked }) }),
401
- h("span", null, t("memory.settings.vectorEnabled"))
402
- ),
403
- h("input", { style: inputStyle, value: vector.baseUrl, placeholder: t("memory.settings.vectorBaseUrl"), onChange: (e) => setVector({ ...vector, baseUrl: e.target.value }) }),
404
- h("input", { style: inputStyle, type: "password", value: vector.apiKey, placeholder: t("memory.settings.vectorApiKey"), onChange: (e) => setVector({ ...vector, apiKey: e.target.value }) }),
405
- h("input", { style: inputStyle, value: vector.model, placeholder: t("memory.settings.vectorModel"), onChange: (e) => setVector({ ...vector, model: e.target.value }) }),
406
- h("div", { style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" } },
407
- h("button", { style: styles.footerButton, onClick: saveVector }, t("memory.settings.vectorSave")),
408
- vectorSaved && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-success, #2a7)" } }, t("memory.settings.vectorSaved")),
409
- h("button", { style: styles.footerButton, onClick: reindex, disabled: reindexing }, reindexing ? t("memory.settings.vectorReindexing") : t("memory.settings.vectorReindex")),
410
- reindexMsg && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #666)" } }, reindexMsg)
411
- )
412
- )
413
- );
414
- return embedded
415
- ? body
416
- : createPortal(h("div", { style: styles.overlay }, body), document.body);
417
- }
418
-
419
- const styles = {
420
- overlay: { position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)", zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center" },
421
- panel: { background: "var(--dsw-alias-bg-base, #fff)", borderRadius: 12, width: 640, maxWidth: "90vw", maxHeight: "80vh", display: "flex", flexDirection: "column", padding: 16, boxShadow: "0 8px 40px rgba(0,0,0,0.2)" },
422
- header: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 },
423
- title: { fontSize: 16, fontWeight: 600 },
424
- close: { border: "none", background: "none", fontSize: 20, cursor: "pointer", color: "var(--dsw-alias-label-secondary, #666)" },
425
- search: { padding: "8px 12px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2, #ddd)", marginBottom: 12, fontSize: 14 },
426
- tabs: { display: "flex", gap: 6, marginBottom: 12, flexWrap: "wrap" },
427
- tab: { padding: "4px 10px", borderRadius: 999, border: "1px solid var(--dsw-alias-border-l2, #ddd)", background: "none", cursor: "pointer", fontSize: 12 },
428
- tabActive: { background: "var(--dsw-alias-interactive-bg-active, #eee)" },
429
- list: { overflowY: "auto", display: "flex", flexDirection: "column", gap: 8 },
430
- hint: { color: "var(--dsw-alias-label-tertiary, #999)", padding: "24px 0", textAlign: "center" },
431
- card: { border: "1px solid var(--dsw-alias-border-l1, #eee)", borderRadius: 8, padding: "10px 12px" },
432
- cardTitle: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4, fontSize: 14, fontWeight: 600 },
433
- badge: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)" },
434
- cardContent: { fontSize: 13, color: "var(--dsw-alias-label-secondary, #555)", marginBottom: 4, whiteSpace: "pre-wrap", wordBreak: "break-word" },
435
- cardMeta: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)" },
436
- footerButton: { padding: "4px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2, #ddd)", background: "none", cursor: "pointer", fontSize: 12, margin: "2px 8px" },
437
- footerButtonActive: { background: "var(--dsw-alias-interactive-bg-active, #eee)" }
438
- };
439
-
440
- function apply(ctx) {
441
- ctx.effect(() => ctx.locale.register(NS, dictionaries), "dsh-mneme: dictionaries");
442
-
443
- // Register a custom section inside the DSH settings panel (nav label +
444
- // embedded memory/settings content). Access is via the official settings
445
- // panel only; no sidebar buttons are registered.
446
- ctx.effect(() => {
447
- const t = ctx.locale.bind(NS);
448
- return ctx.slots.register({
449
- name: "settings.section",
450
- id: "dsh-mneme",
451
- label: () => t("memory.settings.title"),
452
- inject: () => ({})
453
- }, () => react.createElement(SettingsPanel, { t, embedded: true, onClose: () => {} }));
454
- }, "dsh-mneme: settings section");
455
- }
456
-
457
- exports.apply = apply;
458
- exports.inject = inject;
459
- return module.exports;
460
- }
461
- });
1
+ window.__ModuleLoader__.load({
2
+ id: "@modusensus/dsh-mneme",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+
7
+ let react = require("react");
8
+ let reactDom = require("react-dom");
9
+ let { useState, useEffect, useCallback, useRef } = react;
10
+ let { createPortal } = reactDom;
11
+
12
+ const inject = ["slots", "locale"];
13
+
14
+ const NS = "memory";
15
+
16
+ const dictionaries = {
17
+ zh: {
18
+ "memory.panel.title": "记忆库",
19
+ "memory.panel.search": "搜索记忆…",
20
+ "memory.panel.empty": "暂无记忆条目",
21
+ "memory.panel.open": "记忆",
22
+ "memory.tab.all": "全部",
23
+ "memory.tab.preference": "偏好",
24
+ "memory.tab.project": "项目",
25
+ "memory.tab.decision": "决策",
26
+ "memory.tab.history": "历史",
27
+ "memory.settings.open": "设置",
28
+ "memory.settings.title": "记忆库设置",
29
+ "memory.settings.profile": "用户画像",
30
+ "memory.settings.profileHint": "描述你自己(角色、背景、偏好),Agent 会在每轮遵循",
31
+ "memory.settings.profileSave": "保存画像",
32
+ "memory.settings.profileSaved": "画像已保存",
33
+ "memory.settings.rules": "规则",
34
+ "memory.settings.rulesHint": "Agent 必须遵守的行为规则,每轮注入",
35
+ "memory.settings.ruleAdd": "添加规则",
36
+ "memory.settings.rulePlaceholder": "例如:回答时总是先给结论",
37
+ "memory.settings.commands": "自定义指令",
38
+ "memory.settings.commandsHint": "注册斜杠命令(/名称),触发时把指令内容交给 Agent",
39
+ "memory.settings.cmdName": "命令名",
40
+ "memory.settings.cmdDesc": "描述",
41
+ "memory.settings.cmdInstruction": "指令内容",
42
+ "memory.settings.cmdAdd": "添加命令",
43
+ "memory.settings.cmdDelete": "删除",
44
+ "memory.settings.empty": "暂无内容",
45
+ "memory.panel.semantic": "语义",
46
+ "memory.settings.vectorTitle": "向量搜索",
47
+ "memory.settings.vectorHint": "接入 OpenAI 兼容的 embeddings API 做语义搜索,可匹配字面不同但语义相近的记忆",
48
+ "memory.settings.vectorEnabled": "启用向量搜索",
49
+ "memory.settings.vectorBaseUrl": "API 地址 (Base URL)",
50
+ "memory.settings.vectorApiKey": "API Key",
51
+ "memory.settings.vectorModel": "模型名",
52
+ "memory.settings.vectorSave": "保存配置",
53
+ "memory.settings.vectorSaved": "配置已保存",
54
+ "memory.settings.vectorReindex": "重建索引",
55
+ "memory.settings.vectorReindexing": "索引中…",
56
+ "memory.settings.vectorReindexDone": "已索引 {n} 条"
57
+ },
58
+ en: {
59
+ "memory.panel.title": "Memory",
60
+ "memory.panel.search": "Search memories…",
61
+ "memory.panel.empty": "No memories yet",
62
+ "memory.panel.open": "Memory",
63
+ "memory.tab.all": "All",
64
+ "memory.tab.preference": "Preferences",
65
+ "memory.tab.project": "Projects",
66
+ "memory.tab.decision": "Decisions",
67
+ "memory.tab.history": "History",
68
+ "memory.settings.open": "Settings",
69
+ "memory.settings.title": "Memory Settings",
70
+ "memory.settings.profile": "User Profile",
71
+ "memory.settings.profileHint": "Describe yourself — the agent follows this every turn",
72
+ "memory.settings.profileSave": "Save Profile",
73
+ "memory.settings.profileSaved": "Profile saved",
74
+ "memory.settings.rules": "Rules",
75
+ "memory.settings.rulesHint": "Behavior rules the agent must follow every turn",
76
+ "memory.settings.ruleAdd": "Add Rule",
77
+ "memory.settings.rulePlaceholder": "e.g. Always lead with a conclusion",
78
+ "memory.settings.commands": "Custom Commands",
79
+ "memory.settings.commandsHint": "Register slash commands (/name) whose instruction is handed to the agent",
80
+ "memory.settings.cmdName": "Name",
81
+ "memory.settings.cmdDesc": "Description",
82
+ "memory.settings.cmdInstruction": "Instruction",
83
+ "memory.settings.cmdAdd": "Add Command",
84
+ "memory.settings.cmdDelete": "Delete",
85
+ "memory.settings.empty": "Nothing yet",
86
+ "memory.panel.semantic": "Semantic",
87
+ "memory.settings.vectorTitle": "Vector Search",
88
+ "memory.settings.vectorHint": "Connect an OpenAI-compatible embeddings API for semantic search by meaning, not just keywords",
89
+ "memory.settings.vectorEnabled": "Enable vector search",
90
+ "memory.settings.vectorBaseUrl": "Base URL",
91
+ "memory.settings.vectorApiKey": "API Key",
92
+ "memory.settings.vectorModel": "Model",
93
+ "memory.settings.vectorSave": "Save Config",
94
+ "memory.settings.vectorSaved": "Config saved",
95
+ "memory.settings.vectorReindex": "Reindex",
96
+ "memory.settings.vectorReindexing": "Indexing…",
97
+ "memory.settings.vectorReindexDone": "Indexed {n} items"
98
+ }
99
+ };
100
+
101
+ function typeLabel(t, type) {
102
+ const key = `memory.tab.${type}`;
103
+ const label = t(key);
104
+ return label && label !== key ? label : String(type);
105
+ }
106
+
107
+ function formatDate(value) {
108
+ if (!value) return "—";
109
+ const date = new Date(value);
110
+ return Number.isNaN(date.getTime()) ? "—" : date.toLocaleString();
111
+ }
112
+
113
+ function MemoryPanel({ t, onClose, embedded }) {
114
+ const [tab, setTab] = useState("all");
115
+ const [query, setQuery] = useState("");
116
+ const [semantic, setSemantic] = useState(false);
117
+ const [vecEnabled, setVecEnabled] = useState(false);
118
+ const [items, setItems] = useState([]);
119
+ const [loading, setLoading] = useState(false);
120
+ const abortRef = useRef(null);
121
+
122
+ useEffect(() => {
123
+ fetch("/api/dsh-mneme/vector-config")
124
+ .then((res) => res.json())
125
+ .then((d) => setVecEnabled(!!d.config?.enabled))
126
+ .catch(() => {});
127
+ }, []);
128
+
129
+ const load = useCallback(async () => {
130
+ abortRef.current?.abort();
131
+ const controller = new AbortController();
132
+ abortRef.current = controller;
133
+ setLoading(true);
134
+ try {
135
+ const params = new URLSearchParams();
136
+ if (tab !== "all") params.set("type", tab);
137
+ const url = query.trim()
138
+ ? `/api/dsh-mneme/search?q=${encodeURIComponent(query.trim())}&mode=${semantic ? "vector" : "auto"}`
139
+ : `/api/dsh-mneme/list?${params.toString()}`;
140
+ const res = await fetch(url, { signal: controller.signal });
141
+ const data = await res.json();
142
+ setItems(data.items || []);
143
+ } catch (error) {
144
+ if (error.name === "AbortError") return;
145
+ setItems([]);
146
+ } finally {
147
+ setLoading(false);
148
+ }
149
+ }, [tab, query, semantic]);
150
+
151
+ useEffect(() => {
152
+ load();
153
+ return () => abortRef.current?.abort();
154
+ }, [load]);
155
+
156
+ const tabs = ["all", "preference", "project", "decision", "history"];
157
+
158
+ const body = react.createElement("div", { style: embedded ? { ...styles.panel, width: "100%", maxWidth: "100%", maxHeight: "none" } : styles.panel },
159
+ !embedded && react.createElement("div", { style: styles.header },
160
+ react.createElement("span", { style: styles.title }, t("memory.panel.title")),
161
+ react.createElement("button", { style: styles.close, onClick: onClose }, "×")
162
+ ),
163
+ react.createElement("div", { style: { display: "flex", gap: 6, alignItems: "center", marginBottom: 12 } },
164
+ react.createElement("input", {
165
+ style: { ...styles.search, flex: 1, marginBottom: 0 },
166
+ placeholder: t("memory.panel.search"),
167
+ value: query,
168
+ onChange: (e) => setQuery(e.target.value)
169
+ }),
170
+ vecEnabled && react.createElement("button", {
171
+ style: { ...styles.tab, ...(semantic ? styles.tabActive : {}) },
172
+ title: t("memory.settings.vectorTitle"),
173
+ onClick: () => setSemantic(!semantic)
174
+ }, t("memory.panel.semantic"))
175
+ ),
176
+ react.createElement("div", { style: styles.tabs },
177
+ tabs.map((key) =>
178
+ react.createElement("button", {
179
+ key,
180
+ style: { ...styles.tab, ...(tab === key ? styles.tabActive : {}) },
181
+ onClick: () => setTab(key)
182
+ }, t(`memory.tab.${key}`))
183
+ )
184
+ ),
185
+ react.createElement("div", { style: styles.list },
186
+ loading
187
+ ? react.createElement("div", { style: styles.hint }, "…")
188
+ : items.length === 0
189
+ ? react.createElement("div", { style: styles.hint }, t("memory.panel.empty"))
190
+ : items.map((item) =>
191
+ react.createElement("div", { key: item.id, style: styles.card },
192
+ react.createElement("div", { style: styles.cardTitle },
193
+ react.createElement("span", null, item.title),
194
+ react.createElement("span", { style: styles.badge },
195
+ `${typeLabel(t, item.type)} · ★${item.importance}`
196
+ )
197
+ ),
198
+ react.createElement("div", { style: styles.cardContent }, item.content),
199
+ react.createElement("div", { style: styles.cardMeta },
200
+ formatDate(item.updated_at)
201
+ )
202
+ )
203
+ )
204
+ )
205
+ );
206
+ return embedded
207
+ ? body
208
+ : createPortal(react.createElement("div", { style: styles.overlay }, body), document.body);
209
+ }
210
+
211
+ const h = react.createElement;
212
+
213
+ // --- Settings panel: user profile, rules, custom commands ---
214
+ function SettingsPanel({ t, onClose, embedded }) {
215
+ const [sectionTab, setSectionTab] = react.useState("settings");
216
+ const [profile, setProfile] = react.useState("");
217
+ const [rules, setRules] = react.useState([]);
218
+ const [commands, setCommands] = react.useState([]);
219
+ const [newRule, setNewRule] = react.useState("");
220
+ const [newCmd, setNewCmd] = react.useState({ name: "", description: "", instruction: "" });
221
+ const [saved, setSaved] = react.useState(false);
222
+ const [cmdError, setCmdError] = react.useState("");
223
+ const [vector, setVector] = react.useState({ enabled: false, baseUrl: "", apiKey: "", model: "" });
224
+ const [vectorSaved, setVectorSaved] = react.useState(false);
225
+ const [reindexing, setReindexing] = react.useState(false);
226
+ const [reindexMsg, setReindexMsg] = react.useState("");
227
+
228
+ const load = react.useCallback(async () => {
229
+ try {
230
+ const [p, r, c, v] = await Promise.all([
231
+ fetch("/api/dsh-mneme/profile").then((res) => res.json()),
232
+ fetch("/api/dsh-mneme/rules").then((res) => res.json()),
233
+ fetch("/api/dsh-mneme/commands").then((res) => res.json()),
234
+ fetch("/api/dsh-mneme/vector-config").then((res) => res.json())
235
+ ]);
236
+ setProfile(p.profile || "");
237
+ setRules(Array.isArray(r.rules) ? r.rules : []);
238
+ setCommands(Array.isArray(c.commands) ? c.commands : []);
239
+ setVector(v.config || { enabled: false, baseUrl: "", apiKey: "", model: "" });
240
+ } catch { /* ignore */ }
241
+ }, []);
242
+
243
+ react.useEffect(() => { load(); }, [load]);
244
+
245
+ async function saveProfile() {
246
+ try {
247
+ await fetch("/api/dsh-mneme/profile", {
248
+ method: "PUT",
249
+ headers: { "Content-Type": "application/json" },
250
+ body: JSON.stringify({ profile })
251
+ });
252
+ setSaved(true);
253
+ setTimeout(() => setSaved(false), 1500);
254
+ } catch { /* ignore */ }
255
+ }
256
+
257
+ async function putRules(next) {
258
+ await fetch("/api/dsh-mneme/rules", {
259
+ method: "PUT",
260
+ headers: { "Content-Type": "application/json" },
261
+ body: JSON.stringify({ rules: next })
262
+ });
263
+ }
264
+
265
+ async function addRule() {
266
+ const text = newRule.trim();
267
+ if (!text) return;
268
+ const next = [...rules, text];
269
+ await putRules(next);
270
+ setRules(next);
271
+ setNewRule("");
272
+ }
273
+
274
+ async function removeRule(index) {
275
+ const next = rules.filter((_, i) => i !== index);
276
+ await putRules(next);
277
+ setRules(next);
278
+ }
279
+
280
+ async function addCommand() {
281
+ const name = newCmd.name.trim();
282
+ const instruction = newCmd.instruction.trim();
283
+ if (!name || !instruction) return;
284
+ try {
285
+ const res = await fetch("/api/dsh-mneme/commands", {
286
+ method: "POST",
287
+ headers: { "Content-Type": "application/json" },
288
+ body: JSON.stringify({ name, description: newCmd.description, instruction })
289
+ });
290
+ const data = await res.json();
291
+ if (!res.ok) { setCmdError(data.error || "failed"); return; }
292
+ setCmdError("");
293
+ setCommands([...commands, data.command]);
294
+ setNewCmd({ name: "", description: "", instruction: "" });
295
+ } catch { setCmdError("failed"); }
296
+ }
297
+
298
+ async function removeCommand(id) {
299
+ await fetch(`/api/dsh-mneme/commands?id=${encodeURIComponent(id)}`, { method: "DELETE" });
300
+ setCommands(commands.filter((c) => c.id !== id));
301
+ }
302
+
303
+ async function saveVector() {
304
+ try {
305
+ await fetch("/api/dsh-mneme/vector-config", {
306
+ method: "PUT",
307
+ headers: { "Content-Type": "application/json" },
308
+ body: JSON.stringify(vector)
309
+ });
310
+ setVectorSaved(true);
311
+ setTimeout(() => setVectorSaved(false), 1500);
312
+ } catch { /* ignore */ }
313
+ }
314
+
315
+ async function reindex() {
316
+ setReindexing(true);
317
+ setReindexMsg("");
318
+ try {
319
+ const res = await fetch("/api/dsh-mneme/vector-reindex");
320
+ const data = await res.json();
321
+ const n = data.indexed ?? 0;
322
+ setReindexMsg(t("memory.settings.vectorReindexDone").replace("{n}", String(n)));
323
+ } catch { setReindexMsg(""); }
324
+ setReindexing(false);
325
+ }
326
+
327
+ const inputStyle = { ...styles.search, marginBottom: 8 };
328
+ const labelStyle = { fontSize: 12, fontWeight: 600, margin: "12px 0 4px" };
329
+ const hintStyle = { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)", marginBottom: 8 };
330
+ const rowStyle = { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, padding: "6px 0", borderBottom: "1px solid var(--dsw-alias-border-l1, #eee)" };
331
+
332
+ const body = h("div", { style: embedded ? { ...styles.panel, width: "100%", maxWidth: "100%", maxHeight: "none" } : styles.panel },
333
+ !embedded && h("div", { style: styles.header },
334
+ h("span", { style: styles.title }, t("memory.settings.title")),
335
+ h("button", { style: styles.close, onClick: onClose }, "×")
336
+ ),
337
+ h("div", { style: styles.tabs },
338
+ h("button", { style: { ...styles.tab, ...(sectionTab === "memory" ? styles.tabActive : {}) }, onClick: () => setSectionTab("memory") }, t("memory.panel.open")),
339
+ h("button", { style: { ...styles.tab, ...(sectionTab === "settings" ? styles.tabActive : {}) }, onClick: () => setSectionTab("settings") }, t("memory.settings.title"))
340
+ ),
341
+ sectionTab === "memory"
342
+ ? h(MemoryPanel, { t, embedded: true })
343
+ : h("div", { style: { overflowY: "auto" } },
344
+ // profile
345
+ h("div", { style: labelStyle }, t("memory.settings.profile")),
346
+ h("div", { style: hintStyle }, t("memory.settings.profileHint")),
347
+ h("textarea", {
348
+ style: { ...inputStyle, minHeight: 72, resize: "both", fontFamily: "inherit" },
349
+ value: profile,
350
+ placeholder: t("memory.settings.profile"),
351
+ onChange: (e) => setProfile(e.target.value)
352
+ }),
353
+ h("div", null,
354
+ h("button", { style: styles.footerButton, onClick: saveProfile }, t("memory.settings.profileSave")),
355
+ saved && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-success, #2a7)" } }, t("memory.settings.profileSaved"))
356
+ ),
357
+ // rules
358
+ h("div", { style: labelStyle }, t("memory.settings.rules")),
359
+ h("div", { style: hintStyle }, t("memory.settings.rulesHint")),
360
+ rules.length === 0 && h("div", { style: styles.hint }, t("memory.settings.empty")),
361
+ rules.map((rule, i) =>
362
+ h("div", { key: i, style: rowStyle },
363
+ h("span", { style: { fontSize: 13, flex: 1 } }, rule),
364
+ h("button", { style: { ...styles.footerButton, color: "var(--dsw-alias-state-error, #c33)" }, onClick: () => removeRule(i) }, "×")
365
+ )
366
+ ),
367
+ h("div", { style: { display: "flex", gap: 6 } },
368
+ h("input", {
369
+ style: { ...inputStyle, flex: 1, marginBottom: 0 },
370
+ value: newRule,
371
+ placeholder: t("memory.settings.rulePlaceholder"),
372
+ onChange: (e) => setNewRule(e.target.value)
373
+ }),
374
+ h("button", { style: styles.footerButton, onClick: addRule }, t("memory.settings.ruleAdd"))
375
+ ),
376
+ // custom commands
377
+ h("div", { style: labelStyle }, t("memory.settings.commands")),
378
+ h("div", { style: hintStyle }, t("memory.settings.commandsHint")),
379
+ commands.length === 0 && h("div", { style: styles.hint }, t("memory.settings.empty")),
380
+ commands.map((cmd) =>
381
+ h("div", { key: cmd.id, style: rowStyle },
382
+ h("div", { style: { flex: 1 } },
383
+ h("div", { style: { fontSize: 13, fontWeight: 600 } }, `/${cmd.name}`),
384
+ h("div", { style: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)" } }, cmd.description || cmd.instruction)
385
+ ),
386
+ h("button", { style: { ...styles.footerButton, color: "var(--dsw-alias-state-error, #c33)" }, onClick: () => removeCommand(cmd.id) }, t("memory.settings.cmdDelete"))
387
+ )
388
+ ),
389
+ h("div", { style: { display: "grid", gap: 6, marginTop: 6 } },
390
+ h("input", { style: { ...inputStyle, marginBottom: 0 }, value: newCmd.name, placeholder: t("memory.settings.cmdName"), onChange: (e) => setNewCmd({ ...newCmd, name: e.target.value }) }),
391
+ h("input", { style: { ...inputStyle, marginBottom: 0 }, value: newCmd.description, placeholder: t("memory.settings.cmdDesc"), onChange: (e) => setNewCmd({ ...newCmd, description: e.target.value }) }),
392
+ h("textarea", { style: { ...inputStyle, marginBottom: 0, minHeight: 48, resize: "both", fontFamily: "inherit" }, value: newCmd.instruction, placeholder: t("memory.settings.cmdInstruction"), onChange: (e) => setNewCmd({ ...newCmd, instruction: e.target.value }) }),
393
+ h("button", { style: styles.footerButton, onClick: addCommand }, t("memory.settings.cmdAdd")),
394
+ cmdError && h("div", { style: { fontSize: 12, color: "var(--dsw-alias-state-error, #c33)" } }, cmdError)
395
+ ),
396
+ // vector search
397
+ h("div", { style: { ...labelStyle, marginTop: 20 } }, t("memory.settings.vectorTitle")),
398
+ h("div", { style: hintStyle }, t("memory.settings.vectorHint")),
399
+ h("label", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 8, fontSize: 13 } },
400
+ h("input", { type: "checkbox", checked: !!vector.enabled, onChange: (e) => setVector({ ...vector, enabled: e.target.checked }) }),
401
+ h("span", null, t("memory.settings.vectorEnabled"))
402
+ ),
403
+ h("input", { style: inputStyle, value: vector.baseUrl, placeholder: t("memory.settings.vectorBaseUrl"), onChange: (e) => setVector({ ...vector, baseUrl: e.target.value }) }),
404
+ h("input", { style: inputStyle, type: "password", value: vector.apiKey, placeholder: t("memory.settings.vectorApiKey"), onChange: (e) => setVector({ ...vector, apiKey: e.target.value }) }),
405
+ h("input", { style: inputStyle, value: vector.model, placeholder: t("memory.settings.vectorModel"), onChange: (e) => setVector({ ...vector, model: e.target.value }) }),
406
+ h("div", { style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" } },
407
+ h("button", { style: styles.footerButton, onClick: saveVector }, t("memory.settings.vectorSave")),
408
+ vectorSaved && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-success, #2a7)" } }, t("memory.settings.vectorSaved")),
409
+ h("button", { style: styles.footerButton, onClick: reindex, disabled: reindexing }, reindexing ? t("memory.settings.vectorReindexing") : t("memory.settings.vectorReindex")),
410
+ reindexMsg && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #666)" } }, reindexMsg)
411
+ )
412
+ )
413
+ );
414
+ return embedded
415
+ ? body
416
+ : createPortal(h("div", { style: styles.overlay }, body), document.body);
417
+ }
418
+
419
+ const styles = {
420
+ overlay: { position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)", zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center" },
421
+ panel: { background: "var(--dsw-alias-bg-base, #fff)", borderRadius: 12, width: 640, maxWidth: "90vw", maxHeight: "80vh", display: "flex", flexDirection: "column", padding: 16, boxShadow: "0 8px 40px rgba(0,0,0,0.2)" },
422
+ header: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 },
423
+ title: { fontSize: 16, fontWeight: 600 },
424
+ close: { border: "none", background: "none", fontSize: 20, cursor: "pointer", color: "var(--dsw-alias-label-secondary, #666)" },
425
+ search: { padding: "8px 12px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2, #ddd)", marginBottom: 12, fontSize: 14 },
426
+ tabs: { display: "flex", gap: 6, marginBottom: 12, flexWrap: "wrap" },
427
+ tab: { padding: "4px 10px", borderRadius: 999, border: "1px solid var(--dsw-alias-border-l2, #ddd)", background: "none", cursor: "pointer", fontSize: 12 },
428
+ tabActive: { background: "var(--dsw-alias-interactive-bg-active, #eee)" },
429
+ list: { overflowY: "auto", display: "flex", flexDirection: "column", gap: 8 },
430
+ hint: { color: "var(--dsw-alias-label-tertiary, #999)", padding: "24px 0", textAlign: "center" },
431
+ card: { border: "1px solid var(--dsw-alias-border-l1, #eee)", borderRadius: 8, padding: "10px 12px" },
432
+ cardTitle: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4, fontSize: 14, fontWeight: 600 },
433
+ badge: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)" },
434
+ cardContent: { fontSize: 13, color: "var(--dsw-alias-label-secondary, #555)", marginBottom: 4, whiteSpace: "pre-wrap", wordBreak: "break-word" },
435
+ cardMeta: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #999)" },
436
+ footerButton: { padding: "4px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2, #ddd)", background: "none", cursor: "pointer", fontSize: 12, margin: "2px 8px" },
437
+ footerButtonActive: { background: "var(--dsw-alias-interactive-bg-active, #eee)" }
438
+ };
439
+
440
+ function apply(ctx) {
441
+ ctx.effect(() => ctx.locale.register(NS, dictionaries), "dsh-mneme: dictionaries");
442
+
443
+ // Register a custom section inside the DSH settings panel (nav label +
444
+ // embedded memory/settings content). Access is via the official settings
445
+ // panel only; no sidebar buttons are registered.
446
+ ctx.effect(() => {
447
+ const t = ctx.locale.bind(NS);
448
+ return ctx.slots.register({
449
+ name: "settings.section",
450
+ id: "dsh-mneme",
451
+ label: () => t("memory.settings.title"),
452
+ inject: () => ({})
453
+ }, () => react.createElement(SettingsPanel, { t, embedded: true, onClose: () => {} }));
454
+ }, "dsh-mneme: settings section");
455
+ }
456
+
457
+ exports.apply = apply;
458
+ exports.inject = inject;
459
+ return module.exports;
460
+ }
461
+ });