dsh-engram 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 ADDED
@@ -0,0 +1,1438 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-engram",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
+
25
+ // client/src/entry.tsx
26
+ var entry_exports = {};
27
+ __export(entry_exports, {
28
+ apply: () => apply,
29
+ en: () => en,
30
+ inject: () => inject,
31
+ zh: () => zh
32
+ });
33
+ module.exports = __toCommonJS(entry_exports);
34
+
35
+ // client/src/api.ts
36
+ var API_PREFIX = "/api/dsh-engram";
37
+ var EngramApiError = class extends Error {
38
+ constructor(message) {
39
+ super(message);
40
+ this.name = "EngramApiError";
41
+ }
42
+ };
43
+ async function readJson(response) {
44
+ let body;
45
+ try {
46
+ body = await response.json();
47
+ } catch {
48
+ throw new EngramApiError(`HTTP ${response.status}: invalid JSON response`);
49
+ }
50
+ if (!response.ok) {
51
+ const message = typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
52
+ throw new EngramApiError(message);
53
+ }
54
+ return body;
55
+ }
56
+ function query(params) {
57
+ const search = new URLSearchParams();
58
+ for (const [key, value] of Object.entries(params)) {
59
+ if (value !== void 0 && value !== "") search.set(key, String(value));
60
+ }
61
+ const text = search.toString();
62
+ return text === "" ? "" : `?${text}`;
63
+ }
64
+ var EngramApi = class {
65
+ async overview() {
66
+ return readJson(await fetch(`${API_PREFIX}/overview`));
67
+ }
68
+ async memories(opts = {}) {
69
+ return readJson(
70
+ await fetch(`${API_PREFIX}/memories${query({ workspace: opts.workspace, q: opts.q, kind: opts.kind, status: opts.status, limit: opts.limit })}`)
71
+ );
72
+ }
73
+ async tasks(workspace, includeStable = false) {
74
+ return readJson(await fetch(`${API_PREFIX}/tasks${query({ workspace, includeStable: includeStable ? "1" : void 0 })}`));
75
+ }
76
+ async createTask(workspace, name, description = "") {
77
+ return readJson(
78
+ await fetch(`${API_PREFIX}/tasks`, {
79
+ method: "POST",
80
+ headers: { "content-type": "application/json" },
81
+ body: JSON.stringify({ workspace, name, description })
82
+ })
83
+ );
84
+ }
85
+ async closeTask(workspace, id, evidence) {
86
+ return readJson(
87
+ await fetch(`${API_PREFIX}/tasks/close`, {
88
+ method: "POST",
89
+ headers: { "content-type": "application/json" },
90
+ body: JSON.stringify({
91
+ workspace,
92
+ id,
93
+ artifact: evidence.artifact ?? "",
94
+ evaluation: evidence.evaluation ?? "",
95
+ memory_refs: evidence.memoryRefs ?? []
96
+ })
97
+ })
98
+ );
99
+ }
100
+ async links(workspace) {
101
+ return readJson(await fetch(`${API_PREFIX}/links${query({ workspace })}`));
102
+ }
103
+ async nodes(workspace) {
104
+ return readJson(await fetch(`${API_PREFIX}/nodes${query({ workspace })}`));
105
+ }
106
+ async stats(workspace) {
107
+ return readJson(await fetch(`${API_PREFIX}/stats${query({ workspace })}`));
108
+ }
109
+ async config() {
110
+ return readJson(await fetch(`${API_PREFIX}/config`));
111
+ }
112
+ async archive(id, workspace) {
113
+ await readJson(
114
+ await fetch(`${API_PREFIX}/memories/archive`, {
115
+ method: "POST",
116
+ headers: { "content-type": "application/json" },
117
+ body: JSON.stringify({ id, workspace })
118
+ })
119
+ );
120
+ }
121
+ async remove(id, workspace) {
122
+ await readJson(
123
+ await fetch(`${API_PREFIX}/memories/delete`, {
124
+ method: "POST",
125
+ headers: { "content-type": "application/json" },
126
+ body: JSON.stringify({ id, workspace })
127
+ })
128
+ );
129
+ }
130
+ async gc(workspace, dryRun) {
131
+ return readJson(
132
+ await fetch(`${API_PREFIX}/gc`, {
133
+ method: "POST",
134
+ headers: { "content-type": "application/json" },
135
+ body: JSON.stringify({
136
+ ...workspace ? { workspace } : {},
137
+ dryRun
138
+ })
139
+ })
140
+ );
141
+ }
142
+ };
143
+
144
+ // client/src/EngramSection.tsx
145
+ var import_react2 = require("react");
146
+
147
+ // client/src/theme.ts
148
+ var import_react = require("react");
149
+ var LIGHT = {
150
+ text: "#111827",
151
+ surface: "#ffffff",
152
+ border: "#e5e7eb",
153
+ muted: "#6b7280",
154
+ mutedStrong: "#4b5563",
155
+ mutedWeak: "#9ca3af",
156
+ hoverBg: "#f3f4f6",
157
+ primary: "#2563eb"
158
+ };
159
+ var DARK = {
160
+ text: "#e6e9ef",
161
+ surface: "#1c222b",
162
+ border: "#37404e",
163
+ muted: "#9aa4b5",
164
+ mutedStrong: "#c9d0dc",
165
+ mutedWeak: "#7d8796",
166
+ hoverBg: "rgba(255,255,255,0.07)",
167
+ primary: "#5b8cff"
168
+ };
169
+ function detectDark() {
170
+ if (typeof window === "undefined") return false;
171
+ const cs = getComputedStyle(document.documentElement).colorScheme;
172
+ if (cs === "dark") return true;
173
+ if (cs === "light") return false;
174
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
175
+ }
176
+ function useEngramTheme() {
177
+ const [dark, setDark] = (0, import_react.useState)(detectDark);
178
+ (0, import_react.useEffect)(() => {
179
+ const apply2 = () => setDark(detectDark());
180
+ const mo = new MutationObserver(apply2);
181
+ mo.observe(document.documentElement, {
182
+ attributes: true,
183
+ attributeFilter: ["style", "class", "data-theme"]
184
+ });
185
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
186
+ mq.addEventListener?.("change", apply2);
187
+ apply2();
188
+ return () => {
189
+ mo.disconnect();
190
+ mq.removeEventListener?.("change", apply2);
191
+ };
192
+ }, []);
193
+ const p = dark ? DARK : LIGHT;
194
+ const vars = {
195
+ color: p.text,
196
+ "--dsh-color-surface": p.surface,
197
+ "--dsh-color-border": p.border,
198
+ "--dsh-color-muted": p.muted,
199
+ "--dsh-color-muted-strong": p.mutedStrong,
200
+ "--dsh-color-muted-weak": p.mutedWeak,
201
+ "--dsh-color-hover-bg": p.hoverBg,
202
+ "--dsh-color-primary": p.primary
203
+ };
204
+ return { dark, vars };
205
+ }
206
+
207
+ // client/src/EngramSection.tsx
208
+ var import_jsx_runtime = require("react/jsx-runtime");
209
+ var KIND_COLORS = {
210
+ decision: "#2563eb",
211
+ error: "#dc2626",
212
+ procedure: "#7c3aed",
213
+ fact: "#059669",
214
+ insight: "#d97706",
215
+ handoff: "#0891b2",
216
+ task: "#4f46e5"
217
+ };
218
+ var KIND_LABEL = {
219
+ decision: "\u51B3\u5B9A",
220
+ error: "\u9519\u8BEF",
221
+ procedure: "\u6D41\u7A0B",
222
+ fact: "\u4E8B\u5B9E",
223
+ insight: "\u6D1E\u5BDF",
224
+ handoff: "\u4EA4\u63A5",
225
+ task: "\u4EFB\u52A1"
226
+ };
227
+ var s = {
228
+ root: { padding: "2px 4px 40px" },
229
+ h1: { fontSize: 20, fontWeight: 700, margin: "0 0 4px" },
230
+ sub: { color: "var(--dsh-color-muted, #6b7280)", fontSize: 12, margin: "0 0 16px" },
231
+ stats: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", gap: 8, marginBottom: 16 },
232
+ card: {
233
+ border: "1px solid var(--dsh-color-border, #e5e7eb)",
234
+ borderRadius: 8,
235
+ padding: "10px 12px",
236
+ background: "var(--dsh-color-surface, #ffffff)"
237
+ },
238
+ cardNum: { fontSize: 22, fontWeight: 700, lineHeight: 1.2 },
239
+ cardLabel: { color: "var(--dsh-color-muted, #6b7280)", fontSize: 11, marginTop: 2 },
240
+ row: { display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center", marginBottom: 12 },
241
+ input: {
242
+ border: "1px solid var(--dsh-color-border, #d1d5db)",
243
+ borderRadius: 6,
244
+ padding: "5px 8px",
245
+ fontSize: 13,
246
+ background: "var(--dsh-color-surface, #fff)",
247
+ color: "inherit"
248
+ },
249
+ btn: {
250
+ border: "1px solid var(--dsh-color-border, #d1d5db)",
251
+ borderRadius: 6,
252
+ padding: "5px 10px",
253
+ fontSize: 12,
254
+ cursor: "pointer",
255
+ background: "var(--dsh-color-surface, #fff)"
256
+ },
257
+ btnPrimary: {
258
+ border: "1px solid var(--dsh-color-primary, #2563eb)",
259
+ borderRadius: 6,
260
+ padding: "5px 10px",
261
+ fontSize: 12,
262
+ cursor: "pointer",
263
+ color: "#fff",
264
+ background: "var(--dsh-color-primary, #2563eb)"
265
+ },
266
+ table: { width: "100%", borderCollapse: "collapse", fontSize: 12.5, tableLayout: "fixed" },
267
+ clamp3: { display: "-webkit-box", WebkitLineClamp: 3, WebkitBoxOrient: "vertical", overflow: "hidden", maxHeight: 60, minHeight: 18, lineHeight: 1.5, wordBreak: "break-word", whiteSpace: "normal" },
268
+ expanded: { whiteSpace: "pre-wrap", wordBreak: "break-word", lineHeight: 1.5 },
269
+ linkBtn: { border: "none", background: "none", padding: 0, fontSize: 11.5, cursor: "pointer", color: "var(--dsh-color-primary, #2563eb)", textDecoration: "underline" },
270
+ pageBar: { display: "flex", alignItems: "center", gap: 8, marginTop: 8, fontSize: 12.5, color: "var(--dsh-color-muted, #6b7280)", flexWrap: "wrap" },
271
+ tabBar: { display: "flex", gap: 4, borderBottom: "1px solid var(--dsh-color-border, #e5e7eb)", marginBottom: 12 },
272
+ tab: { border: "none", background: "none", padding: "8px 14px", fontSize: 13, cursor: "pointer", color: "var(--dsh-color-muted, #6b7280)", borderBottom: "2px solid transparent", fontWeight: 600, margin: 0 },
273
+ tabActive: { border: "none", background: "none", padding: "8px 14px", fontSize: 13, cursor: "pointer", color: "var(--dsh-color-primary, #2563eb)", borderBottom: "2px solid var(--dsh-color-primary, #2563eb)", fontWeight: 700, margin: 0 },
274
+ th: {
275
+ textAlign: "left",
276
+ padding: "6px 8px",
277
+ borderBottom: "1px solid var(--dsh-color-border, #e5e7eb)",
278
+ color: "var(--dsh-color-muted, #6b7280)",
279
+ fontWeight: 600,
280
+ whiteSpace: "nowrap"
281
+ },
282
+ td: { padding: "6px 8px", borderBottom: "1px solid var(--dsh-color-border, #f3f4f6)", verticalAlign: "top" },
283
+ wsHead: {
284
+ padding: "8px 8px 4px",
285
+ fontSize: 12.5,
286
+ fontWeight: 600,
287
+ color: "var(--dsh-color-muted, #4b5563)",
288
+ background: "var(--dsh-color-hover-bg, #f3f4f6)",
289
+ borderBottom: "1px solid var(--dsh-color-border, #e5e7eb)"
290
+ },
291
+ badge: { display: "inline-block", borderRadius: 5, padding: "1px 6px", fontSize: 11, color: "#fff", whiteSpace: "nowrap" },
292
+ tag: {
293
+ display: "inline-block",
294
+ borderRadius: 4,
295
+ padding: "0 5px",
296
+ fontSize: 11,
297
+ marginRight: 4,
298
+ background: "var(--dsh-color-hover-bg, #f3f4f6)"
299
+ },
300
+ panelTitle: { fontSize: 14, fontWeight: 700, margin: "18px 0 6px" },
301
+ groupLabel: { fontSize: 12.5, fontWeight: 600, color: "var(--dsh-color-muted, #4b5563)", margin: "8px 0 4px" },
302
+ mono: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11.5, color: "var(--dsh-color-muted, #4b5563)" },
303
+ empty: { color: "var(--dsh-color-muted, #9ca3af)", fontSize: 12.5, padding: "14px 4px" },
304
+ error: { color: "#dc2626", fontSize: 12.5, marginBottom: 10 },
305
+ subPanel: { border: "1px solid var(--dsh-color-border, #e5e7eb)", borderRadius: 8, padding: "10px 12px", marginBottom: 10 }
306
+ };
307
+ function fmtDate(ts) {
308
+ const d = new Date(ts);
309
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
310
+ const dd = String(d.getDate()).padStart(2, "0");
311
+ return `${mm}-${dd} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
312
+ }
313
+ var MEM_PAGE_SIZE = 10;
314
+ var pct = (n) => n === null || n === void 0 ? "\u2013" : `${(n * 100).toFixed(1)}%`;
315
+ function daysLeft(ts) {
316
+ if (ts === null || ts === void 0) return "\u221E";
317
+ const days = Math.ceil((ts - Date.now()) / 864e5);
318
+ return days > 0 ? `${days}d` : "expired";
319
+ }
320
+ function StatCard({ num, label }) {
321
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.card, children: [
322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.cardNum, children: num }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.cardLabel, children: label })
324
+ ] });
325
+ }
326
+ function taskGaps(t) {
327
+ const gaps = [];
328
+ if (!t.artifact) gaps.push("artifact");
329
+ if (!t.evaluation) gaps.push("evaluation");
330
+ if (!t.memoryRefs || t.memoryRefs.length === 0) gaps.push("memory_ref");
331
+ return gaps;
332
+ }
333
+ function groupByWorkspace(items) {
334
+ const groups = /* @__PURE__ */ new Map();
335
+ for (const m of items) {
336
+ const list = groups.get(m.workspace) ?? [];
337
+ list.push(m);
338
+ groups.set(m.workspace, list);
339
+ }
340
+ return [...groups.entries()].sort(
341
+ (a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0])
342
+ );
343
+ }
344
+ function EngramSection({ api, t }) {
345
+ const [overview, setOverview] = (0, import_react2.useState)(null);
346
+ const [memories, setMemories] = (0, import_react2.useState)([]);
347
+ const [tasks, setTasks] = (0, import_react2.useState)([]);
348
+ const [links, setLinks] = (0, import_react2.useState)([]);
349
+ const [nodes, setNodes] = (0, import_react2.useState)([]);
350
+ const [usageStats, setUsageStats] = (0, import_react2.useState)(null);
351
+ const [error, setError] = (0, import_react2.useState)(null);
352
+ const [workspace, setWorkspace] = (0, import_react2.useState)("");
353
+ const [kind, setKind] = (0, import_react2.useState)("");
354
+ const [status, setStatus] = (0, import_react2.useState)("active");
355
+ const [q, setQ] = (0, import_react2.useState)("");
356
+ const [busy, setBusy] = (0, import_react2.useState)(false);
357
+ const [memPage, setMemPage] = (0, import_react2.useState)(0);
358
+ const [expandedRows, setExpandedRows] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
359
+ const [view, setView] = (0, import_react2.useState)("mem");
360
+ const [gcDryRun, setGcDryRun] = (0, import_react2.useState)(true);
361
+ const [gcReport, setGcReport] = (0, import_react2.useState)(null);
362
+ const [gcRunning, setGcRunning] = (0, import_react2.useState)(false);
363
+ const [newTaskWs, setNewTaskWs] = (0, import_react2.useState)("");
364
+ const [newTaskName, setNewTaskName] = (0, import_react2.useState)("");
365
+ const [newTaskDesc, setNewTaskDesc] = (0, import_react2.useState)("");
366
+ const [newTaskBusy, setNewTaskBusy] = (0, import_react2.useState)(false);
367
+ const [closeFor, setCloseFor] = (0, import_react2.useState)(null);
368
+ const [closeArtifact, setCloseArtifact] = (0, import_react2.useState)("");
369
+ const [closeEval, setCloseEval] = (0, import_react2.useState)("");
370
+ const [closeRefs, setCloseRefs] = (0, import_react2.useState)("");
371
+ const [closeBusy, setCloseBusy] = (0, import_react2.useState)(false);
372
+ const refresh = (0, import_react2.useCallback)(async () => {
373
+ setBusy(true);
374
+ setError(null);
375
+ try {
376
+ const ov = await api.overview();
377
+ setOverview(ov);
378
+ const wsList = workspace ? [workspace] : Object.keys(ov.workspaces);
379
+ const [mem, taskGroups2, linkGroups2, nodeGroups2, st] = await Promise.all([
380
+ api.memories({
381
+ workspace: workspace || void 0,
382
+ q: q || void 0,
383
+ kind: kind || void 0,
384
+ status: status === "all" ? void 0 : status
385
+ }),
386
+ Promise.all(wsList.map((w) => api.tasks(w, true))),
387
+ Promise.all(wsList.map((w) => api.links(w))),
388
+ Promise.all(wsList.map((w) => api.nodes(w))),
389
+ api.stats(workspace || void 0)
390
+ ]);
391
+ setMemories(mem.items);
392
+ setTasks(taskGroups2.flatMap((x) => x.items));
393
+ setLinks(linkGroups2.flatMap((x) => x.items));
394
+ setNodes(nodeGroups2.flatMap((x) => x.items));
395
+ setUsageStats(st);
396
+ } catch (e) {
397
+ setError(e instanceof Error ? e.message : String(e));
398
+ } finally {
399
+ setBusy(false);
400
+ }
401
+ }, [api, workspace, q, kind, status]);
402
+ (0, import_react2.useEffect)(() => {
403
+ void refresh();
404
+ }, [refresh]);
405
+ const workspaces = (0, import_react2.useMemo)(() => overview ? Object.keys(overview.workspaces) : [], [overview]);
406
+ const kindsPresent = (0, import_react2.useMemo)(() => overview ? Object.keys(overview.kinds) : [], [overview]);
407
+ (0, import_react2.useEffect)(() => {
408
+ if (!newTaskWs && workspaces.length > 0 && !workspace) setNewTaskWs(workspaces[0]);
409
+ }, [newTaskWs, workspaces, workspace]);
410
+ const cfg = overview?.config ?? null;
411
+ const act = async (fn) => {
412
+ try {
413
+ await fn();
414
+ await refresh();
415
+ } catch (e) {
416
+ setError(e instanceof Error ? e.message : String(e));
417
+ }
418
+ };
419
+ const toggleExpand = (id) => {
420
+ setExpandedRows((prev) => {
421
+ const next = new Set(prev);
422
+ if (next.has(id)) next.delete(id);
423
+ else next.add(id);
424
+ return next;
425
+ });
426
+ };
427
+ const runGc = async () => {
428
+ setGcRunning(true);
429
+ setError(null);
430
+ try {
431
+ const { report } = await api.gc(workspace || void 0, gcDryRun);
432
+ setGcReport(report);
433
+ if (!gcDryRun) await refresh();
434
+ } catch (e) {
435
+ setError(e instanceof Error ? e.message : String(e));
436
+ } finally {
437
+ setGcRunning(false);
438
+ }
439
+ };
440
+ const goWorkspace = (dir) => {
441
+ if (!overview || workspace === "") return;
442
+ const list = Object.keys(overview.workspaces);
443
+ if (list.length === 0) return;
444
+ const idx = list.indexOf(workspace);
445
+ const next = list[(idx + dir + list.length) % list.length];
446
+ setWorkspace(next);
447
+ };
448
+ const groupedRows = workspace === "" ? groupByWorkspace(memories) : [[workspace, memories]];
449
+ const taskGroups = workspace === "" ? groupByWorkspace(tasks) : [[workspace, tasks]];
450
+ const linkGroups = workspace === "" ? groupByWorkspace(links) : [[workspace, links]];
451
+ const nodeGroups = workspace === "" ? groupByWorkspace(nodes) : [[workspace, nodes]];
452
+ const flatRows = [];
453
+ for (const [ws, items] of groupedRows) {
454
+ if (workspace === "") flatRows.push({ kind: "head", ws, count: items.length });
455
+ for (const m of items) flatRows.push({ kind: "row", m });
456
+ }
457
+ const memPageCount = Math.max(1, Math.ceil(flatRows.length / MEM_PAGE_SIZE));
458
+ const memPageSafe = Math.min(memPage, memPageCount - 1);
459
+ const memPageRows = flatRows.slice(memPageSafe * MEM_PAGE_SIZE, (memPageSafe + 1) * MEM_PAGE_SIZE);
460
+ (0, import_react2.useEffect)(() => {
461
+ setMemPage(0);
462
+ }, [workspace, status, kind, q]);
463
+ (0, import_react2.useEffect)(() => {
464
+ if (memPage >= memPageCount) setMemPage(Math.max(0, memPageCount - 1));
465
+ }, [memPage, memPageCount]);
466
+ const createNewTask = async () => {
467
+ const ws = newTaskWs || workspace || workspaces[0] || "";
468
+ if (!ws || newTaskName.trim() === "") return;
469
+ setNewTaskBusy(true);
470
+ setError(null);
471
+ try {
472
+ await api.createTask(ws, newTaskName.trim(), newTaskDesc);
473
+ setNewTaskName("");
474
+ setNewTaskDesc("");
475
+ await refresh();
476
+ } catch (e) {
477
+ setError(e instanceof Error ? e.message : String(e));
478
+ } finally {
479
+ setNewTaskBusy(false);
480
+ }
481
+ };
482
+ const submitClose = async (task) => {
483
+ setCloseBusy(true);
484
+ setError(null);
485
+ try {
486
+ const refs = closeRefs.split(/[,\s]+/).map((x) => x.trim()).filter(Boolean);
487
+ const out = await api.closeTask(task.workspace, task.id, {
488
+ artifact: closeArtifact,
489
+ evaluation: closeEval,
490
+ memoryRefs: refs
491
+ });
492
+ if (out.state === "active") {
493
+ setError(`\u8BC1\u636E\u4ECD\u6709\u7F3A\u53E3\uFF1A${(out.gaps ?? []).join(", ")} \u2014 \u4EFB\u52A1\u4FDD\u6301 ACTIVE\uFF0C\u8865\u9F50\u540E\u518D\u63D0\u4EA4`);
494
+ }
495
+ setCloseFor(null);
496
+ setCloseArtifact("");
497
+ setCloseEval("");
498
+ setCloseRefs("");
499
+ await refresh();
500
+ } catch (e) {
501
+ setError(e instanceof Error ? e.message : String(e));
502
+ } finally {
503
+ setCloseBusy(false);
504
+ }
505
+ };
506
+ const indexCost = workspace && overview ? overview.indexes[workspace] : null;
507
+ const gc = overview?.gc ?? null;
508
+ const wsCounts = workspace && overview ? overview.workspaces[workspace] ?? null : null;
509
+ const { vars } = useEngramTheme();
510
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...s.root, ...vars }, children: [
511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { style: s.h1, children: "Engram \u8BB0\u5FC6" }),
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: s.sub, children: "\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6 \xB7 \u96F6 LLM \u81EA\u52A8\u6355\u83B7 \xB7 \u7B26\u53F7\u7D22\u5F15\u6E10\u8FDB\u62AB\u9732 \u2014 \u6570\u636E\u6E90 ~/.dsh/storages/dsh_engram.json" }),
513
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.error, children: [
514
+ t("error"),
515
+ ": ",
516
+ error
517
+ ] }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.tabBar, children: [
519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: view === "mem" ? s.tabActive : s.tab, onClick: () => setView("mem"), children: "\u8BB0\u5FC6" }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: view === "esr" ? s.tabActive : s.tab, onClick: () => setView("esr"), children: "ESR\uFF08\u4EFB\u52A1 \xB7 \u8282\u70B9 \xB7 \u5173\u7CFB\uFF09" })
521
+ ] }),
522
+ view === "mem" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
523
+ overview && (() => {
524
+ const memNum = wsCounts ? wsCounts.memories : overview.totals.memories;
525
+ const taskNum = wsCounts ? wsCounts.tasks : overview.totals.tasks;
526
+ const linkNum = wsCounts ? wsCounts.links : overview.totals.links;
527
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.stats, children: [
528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(memNum), label: wsCounts ? "\u8BB0\u5FC6 (active)" : "\u8BB0\u5FC6 (active, \u5168\u5C40)" }),
529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(taskNum), label: wsCounts ? "\u4EFB\u52A1 (active)" : "\u4EFB\u52A1 (active, \u5168\u5C40)" }),
530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(linkNum), label: wsCounts ? "\u5173\u7CFB" : "\u5173\u7CFB (\u5168\u5C40)" }),
531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(wsCounts ? wsCounts.nodes ?? 0 : overview.totals.nodes ?? 0), label: wsCounts ? "\u8282\u70B9" : "\u8282\u70B9 (\u5168\u5C40)" }),
532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(workspaces.length), label: "\u5DE5\u4F5C\u533A" }),
533
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(overview.captures.total), label: "\u81EA\u52A8\u6355\u83B7" }),
534
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
535
+ StatCard,
536
+ {
537
+ num: indexCost ? `~${indexCost.tokens}` : "\u2013",
538
+ label: "[ENGRAM] \u7D22\u5F15 token / \u5DE5\u4F5C\u533A"
539
+ }
540
+ ),
541
+ gc && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
542
+ StatCard,
543
+ {
544
+ num: String(gc.archivedMemories + gc.archivedTasks),
545
+ label: `GC \u5DF2\u5F52\u6863 \xB7 \u94FE\u63A5-${gc.removedLinks}`
546
+ }
547
+ )
548
+ ] });
549
+ })(),
550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.subPanel, children: [
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }, children: [
552
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 700, fontSize: 13 }, children: "\u8BB0\u5FC6 GC\uFF08pi-esr \u7EA6\u675F\uFF09" }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { fontSize: 12, display: "inline-flex", gap: 4, alignItems: "center" }, children: [
554
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: gcDryRun, onChange: (e) => setGcDryRun(e.target.checked) }),
555
+ "\u4EC5\u9884\u89C8\uFF08dry run\uFF09"
556
+ ] }),
557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btnPrimary, onClick: () => void runGc(), disabled: gcRunning || !workspace, children: gcRunning ? "\u2026" : "\u8FD0\u884C GC" }),
558
+ gc && gc.lastRun > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.mono, children: [
559
+ "\u4E0A\u6B21 ",
560
+ fmtDate(gc.lastRun)
561
+ ] })
562
+ ] }),
563
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11.5, color: "var(--dsh-color-muted, #6b7280)", marginTop: 4 }, children: "\u5DE5\u4F5C\u96C6\uFF08active \u4EFB\u52A1\u5F15\u7528 / \u4EFB\u52A1\u8BB0\u5FC6 / \u5DF2\u5165\u7D22\u5F15\u547D\u4E2D\uFF09\u6C38\u4E0D\u9A71\u9010\uFF1BTTL \u8FC7\u671F\u5F52\u6863\u3001\u8D85\u5BB9\u91CF\u6DD8\u6C70\u3001stable \u4EFB\u52A1\u8D85\u7A97\u5F52\u6863\u3001\u60AC\u7A7A\u94FE\u63A5\u6E05\u7406\u3002\u53EA\u5F52\u6863\u4E0D\u786C\u5220\u2014\u2014\u6761\u76EE id \u4FDD\u6301\u53EF\u91CD\u53D6\u3002" }),
564
+ gcReport && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 8, fontSize: 12.5 }, children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
566
+ gcReport.dryRun ? "dry-run \u9884\u89C8\uFF1A" : "\u5DF2\u6267\u884C\uFF1A",
567
+ " ",
568
+ "\u5F52\u6863\u8BB0\u5FC6 ",
569
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: gcReport.archivedMemories.length }),
570
+ " \xB7 \u5F52\u6863\u4EFB\u52A1 ",
571
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: gcReport.archivedTasks.length }),
572
+ " \xB7 \u6E05\u7406\u94FE\u63A5 ",
573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: gcReport.removedLinks.length }),
574
+ " \xB7 \u4FDD\u62A4 ",
575
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: gcReport.protectedMemories })
576
+ ] }),
577
+ gcReport.archivedMemories.slice(0, 5).map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.mono, children: [
578
+ "- ",
579
+ e.id.slice(0, 8),
580
+ " ",
581
+ e.reason,
582
+ ": ",
583
+ e.text
584
+ ] }, e.id)),
585
+ gcReport.archivedTasks.slice(0, 3).map((t2) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.mono, children: [
586
+ "- ",
587
+ t2.id.slice(0, 6),
588
+ " ",
589
+ t2.reason,
590
+ ": ",
591
+ t2.name
592
+ ] }, t2.id)),
593
+ gcReport.removedLinks.slice(0, 3).map((l) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.mono, children: [
594
+ "- link ",
595
+ l.source.slice(0, 8),
596
+ " --",
597
+ l.relation,
598
+ "--> ",
599
+ l.target.slice(0, 8)
600
+ ] }, l.id))
601
+ ] })
602
+ ] }),
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.row, children: [
604
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { style: s.input, value: workspace, onChange: (e) => setWorkspace(e.target.value), children: [
605
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8\u5DE5\u4F5C\u533A" }),
606
+ workspaces.map((ws) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: ws, children: [
607
+ ws,
608
+ "\uFF08",
609
+ overview?.workspaces[ws]?.memories ?? 0,
610
+ " \u6761\uFF09"
611
+ ] }, ws))
612
+ ] }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, disabled: workspace === "" || workspaces.length === 0, onClick: () => goWorkspace(-1), children: "\u2039 \u4E0A\u4E00\u5DE5\u4F5C\u533A" }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, disabled: workspace === "" || workspaces.length === 0, onClick: () => goWorkspace(1), children: "\u4E0B\u4E00\u5DE5\u4F5C\u533A \u203A" }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { style: s.input, value: kind, onChange: (e) => setKind(e.target.value), children: [
616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8\u7C7B\u578B" }),
617
+ kindsPresent.map((k) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: k, children: KIND_LABEL[k] ?? k }, k))
618
+ ] }),
619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { style: s.input, value: status, onChange: (e) => setStatus(e.target.value), children: [
620
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "active", children: "\u4EC5\u6D3B\u52A8" }),
621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: "\u5168\u90E8\u72B6\u6001" }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "archived", children: "\u5DF2\u5F52\u6863" })
623
+ ] }),
624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
625
+ "input",
626
+ {
627
+ style: { ...s.input, width: 180 },
628
+ placeholder: "\u641C\u7D22\u8BB0\u5FC6\u2026",
629
+ value: q,
630
+ onChange: (e) => setQ(e.target.value),
631
+ onKeyDown: (e) => {
632
+ if (e.key === "Enter") void refresh();
633
+ }
634
+ }
635
+ ),
636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btnPrimary, onClick: () => void refresh(), disabled: busy, children: busy ? "\u2026" : t("refresh") })
637
+ ] }),
638
+ cfg && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.row, children: [
639
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
640
+ "autoCapture ",
641
+ cfg.autoCapture ? "on" : "off"
642
+ ] }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
644
+ "sessionSearch ",
645
+ cfg.sessionSearch ? "on" : "off"
646
+ ] }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
648
+ "TTL ",
649
+ cfg.expireDays,
650
+ "d"
651
+ ] }),
652
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
653
+ "index ",
654
+ cfg.indexMaxLines,
655
+ " \u884C / ",
656
+ cfg.indexMaxChars,
657
+ " \u5B57\u7B26"
658
+ ] }),
659
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
660
+ "promote \u2265",
661
+ cfg.promoteHits,
662
+ " hits"
663
+ ] })
664
+ ] }),
665
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: s.table, children: [
666
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("colgroup", { children: [
667
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("col", { style: { width: 58 } }),
668
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("col", {}),
669
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("col", { style: { width: 72 } })
670
+ ] }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: s.th, children: "\u7C7B\u578B" }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: s.th, children: "\u5185\u5BB9" }),
674
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: s.th })
675
+ ] }) }),
676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tbody", { children: [
677
+ flatRows.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { colSpan: 3, style: s.empty, children: "\u6682\u65E0\u8BB0\u5FC6 \u2014 \u4F7F\u7528 engram_store \u663E\u5F0F\u8BB0\u5F55\uFF0C\u6216\u8BA9\u81EA\u52A8\u6355\u83B7\u5DE5\u4F5C\uFF08git \u63D0\u4EA4 / \u5173\u952E\u6587\u4EF6\u7F16\u8F91 / \u5DE5\u5177\u9519\u8BEF\uFF09" }) }),
678
+ memPageRows.map(
679
+ (r) => r.kind === "head" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { colSpan: 3, style: s.wsHead, children: [
680
+ r.ws,
681
+ " \xB7 ",
682
+ r.count,
683
+ " \u6761"
684
+ ] }) }, `h-${r.ws}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
685
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { style: { ...s.td, whiteSpace: "nowrap" }, children: [
686
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...s.badge, background: KIND_COLORS[r.m.kind] ?? "#6b7280" }, children: KIND_LABEL[r.m.kind] ?? r.m.kind }),
687
+ r.m.status === "archived" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...s.tag, color: "#b45309", background: "#fef3c7" }, children: "archived" })
688
+ ] }),
689
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { style: { ...s.td, minWidth: 0 }, children: [
690
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { title: r.m.text, style: expandedRows.has(r.m.id) ? s.expanded : s.clamp3, children: r.m.text }),
691
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 2, flexWrap: "wrap" }, children: [
692
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...s.mono, ...s.ellipsis, flex: "1 1 160px" }, title: `${fmtDate(r.m.createdAt)} \xB7 ${r.m.id} \xB7 ${r.m.entity ?? ""} \xB7 signal ${r.m.signal.toFixed(2)} \xB7 hits ${r.m.hits} \xB7 TTL ${daysLeft(r.m.expiresAt)}`, children: [
693
+ fmtDate(r.m.createdAt),
694
+ " \xB7 ",
695
+ r.m.id.slice(0, 8),
696
+ r.m.entity ? ` \xB7 ${r.m.entity}` : "",
697
+ " \xB7 ",
698
+ r.m.signal.toFixed(2),
699
+ " \xB7 hits ",
700
+ r.m.hits,
701
+ " \xB7 ",
702
+ daysLeft(r.m.expiresAt)
703
+ ] }),
704
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.linkBtn, onClick: () => toggleExpand(r.m.id), children: expandedRows.has(r.m.id) ? "\u6536\u8D77" : "\u5C55\u5F00\u5168\u6587" })
705
+ ] }),
706
+ r.m.tags.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 3, display: "flex", flexWrap: "wrap", gap: 4 }, children: r.m.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.tag, children: tag }, tag)) })
707
+ ] }),
708
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: s.td, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4, alignItems: "flex-start" }, children: [
709
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, title: "\u5F52\u6863\uFF08TTL/\u8F6F\u5220\uFF0C\u53EF\u6062\u590D\u4E0D\u8F7D\u5165\u7D22\u5F15\uFF09", onClick: () => void act(() => api.archive(r.m.id, r.m.workspace)), children: "\u5F52\u6863" }),
710
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, title: "\u6C38\u4E45\u5220\u9664", onClick: () => {
711
+ if (window.confirm(`\u5220\u9664\u8FD9\u6761\u8BB0\u5FC6?
712
+ ${r.m.text.slice(0, 60)}`)) void act(() => api.remove(r.m.id, r.m.workspace));
713
+ }, children: "\u5220\u9664" })
714
+ ] }) })
715
+ ] }, r.m.id)
716
+ )
717
+ ] })
718
+ ] }),
719
+ flatRows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.pageBar, children: [
720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, disabled: memPageSafe === 0, onClick: () => setMemPage(memPageSafe - 1), children: "\u2039 \u4E0A\u4E00\u9875" }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
722
+ "\u7B2C ",
723
+ memPageSafe + 1,
724
+ " / ",
725
+ memPageCount,
726
+ " \u9875 \xB7 \u5171 ",
727
+ flatRows.length,
728
+ " \u6761"
729
+ ] }),
730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, disabled: memPageSafe >= memPageCount - 1, onClick: () => setMemPage(memPageSafe + 1), children: "\u4E0B\u4E00\u9875 \u203A" }),
731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { style: s.input, value: memPageSafe, onChange: (e) => setMemPage(Number(e.target.value)), title: "\u8DF3\u9875", children: Array.from({ length: memPageCount }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: i, children: [
732
+ "\u7B2C ",
733
+ i + 1,
734
+ " \u9875"
735
+ ] }, i)) })
736
+ ] })
737
+ ] }),
738
+ view === "esr" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.stats, children: [
740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(wsCounts ? wsCounts.tasks : overview?.totals.tasks ?? 0), label: wsCounts ? "\u4EFB\u52A1 (active)" : "\u4EFB\u52A1 (active, \u5168\u5C40)" }),
741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(wsCounts ? wsCounts.links : overview?.totals.links ?? 0), label: wsCounts ? "\u5173\u7CFB" : "\u5173\u7CFB (\u5168\u5C40)" }),
742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatCard, { num: String(wsCounts ? wsCounts.nodes ?? 0 : overview?.totals.nodes ?? 0), label: wsCounts ? "\u8282\u70B9" : "\u8282\u70B9 (\u5168\u5C40)" })
743
+ ] }),
744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.subPanel, children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontWeight: 700, fontSize: 13, marginBottom: 6 }, children: [
746
+ "agent \u884C\u4E3A\u89C2\u6D4B",
747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11.5, fontWeight: 400, color: "var(--dsh-color-muted, #6b7280)" }, children: " \xB7 \u6BCF\u6B21 engram_*/esr_* \u5DE5\u5177\u8C03\u7528\u5B9E\u65F6\u7D2F\u8BA1\uFF08\u771F\u5B9E\u6570\u636E\uFF0C\u6309\u5DE5\u4F5C\u533A/\u5929\u6EDA\u52A8\uFF09" })
748
+ ] }),
749
+ !usageStats ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.mono, children: "\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
750
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
752
+ "ESR \u4E3B\u52A8\u6027 ",
753
+ pct(usageStats.ratios.esrRatio),
754
+ "\uFF08",
755
+ usageStats.ratios.esrCalls,
756
+ "/",
757
+ usageStats.ratios.calls,
758
+ " \u6B21\uFF09"
759
+ ] }),
760
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
761
+ "\u53EC\u56DE\u547D\u4E2D\u7387 ",
762
+ pct(usageStats.ratios.recallHitRate)
763
+ ] }),
764
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
765
+ "\u5E73\u5747\u547D\u4E2D ",
766
+ usageStats.ratios.recallHitsPerQuery ?? "\u2013",
767
+ "/\u67E5\u8BE2"
768
+ ] }),
769
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
770
+ "detail \u8F6C\u5316 ",
771
+ pct(usageStats.ratios.detailFollowRate)
772
+ ] }),
773
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
774
+ "\u5931\u8D25 ",
775
+ usageStats.totals.failures
776
+ ] }),
777
+ usageStats.ratios.calls < 10 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
778
+ "\u6837\u672C\u4E0D\u8DB3\uFF08",
779
+ usageStats.ratios.calls,
780
+ " \u6B21\uFF09\uFF0C\u6BD4\u4F8B\u4EC5\u4F9B\u53C2\u8003"
781
+ ] })
782
+ ] }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11.5, color: "var(--dsh-color-muted, #6b7280)", marginTop: 4 }, children: "ESR \u4E3B\u52A8\u6027\u8FC7\u4F4E\u65F6\uFF0C\u4E0B\u4E00\u4E2A\u4F1A\u8BDD\u7684 [ESR] \u6CE8\u5165\u5757\u4F1A\u9644\u52A0\u4E00\u884C\u57FA\u4E8E\u771F\u5B9E\u6570\u636E\u7684 escalate \u63D0\u9192\uFF0C\u5F15\u5BFC\u6A21\u578B\u5F53\u573A\u8865\u5EFA\u4EFB\u52A1/\u8282\u70B9/\u5173\u7CFB\u2014\u2014\u6BD4\u4F8B\u56DE\u5347\u540E\u63D0\u9192\u81EA\u52A8\u6D88\u5931\u3002" }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11.5, color: "var(--dsh-color-muted-weak, #9ca3af)", marginTop: 5, display: "flex", gap: 4, flexWrap: "wrap", alignItems: "center" }, children: [
785
+ "\u5DE5\u5177\u8C03\u7528\uFF1A",
786
+ Object.entries(usageStats.totals.counts).map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.tag, children: [
787
+ k,
788
+ " \xD7",
789
+ v
790
+ ] }, k))
791
+ ] }),
792
+ usageStats.byDay.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11.5, color: "var(--dsh-color-muted, #6b7280)", marginTop: 4, display: "flex", gap: 8, flexWrap: "wrap" }, children: usageStats.byDay.map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
793
+ d.day,
794
+ " \xB7 \u8C03\u7528 ",
795
+ Object.values(d.counts).reduce((a, b) => a + b, 0),
796
+ " \xB7 \u5931\u8D25 ",
797
+ d.failures
798
+ ] }, d.day)) })
799
+ ] })
800
+ ] }),
801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.panelTitle, children: [
802
+ "ESR \u4EFB\u52A1\uFF08\u8BC1\u636E\u95ED\u73AF\uFF09",
803
+ workspace === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontWeight: 400, color: "var(--dsh-color-muted, #9ca3af)" }, children: "\xB7 \u5168\u90E8\u5DE5\u4F5C\u533A" })
804
+ ] }),
805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.subPanel, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: [
806
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { style: s.input, value: newTaskWs, onChange: (e) => setNewTaskWs(e.target.value), children: [
807
+ workspaces.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "(no workspaces)" }),
808
+ workspaces.map((ws) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: ws, children: ws }, ws))
809
+ ] }),
810
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: { ...s.input, width: 170 }, placeholder: "\u4EFB\u52A1\u540D\u2026", value: newTaskName, onChange: (e) => setNewTaskName(e.target.value), onKeyDown: (e) => {
811
+ if (e.key === "Enter") void createNewTask();
812
+ } }),
813
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: { ...s.input, width: 240 }, placeholder: "\u8981\u4EA7\u51FA / \u6EE1\u8DB3\u4EC0\u4E48\uFF08\u53EF\u9009\uFF09", value: newTaskDesc, onChange: (e) => setNewTaskDesc(e.target.value) }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btnPrimary, disabled: newTaskBusy || newTaskName.trim() === "", onClick: () => void createNewTask(), children: newTaskBusy ? "\u2026" : "\u65B0\u5EFA\u4EFB\u52A1" })
815
+ ] }) }),
816
+ tasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.empty, children: "\u6682\u65E0\u4EFB\u52A1 \u2014 \u7528\u4E0A\u65B9\u300C\u65B0\u5EFA\u4EFB\u52A1\u300D\u6216 esr_task \u5DE5\u5177\u521B\u5EFA" }),
817
+ taskGroups.map(([ws, items]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react2.Fragment, { children: [
818
+ workspace === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.groupLabel, children: [
819
+ ws,
820
+ " \xB7 ",
821
+ items.length,
822
+ " \u4E2A\u4EFB\u52A1"
823
+ ] }),
824
+ items.map((task) => {
825
+ const gaps = taskGaps(task);
826
+ const isStable = task.state === "stable";
827
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.subPanel, children: [
828
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontWeight: 600, fontSize: 13 }, children: [
829
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.mono, children: task.id.slice(0, 6) }),
830
+ " ",
831
+ task.name,
832
+ " ",
833
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...s.badge, background: isStable ? "#059669" : gaps.length === 0 ? "#2563eb" : "#d97706" }, children: isStable ? "STABLE" : gaps.length === 0 ? "READY" : "ACTIVE" }),
834
+ !isStable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, onClick: () => setCloseFor(closeFor === task.id ? null : task.id), children: closeFor === task.id ? "\u6536\u8D77" : "\u586B\u5199\u8BC1\u636E\u5173\u95ED\u2026" })
835
+ ] }),
836
+ !isStable && gaps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, color: "var(--dsh-color-muted, #6b7280)", marginTop: 4 }, children: [
837
+ "\u7F3A\u53E3\uFF1A",
838
+ gaps.join(", "),
839
+ " \u2014 \u63D0\u4F9B artifact / evaluation / memory_ref \u540E\u8F6C\u4E3A STABLE"
840
+ ] }),
841
+ task.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsh-color-muted-strong, #4b5563)", marginTop: 4 }, children: task.description }),
842
+ task.memoryRefs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, marginTop: 4 }, children: [
843
+ "\u8BB0\u5FC6\u5F15\u7528\uFF1A",
844
+ task.memoryRefs.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.tag, children: r.slice(0, 8) }, r))
845
+ ] }),
846
+ closeFor === task.id && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 8, padding: 8, border: "1px solid var(--dsh-color-border, #e5e7eb)", borderRadius: 8 }, children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, color: "var(--dsh-color-muted, #6b7280)", marginBottom: 6 }, children: "\u63D0\u4F9B\u8BC1\u636E\u540E\u5173\u95ED\uFF08\u4E09\u9879\u5168\u9F50\u624D\u8F6C STABLE\uFF09" }),
848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }, children: [
849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: { ...s.input, width: 150 }, placeholder: "artifact \u8DEF\u5F84/URL", value: closeArtifact, onChange: (e) => setCloseArtifact(e.target.value) }),
850
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: { ...s.input, width: 150 }, placeholder: "evaluation \u9A8C\u8BC1\u8BC1\u636E", value: closeEval, onChange: (e) => setCloseEval(e.target.value) }),
851
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: { ...s.input, width: 150 }, placeholder: "memory_refs \u9017\u53F7\u5206\u9694", value: closeRefs, onChange: (e) => setCloseRefs(e.target.value) }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btnPrimary, disabled: closeBusy, onClick: () => void submitClose(task), children: closeBusy ? "\u2026" : "\u63D0\u4EA4\u5173\u95ED" }),
853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.btn, onClick: () => setCloseFor(null), children: "\u53D6\u6D88" })
854
+ ] })
855
+ ] })
856
+ ] }, task.id);
857
+ })
858
+ ] }, ws)),
859
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.panelTitle, children: [
860
+ "\u8282\u70B9\u4E0E\u5173\u7CFB\uFF08esr_node / esr_link\uFF09",
861
+ workspace === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontWeight: 400, color: "var(--dsh-color-muted, #9ca3af)" }, children: "\xB7 \u5168\u90E8\u5DE5\u4F5C\u533A" })
862
+ ] }),
863
+ nodes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.empty, children: "\u6682\u65E0\u8282\u70B9 \u2014 \u6A21\u578B\u4F1A\u4E3A\u53CD\u590D\u51FA\u73B0\u7684\u9886\u57DF\u5BF9\u8C61\u4E3B\u52A8\u767B\u8BB0\uFF08esr_node\uFF09\uFF0C\u6B64\u5904\u4E5F\u53EF\u67E5\u770B\u5173\u7CFB" }),
864
+ nodeGroups.map(([ws, items]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react2.Fragment, { children: [
865
+ workspace === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.groupLabel, children: [
866
+ ws,
867
+ " \xB7 ",
868
+ items.length,
869
+ " \u4E2A\u8282\u70B9"
870
+ ] }),
871
+ items.map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12.5, padding: "2px 0" }, children: [
872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mono", style: { ...s.mono, color: "#4338ca" }, children: n.id.slice(0, 24) }),
873
+ " ",
874
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 600 }, children: n.name }),
875
+ n.kind && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...s.tag, color: "#4338ca", background: "#eef2ff" }, children: n.kind }),
876
+ n.description && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: "var(--dsh-color-muted, #6b7280)" }, children: [
877
+ " \u2014 ",
878
+ n.description.slice(0, 48)
879
+ ] })
880
+ ] }, n.id))
881
+ ] }, ws)),
882
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsh-color-muted, #6b7280)", marginTop: 6 }, children: "\u5173\u7CFB\uFF1A" }),
883
+ links.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.empty, children: "\u6682\u65E0\u5173\u7CFB \u2014 esr_link \u521B\u5EFA" }),
884
+ linkGroups.map(([ws, items]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react2.Fragment, { children: [
885
+ workspace === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.groupLabel, children: [
886
+ ws,
887
+ " \xB7 ",
888
+ items.length,
889
+ " \u6761\u5173\u7CFB"
890
+ ] }),
891
+ items.map((l) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12.5, padding: "2px 0" }, children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mono", style: s.mono, children: l.source.slice(0, 10) }),
893
+ " ",
894
+ "--",
895
+ l.relation,
896
+ "-->",
897
+ " ",
898
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mono", style: s.mono, children: l.target.slice(0, 10) }),
899
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: "var(--dsh-color-muted-weak, #9ca3af)" }, children: [
900
+ " \xB7 ",
901
+ fmtDate(l.createdAt)
902
+ ] })
903
+ ] }, l.id))
904
+ ] }, ws))
905
+ ] })
906
+ ] });
907
+ }
908
+
909
+ // client/src/EngramConfigCard.tsx
910
+ var import_react3 = require("react");
911
+ var import_jsx_runtime2 = require("react/jsx-runtime");
912
+ var GROUPS = [
913
+ {
914
+ id: "capture",
915
+ title: "\u6355\u83B7\u4E0E\u68C0\u7D22",
916
+ description: "\u96F6 LLM \u81EA\u52A8\u6355\u83B7\u4E0E\u8DE8\u4F1A\u8BDD\u641C\u7D22",
917
+ fields: [
918
+ { key: "autoCapture", label: "\u81EA\u52A8\u6355\u83B7", hint: "\u96F6 LLM \u4ECE\u5DE5\u5177\u7ED3\u679C\u63D0\u53D6\u8BB0\u5FC6\uFF08git/\u5173\u952E\u6587\u4EF6/\u9519\u8BEF\uFF09", kind: "bool" },
919
+ { key: "autoCapturePerSession", label: "\u6BCF\u4F1A\u8BDD\u6355\u83B7\u4E0A\u9650", hint: "\u5355\u4F1A\u8BDD\u81EA\u52A8\u6355\u83B7\u6761\u6570\u4E0A\u9650", kind: "num", min: 0, max: 1e3 },
920
+ { key: "sessionSearch", label: "\u4F1A\u8BDD\u5386\u53F2\u641C\u7D22", hint: "engram_recall \u652F\u6301\u8DE8\u4F1A\u8BDD FTS \u515C\u5E95", kind: "bool" }
921
+ ]
922
+ },
923
+ {
924
+ id: "index",
925
+ title: "\u7D22\u5F15",
926
+ description: "[ENGRAM] \u5757\u7684\u5185\u5BB9\u9884\u7B97\u4E0E\u664B\u5347\u89C4\u5219",
927
+ fields: [
928
+ { key: "indexMaxLines", label: "\u7D22\u5F15\u6700\u5927\u884C\u6570", hint: "[ENGRAM] \u5757\u6700\u591A\u663E\u793A\u7684\u6761\u76EE\u884C\u6570", kind: "num", min: 0, max: 50 },
929
+ { key: "indexMaxChars", label: "\u7D22\u5F15\u5B57\u7B26\u4E0A\u9650", hint: "[ENGRAM] \u5757 token \u9884\u7B97", kind: "num", min: 0, max: 4e3 },
930
+ { key: "minIndexSignal", label: "\u5165\u7D22\u5F15\u4FE1\u53F7\u9608\u503C", hint: "signal \u2265 \u6B64\u503C\u7684\u81EA\u52A8\u6355\u83B7\u624D\u8FDB\u7D22\u5F15", kind: "num", min: 0, max: 1, step: 0.05 },
931
+ { key: "promoteHits", label: "\u664B\u5347\u547D\u4E2D\u6570", hint: "hit \u6570\u8FBE\u6B64\u503C\u7684\u4F4E\u4FE1\u53F7\u6761\u76EE\u8FDB\u7D22\u5F15", kind: "num", min: 0, max: 20 }
932
+ ]
933
+ },
934
+ {
935
+ id: "retention",
936
+ title: "\u751F\u547D\u5468\u671F\u4E0E GC",
937
+ description: "\u8FC7\u671F\u3001\u5BB9\u91CF\u4E0A\u9650\u4E0E\u5B9A\u671F\u56DE\u6536",
938
+ fields: [
939
+ { key: "expireDays", label: "TTL\uFF08\u5929\uFF09", hint: "0 = \u4E0D\u8FC7\u671F", kind: "num", min: 0, max: 3650 },
940
+ { key: "maxMemoriesPerWorkspace", label: "\u5DE5\u4F5C\u533A\u8BB0\u5FC6\u4E0A\u9650", kind: "num", min: 0, max: 1e4 },
941
+ { key: "gcEnabled", label: "\u8BB0\u5FC6 GC", hint: "\u5B9A\u65F6\u56DE\u6536\uFF08\u8FC7\u671F/\u8D85\u5BB9\u91CF/stable \u8D85\u7A97/\u60AC\u7A7A\u94FE\u63A5\uFF09", kind: "bool" },
942
+ { key: "gcStableRetentionDays", label: "stable \u4EFB\u52A1\u4FDD\u7559\uFF08\u5929\uFF09", hint: "\u8D85\u7A97\u540E\u7531 GC \u5F52\u6863\u3001\u79BB\u5F00 [ESR] \u8868\u9762", kind: "num", min: 0, max: 3650 }
943
+ ]
944
+ },
945
+ {
946
+ id: "security",
947
+ title: "\u5B89\u5168",
948
+ description: "\u8BBF\u95EE\u56F4\u680F\u4E0E\u96A7\u9053\u6388\u6743",
949
+ fields: [
950
+ { key: "trustedHosts", label: "\u53D7\u4FE1\u96A7\u9053\u57DF\u540D", hint: "\u5141\u8BB8\u7ECF\u96A7\u9053\u8BBF\u95EE\u8BB0\u5FC6\u67E5\u770B\u5668\u7684\u57DF\u540D\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A = \u4EC5\u672C\u673A\u3002\u6539\u540E\u9700\u91CD\u542F dsh web \u751F\u6548", kind: "text", width: 260 }
951
+ ]
952
+ }
953
+ ];
954
+ var FIELDS = GROUPS.flatMap((group) => group.fields);
955
+ var CARD_DESCRIPTION = "\u63A7\u5236 engram \u8BB0\u5FC6\u7684\u6355\u83B7\u3001\u7D22\u5F15\u3001\u4FDD\u7559\u4E0E\u96A7\u9053\u8BBF\u95EE";
956
+ var s2 = {
957
+ card: {
958
+ listStyle: "none",
959
+ border: "1px solid var(--dsw-alias-border-l2, #e5e7eb)",
960
+ borderRadius: 12,
961
+ background: "var(--dsw-alias-bg-layer-3, #ffffff)",
962
+ transition: "border-color .16s, background .16s"
963
+ },
964
+ cardOpen: {
965
+ background: "var(--dsw-alias-bg-layer-2, #ffffff)",
966
+ borderColor: "var(--dsw-alias-label-dimmed, #9ca3af)"
967
+ },
968
+ header: {
969
+ width: "100%",
970
+ appearance: "none",
971
+ border: 0,
972
+ background: "none",
973
+ font: "inherit",
974
+ color: "inherit",
975
+ textAlign: "left",
976
+ cursor: "pointer",
977
+ display: "flex",
978
+ alignItems: "center",
979
+ gap: 12,
980
+ padding: "14px 16px",
981
+ borderRadius: 12
982
+ },
983
+ headText: {
984
+ flex: 1,
985
+ minWidth: 0,
986
+ display: "flex",
987
+ flexDirection: "column",
988
+ gap: 4
989
+ },
990
+ name: { fontSize: 15, fontWeight: 600, lineHeight: 1.4, color: "var(--dsw-alias-label-primary, #111827)" },
991
+ description: { fontSize: 13, lineHeight: 1.5, color: "var(--dsw-alias-label-tertiary, #6b7280)" },
992
+ pending: {
993
+ flex: "none",
994
+ borderRadius: 999,
995
+ padding: "1px 8px",
996
+ fontSize: 11,
997
+ lineHeight: "17px",
998
+ fontWeight: 500,
999
+ whiteSpace: "nowrap",
1000
+ background: "var(--dsw-alias-bg-module-platform, #f3f4f6)",
1001
+ color: "var(--dsw-alias-label-secondary, #4b5563)"
1002
+ },
1003
+ body: {
1004
+ borderTop: "1px solid var(--dsw-alias-border-l2, #e5e7eb)",
1005
+ margin: "0 16px",
1006
+ paddingBottom: 8
1007
+ },
1008
+ readOnly: { margin: "12px 0 0", fontSize: 12, lineHeight: 1.5, color: "var(--dsw-alias-label-tertiary, #6b7280)" },
1009
+ groupPanel: {
1010
+ marginTop: 10,
1011
+ border: "1px solid var(--dsw-alias-border-l2, #e5e7eb)",
1012
+ borderRadius: 10,
1013
+ padding: "8px 12px 2px",
1014
+ background: "var(--dsw-alias-bg-layer-3, #ffffff)"
1015
+ },
1016
+ groupHead: { display: "flex", alignItems: "baseline", gap: 8, marginBottom: 2 },
1017
+ groupTitle: { fontSize: 13, fontWeight: 700, letterSpacing: 0.2, color: "var(--dsw-alias-label-primary, #111827)" },
1018
+ groupDesc: { fontSize: 11, color: "var(--dsw-alias-label-tertiary, #6b7280)" },
1019
+ row: {
1020
+ display: "flex",
1021
+ alignItems: "center",
1022
+ justifyContent: "space-between",
1023
+ gap: 12,
1024
+ padding: "8px 0",
1025
+ borderBottom: "1px solid var(--dsw-alias-border-l1, #f3f4f6)"
1026
+ },
1027
+ label: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #111827)" },
1028
+ hint: { fontSize: 11.5, lineHeight: 1.4, color: "var(--dsw-alias-label-tertiary, #6b7280)", marginTop: 2 },
1029
+ input: {
1030
+ width: 90,
1031
+ border: "1px solid var(--dsw-alias-border-l2, #d1d5db)",
1032
+ borderRadius: 6,
1033
+ padding: "4px 7px",
1034
+ fontSize: 12.5,
1035
+ background: "var(--dsw-alias-bg-layer-3, #fff)",
1036
+ color: "inherit"
1037
+ },
1038
+ footer: {
1039
+ display: "flex",
1040
+ alignItems: "center",
1041
+ justifyContent: "flex-end",
1042
+ gap: 8,
1043
+ padding: "12px 0 4px",
1044
+ borderTop: "1px solid var(--dsw-alias-border-l2, #e5e7eb)"
1045
+ },
1046
+ reset: {
1047
+ appearance: "none",
1048
+ border: 0,
1049
+ background: "none",
1050
+ font: "inherit",
1051
+ fontSize: 12,
1052
+ cursor: "pointer",
1053
+ color: "var(--dsw-alias-label-tertiary, #6b7280)"
1054
+ },
1055
+ discard: {
1056
+ appearance: "none",
1057
+ border: "1px solid var(--dsw-alias-border-l2, #d1d5db)",
1058
+ borderRadius: 8,
1059
+ padding: "5px 14px",
1060
+ font: "inherit",
1061
+ fontSize: 13,
1062
+ lineHeight: 1.5,
1063
+ cursor: "pointer",
1064
+ background: "none",
1065
+ color: "var(--dsw-alias-label-secondary, #4b5563)"
1066
+ },
1067
+ save: {
1068
+ appearance: "none",
1069
+ border: "1px solid transparent",
1070
+ borderRadius: 8,
1071
+ padding: "5px 14px",
1072
+ font: "inherit",
1073
+ fontSize: 13,
1074
+ lineHeight: 1.5,
1075
+ cursor: "pointer",
1076
+ background: "var(--dsw-alias-label-primary, #111827)",
1077
+ color: "var(--dsw-alias-bg-layer-3, #ffffff)"
1078
+ },
1079
+ disabled: { opacity: 0.4, cursor: "default" },
1080
+ failed: { flex: 1, minWidth: 0, margin: 0, fontSize: 12, lineHeight: 1.5, color: "#dc2626" },
1081
+ note: { fontSize: 11.5, color: "var(--dsw-alias-label-tertiary, #9ca3af)", marginTop: 8 }
1082
+ };
1083
+ function Chevron({ open }) {
1084
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1085
+ "svg",
1086
+ {
1087
+ width: "14",
1088
+ height: "14",
1089
+ viewBox: "0 0 14 14",
1090
+ "aria-hidden": "true",
1091
+ style: {
1092
+ flex: "none",
1093
+ color: "var(--dsw-alias-label-tertiary, #6b7280)",
1094
+ transition: "transform .16s",
1095
+ transform: open ? "rotate(180deg)" : void 0
1096
+ },
1097
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1098
+ "path",
1099
+ {
1100
+ d: "M3 5.5 L7 9 L11 5.5",
1101
+ fill: "none",
1102
+ stroke: "currentColor",
1103
+ strokeWidth: "1.6",
1104
+ strokeLinecap: "round",
1105
+ strokeLinejoin: "round"
1106
+ }
1107
+ )
1108
+ }
1109
+ );
1110
+ }
1111
+ function EngramConfigCard({ scope }) {
1112
+ const [snap, setSnap] = (0, import_react3.useState)(null);
1113
+ const [draft, setDraft] = (0, import_react3.useState)({});
1114
+ const [saving, setSaving] = (0, import_react3.useState)(false);
1115
+ const [error, setError] = (0, import_react3.useState)(null);
1116
+ const [open, setOpen] = (0, import_react3.useState)(false);
1117
+ (0, import_react3.useEffect)(() => {
1118
+ void scope.load();
1119
+ const off = scope.subscribe(() => {
1120
+ const next = scope.getSnapshot();
1121
+ setSnap(next);
1122
+ const value2 = next.value ?? next.base;
1123
+ if (value2) setDraft((prev) => ({ ...value2, ...prev }));
1124
+ });
1125
+ setSnap(scope.getSnapshot());
1126
+ return off;
1127
+ }, [scope]);
1128
+ const setField = (0, import_react3.useCallback)((key, value2) => {
1129
+ setDraft((prev) => ({ ...prev, [key]: value2 }));
1130
+ }, []);
1131
+ const effective = snap?.value ?? snap?.base ?? {};
1132
+ const anyDirty = FIELDS.some((field) => {
1133
+ const staged = draft[field.key];
1134
+ return staged !== void 0 && staged !== effective[field.key];
1135
+ });
1136
+ const save = (0, import_react3.useCallback)(async () => {
1137
+ setSaving(true);
1138
+ setError(null);
1139
+ try {
1140
+ for (const field of FIELDS) {
1141
+ const value2 = draft[field.key];
1142
+ if (value2 === void 0) continue;
1143
+ if (value2 === effective[field.key]) continue;
1144
+ if (typeof value2 === "number" && Number.isNaN(value2)) continue;
1145
+ await scope.set(field.key, value2);
1146
+ }
1147
+ await scope.load();
1148
+ } catch (e) {
1149
+ setError(String(e instanceof Error ? e.message : e));
1150
+ } finally {
1151
+ setSaving(false);
1152
+ }
1153
+ }, [draft, scope, effective]);
1154
+ const discard = (0, import_react3.useCallback)(() => {
1155
+ setError(null);
1156
+ const value2 = snap?.value ?? snap?.base ?? {};
1157
+ setDraft({ ...value2 });
1158
+ }, [snap]);
1159
+ const resetField = (0, import_react3.useCallback)(async (key) => {
1160
+ setError(null);
1161
+ try {
1162
+ await scope.unset(key);
1163
+ await scope.load();
1164
+ setDraft((prev) => ({ ...prev, [key]: void 0 }));
1165
+ } catch (e) {
1166
+ setError(String(e instanceof Error ? e.message : e));
1167
+ }
1168
+ }, [scope]);
1169
+ const value = { ...snap?.base ?? {}, ...draft };
1170
+ const writable = snap?.writable !== false && snap?.status !== "unavailable";
1171
+ const available = snap?.status === "ready";
1172
+ const { vars } = useEngramTheme();
1173
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: vars, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: available ? { ...s2.card, ...open ? s2.cardOpen : void 0 } : { display: "none" }, children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1175
+ "button",
1176
+ {
1177
+ type: "button",
1178
+ style: s2.header,
1179
+ "aria-expanded": open,
1180
+ "aria-label": `${open ? "\u6536\u8D77" : "\u5C55\u5F00"} dsh-engram \u8BBE\u7F6E`,
1181
+ onClick: () => setOpen((prev) => !prev),
1182
+ children: [
1183
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: s2.headText, children: [
1184
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s2.name, children: "dsh-engram" }),
1185
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s2.description, children: CARD_DESCRIPTION })
1186
+ ] }),
1187
+ anyDirty && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s2.pending, children: "\u672A\u4FDD\u5B58" }),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Chevron, { open })
1189
+ ]
1190
+ }
1191
+ ),
1192
+ open && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s2.body, children: [
1193
+ !writable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: s2.readOnly, children: "\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\uFF08\u5BBF\u4E3B\u672A\u6388\u6743\u5199\u5165\u6216\u9700\u91CD\u542F\u5E94\u7528\uFF09\u3002" }),
1194
+ GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s2.groupPanel, children: [
1195
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s2.groupHead, children: [
1196
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: s2.groupTitle, children: group.title }),
1197
+ group.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: s2.groupDesc, children: group.description })
1198
+ ] }),
1199
+ group.fields.map((field) => {
1200
+ const raw = value[field.key];
1201
+ const overridden = snap?.user !== void 0 && field.key in snap.user;
1202
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s2.row, children: [
1203
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1204
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: s2.label, children: field.label }),
1205
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: s2.hint, children: field.hint })
1206
+ ] }),
1207
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1208
+ field.kind === "bool" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1209
+ "input",
1210
+ {
1211
+ type: "checkbox",
1212
+ checked: raw === true,
1213
+ disabled: !writable,
1214
+ onChange: (e) => setField(field.key, e.target.checked)
1215
+ }
1216
+ ) : field.kind === "text" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1217
+ "input",
1218
+ {
1219
+ type: "text",
1220
+ style: { ...s2.input, width: field.width ?? 180 },
1221
+ value: Array.isArray(raw) ? raw.join(", ") : raw === void 0 ? "" : String(raw),
1222
+ disabled: !writable,
1223
+ placeholder: "host.domain, \u53E6\u4E00\u57DF\u540D\u2026",
1224
+ onChange: (e) => {
1225
+ const tokens = e.target.value.split(/[,\s]+/).map((x) => x.trim()).filter(Boolean);
1226
+ setField(field.key, tokens);
1227
+ }
1228
+ }
1229
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1230
+ "input",
1231
+ {
1232
+ type: "number",
1233
+ style: s2.input,
1234
+ step: field.step ?? 1,
1235
+ min: field.min,
1236
+ max: field.max,
1237
+ value: raw === void 0 ? "" : String(raw),
1238
+ disabled: !writable,
1239
+ onChange: (e) => setField(field.key, e.target.value === "" ? void 0 : Number(e.target.value))
1240
+ }
1241
+ ),
1242
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1243
+ "button",
1244
+ {
1245
+ type: "button",
1246
+ style: overridden ? { ...s2.reset, color: "var(--dsw-alias-brand-primary, #2563eb)" } : { ...s2.reset, cursor: "default", opacity: 0.55 },
1247
+ title: "\u91CD\u7F6E\u4E3A\u9ED8\u8BA4",
1248
+ disabled: !writable || !overridden,
1249
+ onClick: () => void resetField(field.key),
1250
+ children: "\u91CD\u7F6E"
1251
+ }
1252
+ )
1253
+ ] })
1254
+ ] }, field.key);
1255
+ })
1256
+ ] }, group.id)),
1257
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: s2.note, children: "\u8BBE\u7F6E\u5BF9\u65B0\u5EFA\u4F1A\u8BDD\u5373\u65F6\u751F\u6548\uFF1B\u5DF2\u51BB\u7ED3\u7684 [ENGRAM] \u5757\u4FDD\u6301\u524D\u7F00\u7A33\u5B9A\u3002" }),
1258
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: s2.failed, children: error }),
1259
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s2.footer, children: [
1260
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1261
+ "button",
1262
+ {
1263
+ type: "button",
1264
+ style: { ...s2.discard, ...!anyDirty || saving ? s2.disabled : void 0 },
1265
+ disabled: !anyDirty || saving,
1266
+ onClick: discard,
1267
+ children: "\u653E\u5F03\u4FEE\u6539"
1268
+ }
1269
+ ),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1271
+ "button",
1272
+ {
1273
+ type: "button",
1274
+ style: { ...s2.save, ...!anyDirty || saving ? s2.disabled : void 0 },
1275
+ disabled: !anyDirty || saving,
1276
+ onClick: () => void save(),
1277
+ children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58"
1278
+ }
1279
+ )
1280
+ ] })
1281
+ ] })
1282
+ ] }) });
1283
+ }
1284
+
1285
+ // client/src/scope.ts
1286
+ var LOADING = {
1287
+ status: "loading",
1288
+ value: void 0,
1289
+ base: void 0,
1290
+ user: void 0,
1291
+ revision: 0,
1292
+ writable: false
1293
+ };
1294
+ var EngramScopeImpl = class {
1295
+ constructor(api, ns, opts = { writable: true }) {
1296
+ this.api = api;
1297
+ this.ns = ns;
1298
+ this.opts = opts;
1299
+ }
1300
+ api;
1301
+ ns;
1302
+ opts;
1303
+ listeners = /* @__PURE__ */ new Set();
1304
+ snapshot = LOADING;
1305
+ disposed = false;
1306
+ getSnapshot() {
1307
+ return this.snapshot;
1308
+ }
1309
+ subscribe(listener) {
1310
+ this.listeners.add(listener);
1311
+ return () => this.listeners.delete(listener);
1312
+ }
1313
+ notify() {
1314
+ for (const listener of this.listeners) listener();
1315
+ }
1316
+ fail(reason, writable = false) {
1317
+ this.snapshot = { status: "unavailable", value: void 0, base: void 0, user: void 0, revision: 0, writable, reason };
1318
+ this.notify();
1319
+ }
1320
+ async load() {
1321
+ if (this.disposed) return;
1322
+ this.snapshot = LOADING;
1323
+ this.notify();
1324
+ try {
1325
+ const response = await this.api.settings.describe({});
1326
+ const result = response?.result;
1327
+ if (!result || result.ok !== true) {
1328
+ const err = result && "error" in result ? result.error : { code: "settings.describe", message: "unreachable" };
1329
+ this.fail(`settings.describe failed: ${String(err?.code ?? err)}: ${String(err?.message ?? "")}`);
1330
+ return;
1331
+ }
1332
+ const payload = result.value;
1333
+ const view = payload.namespaces?.find((n) => n.ns === this.ns);
1334
+ if (!view) {
1335
+ this.fail(`the '${this.ns}' settings namespace is not served by this host.`);
1336
+ return;
1337
+ }
1338
+ const applied = view.applies === "live";
1339
+ const viewValue = view.value ?? view.base;
1340
+ this.snapshot = {
1341
+ status: "ready",
1342
+ value: viewValue,
1343
+ base: view.base ?? view.value,
1344
+ user: view.user,
1345
+ revision: view.revision ?? 0,
1346
+ writable: this.opts.writable !== false && payload.writable !== false && applied
1347
+ };
1348
+ this.notify();
1349
+ } catch (error) {
1350
+ this.fail(`settings.describe threw: ${String(error instanceof Error ? error.message : error)}`);
1351
+ }
1352
+ }
1353
+ async mutate(op) {
1354
+ const revision = this.snapshot.revision;
1355
+ const response = await this.api.settings.mutate({
1356
+ ns: this.ns,
1357
+ ops: [op],
1358
+ ...revision ? { expectedRevision: revision } : {}
1359
+ });
1360
+ const result = response?.result;
1361
+ if (!result || result.ok !== true) {
1362
+ const err = result && "error" in result ? result.error : { code: "settings.mutate", message: "unreachable" };
1363
+ throw new Error(`settings.mutate failed: ${String(err?.code ?? err)}: ${String(err?.message ?? "")}`);
1364
+ }
1365
+ const view = result.value;
1366
+ if (view) this.snapshot = { ...this.snapshot, revision: view.revision ?? revision };
1367
+ }
1368
+ async set(key, value) {
1369
+ await this.mutate({ op: "set", path: [String(key)], value });
1370
+ this.notify();
1371
+ }
1372
+ async unset(key) {
1373
+ await this.mutate({ op: "unset", path: [String(key)] });
1374
+ this.notify();
1375
+ }
1376
+ };
1377
+
1378
+ // client/src/entry.tsx
1379
+ var NS = "dsh-engram";
1380
+ var zh = {
1381
+ nav: "Engram \u8BB0\u5FC6",
1382
+ refresh: "\u5237\u65B0",
1383
+ error: "\u8BFB\u53D6\u5931\u8D25"
1384
+ };
1385
+ var en = {
1386
+ nav: "Engram Memory",
1387
+ refresh: "Refresh",
1388
+ error: "Load failed"
1389
+ };
1390
+ var inject = ["slots", "locale", "connection"];
1391
+ function apply(ctx) {
1392
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-engram: dictionaries");
1393
+ const api = new EngramApi();
1394
+ const t = ctx.locale.bind(NS);
1395
+ const sectionInjected = () => ({ api, t });
1396
+ try {
1397
+ ctx.slots.inject(
1398
+ "settings.section",
1399
+ () => ctx.slots.register(
1400
+ {
1401
+ name: "settings.section",
1402
+ id: "engram",
1403
+ order: 16,
1404
+ label: () => t("nav"),
1405
+ locale: NS,
1406
+ inject: sectionInjected
1407
+ },
1408
+ EngramSection
1409
+ )
1410
+ );
1411
+ } catch (error) {
1412
+ console.warn("[dsh-engram] settings.section registration failed:", error);
1413
+ }
1414
+ const connection = ctx.get("connection");
1415
+ const scope = new EngramScopeImpl(connection.api, "dsh-engram");
1416
+ const cardInjected = () => ({ scope });
1417
+ try {
1418
+ ctx.slots.inject(
1419
+ "settings.plugin.item",
1420
+ () => ctx.slots.register(
1421
+ {
1422
+ name: "settings.plugin.item",
1423
+ id: "dsh-engram",
1424
+ key: "dsh-engram",
1425
+ locale: NS,
1426
+ inject: cardInjected
1427
+ },
1428
+ EngramConfigCard
1429
+ )
1430
+ );
1431
+ } catch (error) {
1432
+ console.warn("[dsh-engram] settings.plugin.item registration failed:", error);
1433
+ }
1434
+ }
1435
+
1436
+ return module.exports;
1437
+ }
1438
+ });