dsh-mnemon 0.1.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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +169 -0
  3. package/README.md +169 -0
  4. package/cordis.patch.yml +15 -0
  5. package/docs/README.md +8 -0
  6. package/docs/en/README.md +39 -0
  7. package/docs/en/architecture.md +129 -0
  8. package/docs/en/assets/project-architecture.svg +182 -0
  9. package/docs/en/configuration.md +204 -0
  10. package/docs/en/development.md +178 -0
  11. package/docs/en/getting-started.md +147 -0
  12. package/docs/en/interfaces.md +159 -0
  13. package/docs/en/operations.md +151 -0
  14. package/docs/en/project-overview.md +247 -0
  15. package/docs/en/roadmap.md +41 -0
  16. package/docs/en/storage-model.md +179 -0
  17. package/docs/en/workflows.md +290 -0
  18. package/docs/zh-CN/README.md +39 -0
  19. package/docs/zh-CN/architecture.md +129 -0
  20. package/docs/zh-CN/assets/project-architecture.svg +182 -0
  21. package/docs/zh-CN/assets/screenshots/conversation-recall.png +0 -0
  22. package/docs/zh-CN/assets/screenshots/conversation-writeback.png +0 -0
  23. package/docs/zh-CN/assets/screenshots/documents-markdown.png +0 -0
  24. package/docs/zh-CN/assets/screenshots/entities-context.png +0 -0
  25. package/docs/zh-CN/assets/screenshots/memory-content.png +0 -0
  26. package/docs/zh-CN/assets/screenshots/overview-memory-graph.png +0 -0
  27. package/docs/zh-CN/assets/screenshots/recall-agent-answer.png +0 -0
  28. package/docs/zh-CN/assets/screenshots/runtime-memory.png +0 -0
  29. package/docs/zh-CN/configuration.md +204 -0
  30. package/docs/zh-CN/development.md +178 -0
  31. package/docs/zh-CN/getting-started.md +147 -0
  32. package/docs/zh-CN/interfaces.md +159 -0
  33. package/docs/zh-CN/operations.md +151 -0
  34. package/docs/zh-CN/project-overview.md +247 -0
  35. package/docs/zh-CN/roadmap.md +41 -0
  36. package/docs/zh-CN/storage-model.md +179 -0
  37. package/docs/zh-CN/workflows.md +290 -0
  38. package/lib/client.js +6288 -0
  39. package/lib/client.js.map +1 -0
  40. package/lib/index.js +4453 -0
  41. package/lib/types/client/MnemonLogo.d.ts +7 -0
  42. package/lib/types/client/MnemonLogo.d.ts.map +1 -0
  43. package/lib/types/client/MnemonLogo.js +6 -0
  44. package/lib/types/client/MnemonLogo.js.map +1 -0
  45. package/lib/types/client/MnemonSettingsCard.d.ts +9 -0
  46. package/lib/types/client/MnemonSettingsCard.d.ts.map +1 -0
  47. package/lib/types/client/MnemonSettingsCard.js +109 -0
  48. package/lib/types/client/MnemonSettingsCard.js.map +1 -0
  49. package/lib/types/client/MnemonView.d.ts +11 -0
  50. package/lib/types/client/MnemonView.d.ts.map +1 -0
  51. package/lib/types/client/MnemonView.js +1239 -0
  52. package/lib/types/client/MnemonView.js.map +1 -0
  53. package/lib/types/client/api.d.ts +68 -0
  54. package/lib/types/client/api.d.ts.map +1 -0
  55. package/lib/types/client/api.js +80 -0
  56. package/lib/types/client/api.js.map +1 -0
  57. package/lib/types/client/index.d.ts +4 -0
  58. package/lib/types/client/index.d.ts.map +1 -0
  59. package/lib/types/client/index.js +36 -0
  60. package/lib/types/client/index.js.map +1 -0
  61. package/lib/types/client/locales.d.ts +1054 -0
  62. package/lib/types/client/locales.d.ts.map +1 -0
  63. package/lib/types/client/locales.js +1062 -0
  64. package/lib/types/client/locales.js.map +1 -0
  65. package/lib/types/client/settings.d.ts +17 -0
  66. package/lib/types/client/settings.d.ts.map +1 -0
  67. package/lib/types/client/settings.js +49 -0
  68. package/lib/types/client/settings.js.map +1 -0
  69. package/lib/types/commands.d.ts +6 -0
  70. package/lib/types/commands.d.ts.map +1 -0
  71. package/lib/types/commands.js +100 -0
  72. package/lib/types/commands.js.map +1 -0
  73. package/lib/types/config-values.d.ts +4 -0
  74. package/lib/types/config-values.d.ts.map +1 -0
  75. package/lib/types/config-values.js +4 -0
  76. package/lib/types/config-values.js.map +1 -0
  77. package/lib/types/config.d.ts +49 -0
  78. package/lib/types/config.d.ts.map +1 -0
  79. package/lib/types/config.js +55 -0
  80. package/lib/types/config.js.map +1 -0
  81. package/lib/types/contracts.d.ts +272 -0
  82. package/lib/types/contracts.d.ts.map +1 -0
  83. package/lib/types/contracts.js +2 -0
  84. package/lib/types/contracts.js.map +1 -0
  85. package/lib/types/documents.d.ts +149 -0
  86. package/lib/types/documents.d.ts.map +1 -0
  87. package/lib/types/documents.js +503 -0
  88. package/lib/types/documents.js.map +1 -0
  89. package/lib/types/guidance.d.ts +9 -0
  90. package/lib/types/guidance.d.ts.map +1 -0
  91. package/lib/types/guidance.js +18 -0
  92. package/lib/types/guidance.js.map +1 -0
  93. package/lib/types/index.d.ts +15 -0
  94. package/lib/types/index.d.ts.map +1 -0
  95. package/lib/types/index.js +45 -0
  96. package/lib/types/index.js.map +1 -0
  97. package/lib/types/lifecycle.d.ts +75 -0
  98. package/lib/types/lifecycle.d.ts.map +1 -0
  99. package/lib/types/lifecycle.js +380 -0
  100. package/lib/types/lifecycle.js.map +1 -0
  101. package/lib/types/memory-bodies.d.ts +47 -0
  102. package/lib/types/memory-bodies.d.ts.map +1 -0
  103. package/lib/types/memory-bodies.js +160 -0
  104. package/lib/types/memory-bodies.js.map +1 -0
  105. package/lib/types/process.d.ts +14 -0
  106. package/lib/types/process.d.ts.map +1 -0
  107. package/lib/types/process.js +65 -0
  108. package/lib/types/process.js.map +1 -0
  109. package/lib/types/review-activity.d.ts +33 -0
  110. package/lib/types/review-activity.d.ts.map +1 -0
  111. package/lib/types/review-activity.js +37 -0
  112. package/lib/types/review-activity.js.map +1 -0
  113. package/lib/types/rpc.d.ts +12 -0
  114. package/lib/types/rpc.d.ts.map +1 -0
  115. package/lib/types/rpc.js +225 -0
  116. package/lib/types/rpc.js.map +1 -0
  117. package/lib/types/runner.d.ts +29 -0
  118. package/lib/types/runner.d.ts.map +1 -0
  119. package/lib/types/runner.js +150 -0
  120. package/lib/types/runner.js.map +1 -0
  121. package/lib/types/runtime-memory.d.ts +104 -0
  122. package/lib/types/runtime-memory.d.ts.map +1 -0
  123. package/lib/types/runtime-memory.js +415 -0
  124. package/lib/types/runtime-memory.js.map +1 -0
  125. package/lib/types/service.d.ts +173 -0
  126. package/lib/types/service.d.ts.map +1 -0
  127. package/lib/types/service.js +508 -0
  128. package/lib/types/service.js.map +1 -0
  129. package/lib/types/settings.d.ts +6 -0
  130. package/lib/types/settings.d.ts.map +1 -0
  131. package/lib/types/settings.js +75 -0
  132. package/lib/types/settings.js.map +1 -0
  133. package/lib/types/storage-scope.d.ts +39 -0
  134. package/lib/types/storage-scope.d.ts.map +1 -0
  135. package/lib/types/storage-scope.js +179 -0
  136. package/lib/types/storage-scope.js.map +1 -0
  137. package/lib/types/subagent.d.ts +97 -0
  138. package/lib/types/subagent.d.ts.map +1 -0
  139. package/lib/types/subagent.js +619 -0
  140. package/lib/types/subagent.js.map +1 -0
  141. package/lib/types/tools.d.ts +8 -0
  142. package/lib/types/tools.d.ts.map +1 -0
  143. package/lib/types/tools.js +328 -0
  144. package/lib/types/tools.js.map +1 -0
  145. package/package.json +111 -0
@@ -0,0 +1,1239 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
3
+ import Markdown from 'markdown-to-jsx';
4
+ import { QODERWORK_REVIEW_POLICY } from "../review-activity.js";
5
+ import { CATEGORIES, } from "../service.js";
6
+ import { MnemonClient } from "./api.js";
7
+ import { translateZh } from "./locales.js";
8
+ import { MnemonLogo } from "./MnemonLogo.js";
9
+ import css from './MnemonView.module.css';
10
+ /** 系统 → 三层存储 → 读写工具;组间以分隔线呈现。 */
11
+ const PAGE_NAV = [
12
+ {
13
+ aria: 'nav.group.system',
14
+ entries: [
15
+ { id: 'status', label: 'nav.status', detail: 'nav.status.detail', glyph: '⌘' },
16
+ ],
17
+ },
18
+ {
19
+ aria: 'nav.group.storage',
20
+ entries: [
21
+ { id: 'runtime', label: 'nav.runtime', detail: 'nav.runtime.detail', glyph: '◫' },
22
+ { id: 'overview', label: 'nav.bodies', detail: 'nav.bodies.detail', glyph: '◇' },
23
+ { id: 'documents', label: 'nav.documents', detail: 'nav.documents.detail', glyph: '▤' },
24
+ ],
25
+ },
26
+ {
27
+ aria: 'nav.group.tools',
28
+ entries: [
29
+ { id: 'remember', label: 'nav.remember', detail: 'nav.remember.detail', glyph: '+' },
30
+ { id: 'explore', label: 'nav.search', detail: 'nav.search.detail', glyph: '⌕' },
31
+ { id: 'entities', label: 'nav.entities', detail: 'nav.entities.detail', glyph: '◎' },
32
+ { id: 'list', label: 'nav.content', detail: 'nav.content.detail', glyph: '≡' },
33
+ ],
34
+ },
35
+ ];
36
+ const CATEGORY_KEYS = {
37
+ decision: 'category.decision',
38
+ preference: 'category.preference',
39
+ fact: 'category.fact',
40
+ insight: 'category.insight',
41
+ context: 'category.context',
42
+ general: 'category.general',
43
+ };
44
+ const I18nContext = createContext(translateZh);
45
+ function useT() { return useContext(I18nContext); }
46
+ function categoryLabel(t, category) { return CATEGORY_KEYS[category] === undefined ? category : t(CATEGORY_KEYS[category]); }
47
+ function humanBytes(bytes) {
48
+ if (bytes < 1024)
49
+ return `${bytes} B`;
50
+ if (bytes < 1024 * 1024)
51
+ return `${(bytes / 1024).toFixed(1)} KB`;
52
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
53
+ }
54
+ function message(error) {
55
+ return error instanceof Error ? error.message : String(error);
56
+ }
57
+ function short(value, max) {
58
+ return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
59
+ }
60
+ function insightKey(insight) {
61
+ return `${insight.memoryBodyId ?? 'memory'}:${insight.id}`;
62
+ }
63
+ function PageHeader(props) {
64
+ return (_jsxs("div", { className: css.pageHeader, children: [_jsxs("div", { children: [_jsx("h2", { children: props.title }), _jsx("p", { children: props.description })] }), _jsxs("div", { className: css.pageHeaderMeta, children: [props.meta !== undefined && _jsx("code", { children: props.meta }), props.action] })] }));
65
+ }
66
+ function EmptyState(props) {
67
+ return (_jsxs("div", { className: css.emptyState, children: [_jsx("div", { className: css.emptyGlyph, "aria-hidden": "true", children: _jsx("span", { children: props.glyph }) }), _jsxs("div", { children: [_jsx("h3", { children: props.title }), _jsx("p", { children: props.children })] })] }));
68
+ }
69
+ /** Full-text popup for a selected graph node whose inspector preview is clamped. */
70
+ function ContentPreview(props) {
71
+ const t = useT();
72
+ const close = useCallback(() => props.onClose(), [props]);
73
+ useEffect(() => {
74
+ const onKey = (event) => { if (event.key === 'Escape')
75
+ props.onClose(); };
76
+ window.addEventListener('keydown', onKey);
77
+ return () => window.removeEventListener('keydown', onKey);
78
+ }, [props]);
79
+ const meta = [props.kind, props.node.id, props.node.memoryBodyName].filter((entry) => entry !== undefined).join(' · ');
80
+ return (_jsx("div", { className: css.previewOverlay, onPointerDown: event => { if (event.target === event.currentTarget)
81
+ props.onClose(); }, children: _jsxs("div", { className: css.previewDialog, role: "dialog", "aria-modal": "true", "aria-label": t('overview.previewTitle'), children: [_jsxs("header", { className: css.previewHeading, children: [_jsx("span", { children: t('overview.previewTitle') }), _jsx("button", { type: "button", onClick: close, "aria-label": t('common.cancel'), children: "\u00D7" })] }), _jsx("div", { className: css.previewMeta, children: meta }), _jsx("div", { className: css.previewBody, children: _jsx("p", { children: props.node.content }) })] }) }));
82
+ }
83
+ const SAFE_LINK_PATTERN = /^(?:https?:|mailto:|#|\/)/iu;
84
+ function safeLink(href) {
85
+ if (href == null)
86
+ return undefined;
87
+ const value = href.trim();
88
+ return SAFE_LINK_PATTERN.test(value) ? value : undefined;
89
+ }
90
+ /** Render managed Markdown without raw HTML and with a deliberately small link surface. */
91
+ function DocumentMarkdown(props) {
92
+ return (_jsx("div", { className: css.markdownBody, children: _jsx(Markdown, { options: {
93
+ disableParsingRawHTML: true,
94
+ forceBlock: true,
95
+ overrides: {
96
+ a: {
97
+ component: ({ href, children, ...rest }) => {
98
+ const target = safeLink(href);
99
+ return target === undefined
100
+ ? _jsx("span", { children: children })
101
+ : _jsx("a", { ...rest, href: target, target: target.startsWith('http') ? '_blank' : undefined, rel: target.startsWith('http') ? 'noreferrer noopener' : undefined, children: children });
102
+ },
103
+ },
104
+ },
105
+ }, children: props.content }) }));
106
+ }
107
+ function InsightCard(props) {
108
+ const t = useT();
109
+ const [confirming, setConfirming] = useState(false);
110
+ const [forgetting, setForgetting] = useState(false);
111
+ const { insight } = props;
112
+ const meta = [
113
+ insight.memoryBodyName,
114
+ insight.category !== undefined ? categoryLabel(t, insight.category) : undefined,
115
+ insight.importance !== undefined ? t('common.importance', { value: insight.importance }) : undefined,
116
+ insight.score !== undefined ? `score ${insight.score.toFixed(3)}` : undefined,
117
+ insight.depth !== undefined ? t('common.hops', { count: insight.depth }) : undefined,
118
+ ].filter((entry) => entry !== undefined);
119
+ const forget = async () => {
120
+ setForgetting(true);
121
+ try {
122
+ await props.onForget(insight);
123
+ }
124
+ finally {
125
+ setForgetting(false);
126
+ setConfirming(false);
127
+ }
128
+ };
129
+ return (_jsxs("article", { className: css.insightCard, children: [_jsxs("div", { className: css.cardTop, children: [_jsx("div", { className: css.badges, children: meta.map(entry => _jsx("span", { className: css.badge, children: entry }, entry)) }), _jsx("code", { className: css.id, title: insight.id, children: insight.id.slice(0, 8) })] }), _jsx("p", { className: css.content, children: insight.content }), (insight.tags?.length ?? 0) > 0 && _jsx("div", { className: css.tags, children: insight.tags.map(tag => _jsxs("span", { children: ["#", tag] }, tag)) }), (insight.entities?.length ?? 0) > 0 && _jsx("div", { className: css.entities, children: insight.entities.map(entity => _jsx("span", { children: entity }, entity)) }), _jsx("div", { className: css.cardActions, children: confirming ? (_jsxs("div", { className: css.confirmBar, role: "group", "aria-label": t('card.confirmAria'), children: [_jsx("span", { children: t('card.confirmText') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: forgetting, onClick: () => void forget(), children: forgetting ? t('card.processing') : t('card.confirmForget') }), _jsx("button", { type: "button", className: css.ghostButton, disabled: forgetting, onClick: () => setConfirming(false), children: t('common.cancel') })] })) : (_jsxs(_Fragment, { children: [props.onRelated !== undefined && _jsx("button", { type: "button", className: css.ghostButton, onClick: () => props.onRelated?.(insight), children: t('card.related') }), props.onClone !== undefined && _jsx("button", { type: "button", className: css.ghostButton, onClick: () => props.onClone?.(insight), children: t('card.clone') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => void navigator.clipboard?.writeText(insight.id), children: t('common.copyId') }), props.writeEnabled && _jsx("button", { type: "button", className: css.dangerButton, onClick: () => setConfirming(true), children: t('card.forget') })] })) })] }));
130
+ }
131
+ const GRAPH_WIDTH = 930;
132
+ const GRAPH_HEIGHT = 520;
133
+ const GRAPH_MARGIN_X = 58;
134
+ const GRAPH_MARGIN_Y = 58;
135
+ const CATEGORY_ORDER = ['space', 'entity', 'preference', 'decision', 'fact', 'insight', 'context', 'general'];
136
+ function hash(value) {
137
+ let result = 2166136261;
138
+ for (const char of value)
139
+ result = Math.imul(result ^ char.charCodeAt(0), 16777619);
140
+ return result >>> 0;
141
+ }
142
+ function graphNodeKey(node) {
143
+ return node.graphId ?? node.id;
144
+ }
145
+ function graphNodeKind(node) {
146
+ return node.kind ?? 'memory';
147
+ }
148
+ function spaceGraphId(id) {
149
+ return `space:${id}`;
150
+ }
151
+ function entityGraphId(entity) {
152
+ return `entity:${encodeURIComponent(normalizeEntity(entity))}`;
153
+ }
154
+ function normalizeEntity(entity) {
155
+ return entity.normalize('NFKC').trim().toLocaleLowerCase();
156
+ }
157
+ /** Add routing scopes and entity indexes without issuing another recall. */
158
+ function enrichMultiSpaceGraph(graph, bodies) {
159
+ if (graph.nodes.length === 0)
160
+ return graph;
161
+ const memories = graph.nodes.map(node => ({ ...node, kind: 'memory' }));
162
+ const memoriesByBody = new Map();
163
+ for (const node of memories) {
164
+ if (node.memoryBodyId === undefined)
165
+ continue;
166
+ memoriesByBody.set(node.memoryBodyId, [...(memoriesByBody.get(node.memoryBodyId) ?? []), node]);
167
+ }
168
+ const activeBodies = bodies.filter(body => body.active && ((memoriesByBody.get(body.id)?.length ?? 0) > 0 || (body.stats?.topEntities.length ?? 0) > 0));
169
+ const spaceNodes = activeBodies.map(body => ({
170
+ id: body.id,
171
+ graphId: spaceGraphId(body.id),
172
+ kind: 'space',
173
+ category: 'space',
174
+ content: body.name,
175
+ color: '#22a879',
176
+ memoryBodyId: body.id,
177
+ memoryBodyName: body.name,
178
+ occurrenceCount: body.stats?.totalInsights ?? memoriesByBody.get(body.id)?.length ?? 0,
179
+ }));
180
+ // Native Mnemon entity edges connect two memories. This overview renders
181
+ // entities as first-class nodes, so retaining those edges would falsely make
182
+ // a memory-to-memory edge look like an entity-to-memory association.
183
+ const edges = graph.edges.filter(edge => edge.type !== 'entity');
184
+ for (const body of activeBodies) {
185
+ for (const memory of memoriesByBody.get(body.id) ?? []) {
186
+ edges.push({ sourceId: spaceGraphId(body.id), targetId: graphNodeKey(memory), label: 'scope', color: '#708199', type: 'scope' });
187
+ }
188
+ }
189
+ const bodiesById = new Map(activeBodies.map(body => [body.id, body]));
190
+ const indexedEntities = new Map();
191
+ for (const memory of memories) {
192
+ const body = memory.memoryBodyId === undefined ? undefined : bodiesById.get(memory.memoryBodyId);
193
+ if (body === undefined)
194
+ continue;
195
+ const seen = new Set();
196
+ for (const rawEntity of memory.entities ?? []) {
197
+ const entity = rawEntity.trim();
198
+ const key = normalizeEntity(entity);
199
+ if (key === '' || seen.has(key))
200
+ continue;
201
+ seen.add(key);
202
+ const current = indexedEntities.get(key);
203
+ if (current === undefined)
204
+ indexedEntities.set(key, { entity, memories: [memory], bodies: [body] });
205
+ else {
206
+ current.memories.push(memory);
207
+ if (!current.bodies.some(candidate => candidate.id === body.id))
208
+ current.bodies.push(body);
209
+ }
210
+ }
211
+ }
212
+ const entities = [...indexedEntities.values()].sort((left, right) => right.memories.length - left.memories.length || left.entity.localeCompare(right.entity)).slice(0, 24);
213
+ const entityNodes = entities.map(item => ({
214
+ id: item.entity,
215
+ graphId: entityGraphId(item.entity),
216
+ kind: 'entity',
217
+ category: 'entity',
218
+ content: item.entity,
219
+ color: '#2b9db9',
220
+ occurrenceCount: item.memories.length,
221
+ memoryBodyIds: item.bodies.map(body => body.id),
222
+ memoryBodyNames: item.bodies.map(body => body.name),
223
+ }));
224
+ for (const item of entities) {
225
+ const key = entityGraphId(item.entity);
226
+ for (const memory of item.memories)
227
+ edges.push({ sourceId: key, targetId: graphNodeKey(memory), label: item.entity, color: '#22a879', type: 'entity' });
228
+ }
229
+ return { ...graph, nodes: [...spaceNodes, ...entityNodes, ...memories], edges };
230
+ }
231
+ function graphKindLabel(t, node) {
232
+ const kind = graphNodeKind(node);
233
+ return kind === 'space' ? t('graph.kindSpace') : kind === 'entity' ? t('graph.kindEntity') : categoryLabel(t, node.category ?? 'general');
234
+ }
235
+ function activeCategoryAnchors(grouped) {
236
+ const categories = [...grouped.keys()].sort((left, right) => {
237
+ const leftIndex = CATEGORY_ORDER.indexOf(left);
238
+ const rightIndex = CATEGORY_ORDER.indexOf(right);
239
+ return (leftIndex < 0 ? CATEGORY_ORDER.length : leftIndex) - (rightIndex < 0 ? CATEGORY_ORDER.length : rightIndex);
240
+ });
241
+ const anchors = new Map();
242
+ if (categories.length === 1) {
243
+ anchors.set(categories[0], { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 });
244
+ return anchors;
245
+ }
246
+ categories.forEach((category, index) => {
247
+ const angle = -Math.PI / 2 + (index / categories.length) * Math.PI * 2;
248
+ anchors.set(category, {
249
+ x: GRAPH_WIDTH / 2 + Math.cos(angle) * Math.min(250, 115 + categories.length * 23),
250
+ y: GRAPH_HEIGHT / 2 + Math.sin(angle) * Math.min(165, 78 + categories.length * 15),
251
+ });
252
+ });
253
+ return anchors;
254
+ }
255
+ function clampGraphPosition(position) {
256
+ return {
257
+ x: Math.min(GRAPH_WIDTH - GRAPH_MARGIN_X, Math.max(GRAPH_MARGIN_X, position.x)),
258
+ y: Math.min(GRAPH_HEIGHT - GRAPH_MARGIN_Y, Math.max(GRAPH_MARGIN_Y, position.y)),
259
+ };
260
+ }
261
+ function naturalGraphPositions(nodes, edges) {
262
+ const positions = new Map();
263
+ const grouped = new Map();
264
+ for (const node of nodes) {
265
+ const category = node.category ?? 'general';
266
+ grouped.set(category, [...(grouped.get(category) ?? []), node]);
267
+ }
268
+ const anchors = activeCategoryAnchors(grouped);
269
+ for (const [category, items] of grouped) {
270
+ const anchor = anchors.get(category) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
271
+ items.forEach((node, index) => {
272
+ const seed = hash(graphNodeKey(node));
273
+ const angle = index * 2.399963 + ((seed % 37) / 37) * .4;
274
+ const radius = items.length === 1 ? 0 : 24 + Math.sqrt(index + 1) * 35;
275
+ positions.set(graphNodeKey(node), clampGraphPosition({ x: anchor.x + Math.cos(angle) * radius, y: anchor.y + Math.sin(angle) * radius }));
276
+ });
277
+ }
278
+ const velocities = new Map(nodes.map(node => [graphNodeKey(node), { x: 0, y: 0 }]));
279
+ const visibleIds = new Set(nodes.map(graphNodeKey));
280
+ const visibleEdges = edges.filter(edge => visibleIds.has(edge.sourceId) && visibleIds.has(edge.targetId));
281
+ for (let iteration = 0; iteration < 150; iteration += 1) {
282
+ const cooling = 1 - iteration / 180;
283
+ for (let leftIndex = 0; leftIndex < nodes.length; leftIndex += 1) {
284
+ const left = nodes[leftIndex];
285
+ const leftPosition = positions.get(graphNodeKey(left));
286
+ const leftVelocity = velocities.get(graphNodeKey(left));
287
+ for (let rightIndex = leftIndex + 1; rightIndex < nodes.length; rightIndex += 1) {
288
+ const right = nodes[rightIndex];
289
+ const rightPosition = positions.get(graphNodeKey(right));
290
+ const rightVelocity = velocities.get(graphNodeKey(right));
291
+ let dx = leftPosition.x - rightPosition.x;
292
+ let dy = leftPosition.y - rightPosition.y;
293
+ if (dx === 0 && dy === 0) {
294
+ dx = ((hash(graphNodeKey(left)) % 13) - 6) || 1;
295
+ dy = ((hash(graphNodeKey(right)) % 11) - 5) || -1;
296
+ }
297
+ const distanceSquared = Math.max(100, dx * dx + dy * dy);
298
+ const distance = Math.sqrt(distanceSquared);
299
+ const repulsion = Math.min(9, 18_000 / distanceSquared) * cooling;
300
+ const collision = distance < 66 ? (66 - distance) * .08 : 0;
301
+ const force = repulsion + collision;
302
+ const forceX = (dx / distance) * force;
303
+ const forceY = (dy / distance) * force;
304
+ leftVelocity.x += forceX;
305
+ leftVelocity.y += forceY;
306
+ rightVelocity.x -= forceX;
307
+ rightVelocity.y -= forceY;
308
+ }
309
+ }
310
+ for (const edge of visibleEdges) {
311
+ const source = positions.get(edge.sourceId);
312
+ const target = positions.get(edge.targetId);
313
+ const sourceVelocity = velocities.get(edge.sourceId);
314
+ const targetVelocity = velocities.get(edge.targetId);
315
+ const dx = target.x - source.x;
316
+ const dy = target.y - source.y;
317
+ const distance = Math.max(1, Math.hypot(dx, dy));
318
+ const sparseScale = nodes.length <= 3 ? 2 : nodes.length <= 8 ? 1.45 : 1;
319
+ const desired = (edge.type === 'scope' ? 138 : edge.type === 'entity' ? 94 : edge.type === 'semantic' ? 118 : 106) * sparseScale;
320
+ const spring = (distance - desired) * .018 * cooling;
321
+ const forceX = (dx / distance) * spring;
322
+ const forceY = (dy / distance) * spring;
323
+ sourceVelocity.x += forceX;
324
+ sourceVelocity.y += forceY;
325
+ targetVelocity.x -= forceX;
326
+ targetVelocity.y -= forceY;
327
+ }
328
+ for (const node of nodes) {
329
+ const key = graphNodeKey(node);
330
+ const position = positions.get(key);
331
+ const velocity = velocities.get(key);
332
+ const anchor = anchors.get(node.category ?? 'general') ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
333
+ velocity.x += (anchor.x - position.x) * .0035 * cooling + (GRAPH_WIDTH / 2 - position.x) * .0008;
334
+ velocity.y += (anchor.y - position.y) * .0035 * cooling + (GRAPH_HEIGHT / 2 - position.y) * .0008;
335
+ velocity.x = Math.max(-12, Math.min(12, velocity.x * .76));
336
+ velocity.y = Math.max(-12, Math.min(12, velocity.y * .76));
337
+ positions.set(key, clampGraphPosition({ x: position.x + velocity.x, y: position.y + velocity.y }));
338
+ }
339
+ }
340
+ return positions;
341
+ }
342
+ function uniformGraphPositions(nodes) {
343
+ const positions = new Map();
344
+ const ordered = [...nodes].sort((left, right) => {
345
+ const categoryDifference = CATEGORY_ORDER.indexOf(left.category ?? 'general') - CATEGORY_ORDER.indexOf(right.category ?? 'general');
346
+ return categoryDifference === 0 ? left.id.localeCompare(right.id) : categoryDifference;
347
+ });
348
+ const columns = Math.max(1, Math.ceil(Math.sqrt(ordered.length * 1.65)));
349
+ const rows = Math.max(1, Math.ceil(ordered.length / columns));
350
+ const cellWidth = (GRAPH_WIDTH - GRAPH_MARGIN_X * 2) / columns;
351
+ const cellHeight = (GRAPH_HEIGHT - GRAPH_MARGIN_Y * 2) / rows;
352
+ ordered.forEach((node, index) => {
353
+ const row = Math.floor(index / columns);
354
+ const column = index % columns;
355
+ const rowLength = Math.min(columns, ordered.length - row * columns);
356
+ const rowOffset = (columns - rowLength) * cellWidth / 2;
357
+ positions.set(graphNodeKey(node), {
358
+ x: GRAPH_MARGIN_X + rowOffset + cellWidth * (column + .5),
359
+ y: GRAPH_MARGIN_Y + cellHeight * (row + .5),
360
+ });
361
+ });
362
+ return positions;
363
+ }
364
+ function graphPoint(svg, clientX, clientY) {
365
+ const matrix = svg.getScreenCTM?.();
366
+ if (matrix !== null && matrix !== undefined && typeof svg.createSVGPoint === 'function') {
367
+ const point = svg.createSVGPoint();
368
+ point.x = clientX;
369
+ point.y = clientY;
370
+ return clampGraphPosition(point.matrixTransform(matrix.inverse()));
371
+ }
372
+ const bounds = svg.getBoundingClientRect();
373
+ const width = bounds.width || GRAPH_WIDTH;
374
+ const height = bounds.height || GRAPH_HEIGHT;
375
+ return clampGraphPosition({ x: (clientX - bounds.left) * GRAPH_WIDTH / width, y: (clientY - bounds.top) * GRAPH_HEIGHT / height });
376
+ }
377
+ function MemoryGraph(props) {
378
+ const t = useT();
379
+ const visibleNodes = useMemo(() => {
380
+ const spaces = props.graph.nodes.filter(node => graphNodeKind(node) === 'space');
381
+ const entities = props.graph.nodes.filter(node => graphNodeKind(node) === 'entity').slice(0, 20);
382
+ const memories = props.graph.nodes.filter(node => graphNodeKind(node) === 'memory').slice(0, Math.max(0, 60 - spaces.length - entities.length));
383
+ return [...spaces, ...entities, ...memories].slice(0, 60);
384
+ }, [props.graph.nodes]);
385
+ const visibleIds = useMemo(() => new Set(visibleNodes.map(graphNodeKey)), [visibleNodes]);
386
+ const visibleKinds = useMemo(() => new Map(visibleNodes.map(node => [graphNodeKey(node), graphNodeKind(node)])), [visibleNodes]);
387
+ const edges = useMemo(() => {
388
+ const priority = new Map([['entity', 0], ['scope', 1], ['causal', 2], ['semantic', 3], ['temporal', 4]]);
389
+ return props.graph.edges
390
+ .filter(edge => visibleIds.has(edge.sourceId) && visibleIds.has(edge.targetId))
391
+ .map((edge, index) => ({ edge, index }))
392
+ .sort((left, right) => (priority.get(left.edge.type ?? 'temporal') ?? 5) - (priority.get(right.edge.type ?? 'temporal') ?? 5) || left.index - right.index)
393
+ .slice(0, 180)
394
+ .map(({ edge }) => edge);
395
+ }, [props.graph.edges, visibleIds]);
396
+ const curvedEdges = useMemo(() => {
397
+ const groups = new Map();
398
+ edges.forEach((edge, index) => {
399
+ const key = [edge.sourceId, edge.targetId].sort().join('::');
400
+ groups.set(key, [...(groups.get(key) ?? []), index]);
401
+ });
402
+ return edges.map((edge, index) => {
403
+ const key = [edge.sourceId, edge.targetId].sort().join('::');
404
+ const group = groups.get(key) ?? [index];
405
+ const groupIndex = group.indexOf(index);
406
+ return { edge, offset: (groupIndex - (group.length - 1) / 2) * 12 };
407
+ });
408
+ }, [edges]);
409
+ const layoutKey = `${visibleNodes.map(node => `${graphNodeKey(node)}:${graphNodeKind(node)}:${node.category ?? 'general'}`).join('|')}::${edges.map(edge => `${edge.sourceId}>${edge.targetId}:${edge.type ?? 'temporal'}`).join('|')}`;
410
+ const naturalLayout = useMemo(() => naturalGraphPositions(visibleNodes, edges), [layoutKey]);
411
+ const [positions, setPositions] = useState(() => naturalLayout);
412
+ const [layoutMode, setLayoutMode] = useState('natural');
413
+ const positionsRef = useRef(positions);
414
+ const animationRef = useRef(null);
415
+ const dragRef = useRef(null);
416
+ const commitPositions = useCallback((next) => {
417
+ positionsRef.current = next;
418
+ setPositions(next);
419
+ }, []);
420
+ const cancelAnimation = useCallback(() => {
421
+ if (animationRef.current !== null && typeof window.cancelAnimationFrame === 'function')
422
+ window.cancelAnimationFrame(animationRef.current);
423
+ animationRef.current = null;
424
+ }, []);
425
+ const animateTo = useCallback((target, mode) => {
426
+ cancelAnimation();
427
+ setLayoutMode(mode);
428
+ if (typeof window.requestAnimationFrame !== 'function') {
429
+ commitPositions(target);
430
+ return;
431
+ }
432
+ const start = new Map(positionsRef.current);
433
+ const startedAt = performance.now();
434
+ const tick = (time) => {
435
+ const progress = Math.min(1, (time - startedAt) / 620);
436
+ const eased = 1 - Math.pow(1 - progress, 3);
437
+ const next = new Map();
438
+ for (const [id, destination] of target) {
439
+ const origin = start.get(id) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
440
+ next.set(id, { x: origin.x + (destination.x - origin.x) * eased, y: origin.y + (destination.y - origin.y) * eased });
441
+ }
442
+ commitPositions(next);
443
+ if (progress < 1)
444
+ animationRef.current = window.requestAnimationFrame(tick);
445
+ else
446
+ animationRef.current = null;
447
+ };
448
+ animationRef.current = window.requestAnimationFrame(tick);
449
+ }, [cancelAnimation, commitPositions]);
450
+ useEffect(() => { animateTo(naturalLayout, 'natural'); }, [layoutKey]);
451
+ useEffect(() => () => cancelAnimation(), [cancelAnimation]);
452
+ const beginDrag = (event, nodeId) => {
453
+ cancelAnimation();
454
+ dragRef.current = { nodeId, pointerId: event.pointerId, startX: event.clientX, startY: event.clientY, moved: false };
455
+ event.currentTarget.setPointerCapture?.(event.pointerId);
456
+ };
457
+ const moveDrag = (event) => {
458
+ const drag = dragRef.current;
459
+ const svg = event.currentTarget.ownerSVGElement;
460
+ if (drag === null || svg === null || drag.pointerId !== event.pointerId)
461
+ return;
462
+ if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 4)
463
+ return;
464
+ drag.moved = true;
465
+ const point = graphPoint(svg, event.clientX, event.clientY);
466
+ const next = new Map(positionsRef.current);
467
+ next.set(drag.nodeId, point);
468
+ commitPositions(next);
469
+ setLayoutMode('custom');
470
+ };
471
+ const endDrag = (event) => {
472
+ const drag = dragRef.current;
473
+ if (drag === null || drag.pointerId !== event.pointerId)
474
+ return;
475
+ const svg = event.currentTarget.ownerSVGElement;
476
+ if (drag.moved && svg !== null) {
477
+ const next = new Map(positionsRef.current);
478
+ next.set(drag.nodeId, graphPoint(svg, event.clientX, event.clientY));
479
+ commitPositions(next);
480
+ }
481
+ dragRef.current = null;
482
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
483
+ if (!drag.moved) {
484
+ const node = visibleNodes.find(candidate => graphNodeKey(candidate) === drag.nodeId);
485
+ if (node !== undefined)
486
+ props.onSelect(node);
487
+ }
488
+ };
489
+ const cancelDrag = (event) => {
490
+ if (dragRef.current?.pointerId === event.pointerId)
491
+ dragRef.current = null;
492
+ };
493
+ const nudge = (nodeId, dx, dy) => {
494
+ cancelAnimation();
495
+ const current = positionsRef.current.get(nodeId);
496
+ if (current === undefined)
497
+ return;
498
+ const next = new Map(positionsRef.current);
499
+ next.set(nodeId, clampGraphPosition({ x: current.x + dx, y: current.y + dy }));
500
+ commitPositions(next);
501
+ setLayoutMode('custom');
502
+ };
503
+ const layoutLabel = t(layoutMode === 'natural' ? 'graph.layoutNatural' : layoutMode === 'uniform' ? 'graph.layoutUniform' : 'graph.layoutCustom');
504
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.graphCanvasControls, role: "toolbar", "aria-label": t('graph.layoutAria'), children: [_jsxs("span", { role: "status", "aria-label": t('graph.layoutStatus', { layout: layoutLabel }), children: [_jsx("i", {}), t('graph.draggable', { layout: layoutLabel })] }), _jsx("button", { type: "button", "data-active": layoutMode === 'natural' || undefined, onClick: () => animateTo(naturalGraphPositions(visibleNodes, edges), 'natural'), children: t('graph.naturalAction') }), _jsx("button", { type: "button", "data-active": layoutMode === 'uniform' || undefined, onClick: () => animateTo(uniformGraphPositions(visibleNodes), 'uniform'), children: t('graph.uniformAction') })] }), _jsxs("svg", { className: css.graphSvg, viewBox: `0 0 ${GRAPH_WIDTH} ${GRAPH_HEIGHT}`, role: "img", "data-layout": layoutMode, "data-density": visibleNodes.length <= 12 ? 'sparse' : 'dense', "aria-label": t('graph.aria', { nodes: props.graph.nodes.length, edges: props.graph.edges.length }), children: [_jsxs("defs", { children: [_jsx("pattern", { id: "mnemon-grid", width: "26", height: "26", patternUnits: "userSpaceOnUse", children: _jsx("path", { d: "M 26 0 L 0 0 0 26", className: css.graphGridLine, fill: "none" }) }), _jsxs("filter", { id: "mnemon-glow", x: "-100%", y: "-100%", width: "300%", height: "300%", children: [_jsx("feGaussianBlur", { stdDeviation: "4", result: "blur" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "blur" }), _jsx("feMergeNode", { in: "SourceGraphic" })] })] })] }), _jsx("rect", { width: GRAPH_WIDTH, height: GRAPH_HEIGHT, className: css.graphBackdrop }), _jsx("rect", { width: GRAPH_WIDTH, height: GRAPH_HEIGHT, fill: "url(#mnemon-grid)" }), curvedEdges.map(({ edge, offset }, index) => {
505
+ const source = positions.get(edge.sourceId) ?? naturalLayout.get(edge.sourceId) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
506
+ const target = positions.get(edge.targetId) ?? naturalLayout.get(edge.targetId) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
507
+ const dx = target.x - source.x;
508
+ const dy = target.y - source.y;
509
+ const distance = Math.max(1, Math.hypot(dx, dy));
510
+ const direction = edge.sourceId.localeCompare(edge.targetId) <= 0 ? 1 : -1;
511
+ const controlX = (source.x + target.x) / 2 - (dy / distance) * offset * direction;
512
+ const controlY = (source.y + target.y) / 2 + (dx / distance) * offset * direction;
513
+ return _jsx("path", { d: `M ${source.x} ${source.y} Q ${controlX} ${controlY} ${target.x} ${target.y}`, className: css.graphEdge, "data-edge": edge.type ?? 'temporal', "data-source-id": edge.sourceId, "data-target-id": edge.targetId, "data-source-kind": visibleKinds.get(edge.sourceId), "data-target-kind": visibleKinds.get(edge.targetId) }, `${edge.sourceId}-${edge.targetId}-${index}`);
514
+ }), visibleNodes.map((node, index) => {
515
+ const nodeKey = graphNodeKey(node);
516
+ const position = positions.get(nodeKey) ?? naturalLayout.get(nodeKey) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
517
+ const selected = props.selectedId === nodeKey;
518
+ const showLabel = selected || visibleNodes.length < 22 || index % 3 === 0;
519
+ return (_jsxs("g", { className: css.graphNode, "data-node-id": nodeKey, "data-category": node.category ?? 'general', "data-kind": graphNodeKind(node), "data-selected": selected || undefined, transform: `translate(${position.x} ${position.y})`, role: "button", tabIndex: 0, "aria-label": `${graphKindLabel(t, node)}: ${short(node.content, 80)}`, "data-dragging": dragRef.current?.nodeId === nodeKey || undefined, onPointerDown: event => beginDrag(event, nodeKey), onPointerMove: moveDrag, onPointerUp: endDrag, onPointerCancel: cancelDrag, onLostPointerCapture: cancelDrag, onClick: () => props.onSelect(node), onKeyDown: event => {
520
+ if (event.key === 'Enter' || event.key === ' ')
521
+ props.onSelect(node);
522
+ else if (event.key === 'ArrowLeft') {
523
+ event.preventDefault();
524
+ nudge(nodeKey, -12, 0);
525
+ }
526
+ else if (event.key === 'ArrowRight') {
527
+ event.preventDefault();
528
+ nudge(nodeKey, 12, 0);
529
+ }
530
+ else if (event.key === 'ArrowUp') {
531
+ event.preventDefault();
532
+ nudge(nodeKey, 0, -12);
533
+ }
534
+ else if (event.key === 'ArrowDown') {
535
+ event.preventDefault();
536
+ nudge(nodeKey, 0, 12);
537
+ }
538
+ }, children: [graphNodeKind(node) === 'space'
539
+ ? _jsxs(_Fragment, { children: [_jsx("rect", { x: selected ? -20 : -17, y: selected ? -15 : -13, width: selected ? 40 : 34, height: selected ? 30 : 26, rx: "9", className: css.nodeHalo, filter: selected ? 'url(#mnemon-glow)' : undefined }), _jsx("circle", { r: selected ? 6 : 5, className: css.nodeCore })] })
540
+ : graphNodeKind(node) === 'entity'
541
+ ? _jsxs(_Fragment, { children: [_jsx("path", { d: selected ? 'M 0 -18 L 18 0 L 0 18 L -18 0 Z' : 'M 0 -14 L 14 0 L 0 14 L -14 0 Z', className: css.nodeHalo, filter: selected ? 'url(#mnemon-glow)' : undefined }), _jsx("circle", { r: selected ? 5 : 4, className: css.nodeCore })] })
542
+ : _jsxs(_Fragment, { children: [_jsx("circle", { r: selected ? 17 : visibleNodes.length <= 12 ? 14 : 11, className: css.nodeHalo, filter: selected ? 'url(#mnemon-glow)' : undefined }), _jsx("circle", { r: selected ? 7 : visibleNodes.length <= 12 ? 6 : 4.5, className: css.nodeCore })] }), (selected || visibleNodes.length <= 12) && graphNodeKind(node) === 'memory' && node.memoryBodyName !== undefined && _jsx("text", { x: "0", y: "-18", textAnchor: "middle", className: css.nodeBodyLabel, children: short(node.memoryBodyName, 12) }), showLabel && _jsx("text", { x: visibleNodes.length <= 12 ? 19 : 15, y: "4", className: css.nodeLabel, children: short(node.content.replace(/\s+/gu, ' '), selected ? 34 : visibleNodes.length <= 12 ? 26 : 19) })] }, nodeKey));
543
+ })] })] }));
544
+ }
545
+ function OverviewPage(props) {
546
+ const t = useT();
547
+ const [graph, setGraph] = useState(null);
548
+ const [catalog, setCatalog] = useState(null);
549
+ const [selected, setSelected] = useState(null);
550
+ const [loading, setLoading] = useState(true);
551
+ const [error, setError] = useState(null);
552
+ const [changing, setChanging] = useState(null);
553
+ const [creating, setCreating] = useState(false);
554
+ const [bodyName, setBodyName] = useState('');
555
+ const [bodyDescription, setBodyDescription] = useState('');
556
+ const [catalogUnavailable, setCatalogUnavailable] = useState(false);
557
+ const [editingBody, setEditingBody] = useState(null);
558
+ const [editName, setEditName] = useState('');
559
+ const [editDescription, setEditDescription] = useState('');
560
+ const [savingBody, setSavingBody] = useState(null);
561
+ const [preview, setPreview] = useState(null);
562
+ const load = useCallback(async (quiet = false) => {
563
+ if (!quiet)
564
+ setLoading(true);
565
+ setError(null);
566
+ try {
567
+ const [nextCatalog, next] = await Promise.all([
568
+ props.client.bodies().then(next => { setCatalogUnavailable(false); return next; }).catch(() => {
569
+ setCatalogUnavailable(!props.catalogKnown);
570
+ return {
571
+ items: props.fallbackBodies,
572
+ total: props.fallbackBodies.length,
573
+ activeCount: props.fallbackBodies.filter(body => body.active).length,
574
+ directory: props.fallbackDirectory ?? '',
575
+ generatedAt: new Date().toISOString(),
576
+ };
577
+ }),
578
+ props.client.graph(),
579
+ ]);
580
+ const enriched = enrichMultiSpaceGraph(next, nextCatalog.items);
581
+ setCatalog(nextCatalog);
582
+ setGraph(enriched);
583
+ setSelected(current => current === null ? null : enriched.nodes.find(node => graphNodeKey(node) === graphNodeKey(current)) ?? null);
584
+ }
585
+ catch (reason) {
586
+ setError(message(reason));
587
+ }
588
+ finally {
589
+ setLoading(false);
590
+ }
591
+ }, [props.catalogKnown, props.client, props.fallbackBodies, props.fallbackDirectory]);
592
+ useEffect(() => {
593
+ void load();
594
+ const timer = window.setInterval(() => void load(true), 15_000);
595
+ return () => window.clearInterval(timer);
596
+ }, [load, props.revision]);
597
+ const toggle = async (body) => {
598
+ setChanging(body.id);
599
+ setError(null);
600
+ try {
601
+ await props.client.updateBody(body.id, { active: !body.active });
602
+ await load(true);
603
+ props.onMutate();
604
+ }
605
+ catch (reason) {
606
+ setError(message(reason));
607
+ }
608
+ finally {
609
+ setChanging(null);
610
+ }
611
+ };
612
+ const beginEdit = (body) => {
613
+ setEditingBody(body.id);
614
+ setEditName(body.name);
615
+ setEditDescription(body.description ?? '');
616
+ setError(null);
617
+ };
618
+ const saveEdit = async (event, body) => {
619
+ event.preventDefault();
620
+ if (editName.trim() === '')
621
+ return;
622
+ setSavingBody(body.id);
623
+ setError(null);
624
+ try {
625
+ await props.client.updateBody(body.id, { name: editName, description: editDescription });
626
+ setEditingBody(null);
627
+ await load(true);
628
+ props.onMutate();
629
+ }
630
+ catch (reason) {
631
+ setError(message(reason));
632
+ }
633
+ finally {
634
+ setSavingBody(null);
635
+ }
636
+ };
637
+ const create = async (event) => {
638
+ event.preventDefault();
639
+ if (bodyName.trim() === '' || bodyDescription.trim() === '')
640
+ return;
641
+ setCreating(true);
642
+ setError(null);
643
+ try {
644
+ await props.client.createBody({ name: bodyName, description: bodyDescription });
645
+ setBodyName('');
646
+ setBodyDescription('');
647
+ await load(true);
648
+ props.onMutate();
649
+ }
650
+ catch (reason) {
651
+ setError(message(reason));
652
+ }
653
+ finally {
654
+ setCreating(false);
655
+ }
656
+ };
657
+ const generated = graph === null ? t('overview.waitingSnapshot') : t('overview.updatedAt', { time: new Date(graph.generatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) });
658
+ const graphSpaces = graph?.nodes.filter(node => graphNodeKind(node) === 'space').length ?? 0;
659
+ const graphEntities = graph?.nodes.filter(node => graphNodeKind(node) === 'entity').length ?? 0;
660
+ const graphMemories = graph?.nodes.filter(node => graphNodeKind(node) === 'memory').length ?? 0;
661
+ const selectedKind = selected === null ? null : graphNodeKind(selected);
662
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('overview.title'), description: t('overview.description'), meta: t('overview.interval'), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: loading ? t('overview.syncing') : t('overview.syncNow') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), _jsxs("section", { className: css.bodyDirectory, "aria-label": t('overview.directory'), children: [_jsxs("div", { className: css.bodyDirectoryHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('overview.directory') }), _jsx("p", { children: t('overview.directory.description') }), _jsx("code", { className: css.bodyDirectoryPath, children: catalogUnavailable ? t('overview.directory.unsynced') : catalog?.directory || props.fallbackDirectory || t('overview.directory.waiting') })] }), _jsx("strong", { children: catalogUnavailable ? t('overview.directory.unsyncedBadge') : `${catalog?.activeCount ?? '—'} / ${catalog?.total ?? '—'} ${t('common.active')}` })] }), _jsxs("div", { className: css.bodyGrid, children: [catalog?.items.map(body => (_jsx("article", { className: css.bodyCard, "data-active": body.active || undefined, "data-healthy": body.healthy || undefined, "data-editing": editingBody === body.id || undefined, title: body.error, children: editingBody === body.id ? (_jsxs("form", { className: css.bodyEdit, onSubmit: event => void saveEdit(event, body), children: [_jsxs("label", { children: [t('overview.editName'), _jsx("input", { "aria-label": t('overview.editName'), value: editName, onChange: event => setEditName(event.target.value), maxLength: 100, required: true })] }), _jsxs("label", { children: [t('overview.editDescription'), _jsx("textarea", { "aria-label": t('overview.editDescription'), value: editDescription, onChange: event => setEditDescription(event.target.value), rows: 3, maxLength: 1000 })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "submit", className: css.primaryButton, disabled: savingBody === body.id || editName.trim() === '', children: savingBody === body.id ? t('overview.savingBody') : t('overview.saveBody') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditingBody(null), children: t('common.cancel') })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.bodyCardTop, children: [_jsx("span", { className: css.bodySignal }), _jsxs("div", { children: [_jsx("strong", { children: body.name }), _jsx("code", { children: body.id }), _jsx("small", { className: css.bodyHealth, children: body.healthy ? t('overview.storageHealthy') : t('overview.storageUnhealthy') })] }), _jsxs("div", { className: css.bodyCardActions, children: [_jsxs("button", { type: "button", className: css.bodySwitch, role: "switch", "aria-checked": body.active, "aria-label": t('overview.toggleAria', { name: body.name }), disabled: !props.writeEnabled || changing === body.id, onClick: () => void toggle(body), children: [_jsx("span", { className: css.bodySwitchTrack, "aria-hidden": "true", children: _jsx("i", {}) }), _jsx("span", { children: changing === body.id ? t('overview.toggling') : body.active ? t('common.active') : t('common.inactive') })] }), _jsx("button", { type: "button", className: css.bodyEditButton, "aria-label": t('overview.editBodyAria', { name: body.name }), title: t('overview.editBody'), disabled: !props.writeEnabled, onClick: () => beginEdit(body), children: "\u270E" })] })] }), _jsx("p", { children: body.description || t('overview.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: t('common.memories', { count: body.stats?.totalInsights ?? 0 }) }), _jsx("span", { children: t('common.edges', { count: body.stats?.edgeCount ?? 0 }) }), _jsx("span", { children: humanBytes(body.stats?.dbSizeBytes ?? 0) })] })] })) }, body.id))), catalog?.total === 0 && _jsxs("div", { className: css.bodyDirectoryEmpty, children: [_jsx("span", { children: "\u25C7" }), _jsxs("div", { children: [_jsx("strong", { children: catalogUnavailable ? t('overview.unsyncedTitle') : t('overview.emptyTitle') }), _jsx("p", { children: catalogUnavailable ? t('overview.unsyncedShort') : t('overview.emptyShort') })] })] })] }), props.writeEnabled && !catalogUnavailable && _jsxs("details", { className: css.bodyCreate, open: catalog?.total === 0 ? true : undefined, children: [_jsx("summary", { children: t('overview.create') }), _jsxs("form", { onSubmit: event => void create(event), children: [_jsx("input", { "aria-label": t('overview.createName'), value: bodyName, onChange: event => setBodyName(event.target.value), placeholder: t('overview.createNamePlaceholder'), required: true }), _jsx("input", { "aria-label": t('overview.createDescription'), value: bodyDescription, onChange: event => setBodyDescription(event.target.value), placeholder: t('overview.createDescriptionPlaceholder'), required: true }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: creating, children: creating ? t('overview.creating') : t('overview.createAction') })] })] })] }), !catalogUnavailable && graph !== null && graph.nodes.length > 0 ? (_jsxs("div", { className: css.graphLayout, children: [_jsxs("section", { className: css.graphPanel, children: [_jsxs("div", { className: css.graphToolbar, children: [_jsxs("div", { children: [_jsx("span", { className: css.liveDot }), t('overview.snapshot'), " ", _jsx("small", { children: generated })] }), _jsxs("div", { className: css.graphLegend, children: [_jsx("span", { "data-edge": "scope", children: t('overview.edgeScope') }), _jsx("span", { "data-edge": "temporal", children: t('overview.edgeTemporal') }), _jsx("span", { "data-edge": "semantic", children: t('overview.edgeSemantic') }), _jsx("span", { "data-edge": "causal", children: t('overview.edgeCausal') }), _jsx("span", { "data-edge": "entity", children: t('overview.edgeEntity') })] })] }), _jsx("div", { className: css.graphViewport, children: _jsx(MemoryGraph, { graph: graph, selectedId: selected === null ? undefined : graphNodeKey(selected), onSelect: setSelected }) }), _jsxs("div", { className: css.graphFooter, children: [_jsx("span", { children: t('overview.graphComposition', { spaces: graphSpaces, memories: graphMemories, entities: graphEntities }) }), _jsxs("span", { children: [t('overview.graphCount', { visible: Math.min(graph.nodes.length, 60), total: graph.nodes.length }), " \u00B7 ", t('overview.graphEdges', { count: graph.edges.length })] })] })] }), _jsx("aside", { className: css.graphInspector, "data-empty": selected === null || undefined, children: selected === null ? (_jsxs("div", { className: css.inspectorEmpty, children: [_jsx(MnemonLogo, { className: css.inspectorLogo, title: t('overview.inspector') }), _jsx("h3", { children: t('overview.selectNode') }), _jsx("p", { children: t('overview.selectNodeText') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.inspectorHeading, children: [_jsx("span", { children: t(selectedKind === 'space' ? 'overview.inspectorSpace' : selectedKind === 'entity' ? 'overview.inspectorEntity' : 'overview.inspector') }), _jsx("button", { type: "button", onClick: () => setSelected(null), "aria-label": t('overview.closeInspector'), children: "\u00D7" })] }), _jsx("span", { className: css.categoryChip, children: graphKindLabel(t, selected) }), _jsxs("div", { className: css.inspectorTitleRow, children: [_jsx("h3", { className: css.inspectorTitle, children: selected.content }), selectedKind === 'memory' && selected.content.length > 140 && _jsx("button", { type: "button", className: css.inspectorEye, onClick: () => setPreview(selected), "aria-label": t('overview.previewAria'), title: t('overview.previewAria'), children: _jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [_jsx("path", { d: "M1 8s2.6-4.4 7-4.4S15 8 15 8s-2.6 4.4-7 4.4S1 8 1 8z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("circle", { cx: "8", cy: "8", r: "2.1", fill: "currentColor" })] }) })] }), selectedKind === 'space'
663
+ ? _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('overview.spaceId') }), _jsx("dd", { children: _jsx("code", { children: selected.memoryBodyId ?? selected.id }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('overview.containedMemories') }), _jsx("dd", { children: selected.occurrenceCount ?? 0 })] })] })
664
+ : selectedKind === 'entity'
665
+ ? _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('overview.entityMentions') }), _jsx("dd", { children: selected.occurrenceCount ?? 0 })] }), _jsxs("div", { children: [_jsx("dt", { children: t('term.spaces') }), _jsx("dd", { children: selected.memoryBodyNames?.join(' · ') || '—' })] })] })
666
+ : _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('term.space') }), _jsxs("dd", { children: [selected.memoryBodyName ?? '—', " ", _jsx("code", { children: selected.memoryBodyId ?? '' })] })] }), _jsxs("div", { children: [_jsx("dt", { children: t('overview.memoryId') }), _jsx("dd", { children: _jsx("code", { children: selected.id }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('common.category') }), _jsx("dd", { children: categoryLabel(t, selected.category ?? 'general') })] })] }), _jsxs("div", { className: css.inspectorActions, children: [selectedKind !== 'space' && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => props.onExplore(selected.content), children: t('overview.exploreNode') }), _jsx("button", { type: "button", className: css.secondaryButton, onClick: () => void navigator.clipboard?.writeText(selected.id), children: t('common.copyId') })] })] })) })] })) : !loading && error === null ? (catalogUnavailable
667
+ ? _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.unsyncedTitle'), children: t('overview.unsyncedLong') })
668
+ : catalog?.total === 0
669
+ ? _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.emptyTitle'), children: t('overview.emptyLong') })
670
+ : catalog?.activeCount === 0
671
+ ? _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.noActiveTitle'), children: t('overview.noActiveText') })
672
+ : _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.noContentTitle'), children: t('overview.noContentText') })) : (_jsx("div", { className: css.loadingPanel, children: t('overview.loading') })), preview !== null && _jsx(ContentPreview, { node: preview, kind: graphKindLabel(t, preview), onClose: () => setPreview(null) })] }));
673
+ }
674
+ function ExplorePage(props) {
675
+ const t = useT();
676
+ const [query, setQuery] = useState(props.seed);
677
+ const [mode, setMode] = useState('smart');
678
+ const [category, setCategory] = useState('');
679
+ const [results, setResults] = useState([]);
680
+ const [searchKind, setSearchKind] = useState(null);
681
+ const [agentAnswer, setAgentAnswer] = useState(null);
682
+ const [searched, setSearched] = useState(false);
683
+ const [error, setError] = useState(null);
684
+ const [relatedTo, setRelatedTo] = useState(null);
685
+ const [related, setRelated] = useState([]);
686
+ const [relatedLoading, setRelatedLoading] = useState(false);
687
+ useEffect(() => { if (props.seed !== '')
688
+ setQuery(props.seed); }, [props.seed]);
689
+ const runSearch = async (withAgent) => {
690
+ if (query.trim() === '')
691
+ return;
692
+ setSearchKind(withAgent ? 'agent' : 'direct');
693
+ setSearched(true);
694
+ setError(null);
695
+ setRelatedTo(null);
696
+ setAgentAnswer(null);
697
+ try {
698
+ const request = { query, mode, ...(category === '' ? {} : { category }), limit: props.status?.defaultRecallLimit ?? 10 };
699
+ if (withAgent) {
700
+ const response = await props.client.agentSearch(request);
701
+ setResults(response.results);
702
+ setAgentAnswer({ answer: response.answer, citations: response.citations, runId: response.delegation.runId });
703
+ }
704
+ else {
705
+ setResults((await props.client.search(request)).results);
706
+ }
707
+ }
708
+ catch (reason) {
709
+ setError(message(reason));
710
+ setResults([]);
711
+ setAgentAnswer(null);
712
+ }
713
+ finally {
714
+ setSearchKind(null);
715
+ }
716
+ };
717
+ const search = (event) => { event.preventDefault(); void runSearch(false); };
718
+ const searching = searchKind !== null;
719
+ const showRelated = async (insight) => {
720
+ setRelatedTo(insight);
721
+ setRelated([]);
722
+ setRelatedLoading(true);
723
+ setError(null);
724
+ if (typeof window.requestAnimationFrame === 'function')
725
+ window.requestAnimationFrame(() => document.getElementById('mnemon-related-pane')?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }));
726
+ try {
727
+ setRelated(await props.client.related(insight.id, insight.memoryBodyId));
728
+ }
729
+ catch (reason) {
730
+ setError(message(reason));
731
+ }
732
+ finally {
733
+ setRelatedLoading(false);
734
+ }
735
+ };
736
+ const forget = async (insight) => {
737
+ await props.onForget(insight);
738
+ setResults(items => items.filter(item => insightKey(item) !== insightKey(insight)));
739
+ setRelated(items => items.filter(item => insightKey(item) !== insightKey(insight)));
740
+ if (relatedTo !== null && insightKey(relatedTo) === insightKey(insight))
741
+ setRelatedTo(null);
742
+ };
743
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('search.title'), description: t('search.description'), meta: t('search.maxResults', { count: props.status?.defaultRecallLimit ?? '—' }) }), _jsxs("form", { className: css.searchBar, onSubmit: event => void search(event), children: [_jsxs("div", { className: css.queryField, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { value: query, onChange: event => setQuery(event.target.value), placeholder: t('search.placeholder'), "aria-label": t('search.queryAria') }), _jsx("kbd", { children: "\u21B5" })] }), _jsxs("div", { className: css.searchControls, children: [_jsxs("label", { children: [t('common.category'), _jsxs("select", { value: category, onChange: event => setCategory(event.target.value), "aria-label": t('search.categoryAria'), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] })] }), _jsxs("label", { children: [t('search.strategy'), _jsxs("select", { value: mode, onChange: event => setMode(event.target.value), "aria-label": t('search.modeAria'), children: [_jsx("option", { value: "smart", children: t('search.modeSmart') }), _jsx("option", { value: "keyword", children: t('search.modeKeyword') }), _jsx("option", { value: "basic", children: t('search.modeBasic') })] })] }), _jsxs("div", { className: css.searchActions, children: [_jsx("button", { type: "submit", className: css.secondaryButton, disabled: searching || query.trim() === '', children: searchKind === 'direct' ? t('search.searching') : t('search.action') }), _jsx("button", { type: "button", className: css.primaryButton, disabled: searching || query.trim() === '' || props.status?.lifecycle?.sessionAvailable !== true, onClick: () => void runSearch(true), children: searchKind === 'agent' ? t('search.agentSearching') : t('search.agentAction') })] })] })] }), agentAnswer !== null && _jsxs("section", { className: css.agentAnswer, "aria-label": t('search.agentAnswer'), children: [_jsxs("div", { className: css.agentAnswerHeading, children: [_jsxs("div", { children: [_jsx("span", { children: t('search.agentAnswerHint') }), _jsx("h3", { children: t('search.agentAnswer') })] }), _jsx("code", { children: agentAnswer.runId.slice(0, 8) })] }), _jsx("p", { children: agentAnswer.answer }), agentAnswer.citations.length > 0 && _jsx("div", { className: css.agentCitations, children: agentAnswer.citations.map(citation => _jsx("code", { children: citation }, citation)) })] }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !searched && _jsx(EmptyState, { glyph: "\u2315", title: t('search.startTitle'), children: t('search.startText') }), searched && !searching && results.length === 0 && error === null && _jsx(EmptyState, { glyph: "0", title: t('search.emptyTitle'), children: t('search.emptyText') }), results.length > 0 && (_jsxs("div", { className: relatedTo === null ? css.singleColumn : css.resultLayout, children: [_jsxs("section", { className: css.results, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.results') }) }), _jsx("strong", { children: results.length })] }), results.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight)))] }), relatedTo !== null && _jsxs("aside", { id: "mnemon-related-pane", className: css.relatedPane, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.related') }) }), _jsx("button", { type: "button", onClick: () => setRelatedTo(null), "aria-label": t('search.closeRelated'), children: "\u00D7" })] }), _jsx("p", { className: css.relatedSource, children: relatedTo.content }), relatedLoading && _jsx("div", { className: css.loading, children: t('search.traversing') }), !relatedLoading && related.length === 0 && _jsx("div", { className: css.muted, children: t('search.noRelated') }), related.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight)))] })] }))] }));
744
+ }
745
+ function EntitiesPage(props) {
746
+ const t = useT();
747
+ const [view, setView] = useState({ items: [], insights: [] });
748
+ const [entity, setEntity] = useState('');
749
+ const [loading, setLoading] = useState(true);
750
+ const [error, setError] = useState(null);
751
+ const load = useCallback(async (selected) => {
752
+ setLoading(true);
753
+ setError(null);
754
+ try {
755
+ setView(await props.client.entities(selected, 20));
756
+ }
757
+ catch (reason) {
758
+ setError(message(reason));
759
+ }
760
+ finally {
761
+ setLoading(false);
762
+ }
763
+ }, [props.client]);
764
+ useEffect(() => { void load(); }, [load, props.revision]);
765
+ const submit = (event) => { event.preventDefault(); if (entity.trim() !== '')
766
+ void load(entity); };
767
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('entities.title'), description: t('entities.description'), meta: t('entities.count', { count: view.items.length }) }), _jsxs("div", { className: css.entityLayout, children: [_jsxs("aside", { className: css.entityRail, children: [_jsxs("form", { className: css.entitySearch, onSubmit: submit, children: [_jsx("input", { "aria-label": t('entities.nameAria'), value: entity, onChange: event => setEntity(event.target.value), placeholder: t('entities.placeholder') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading || entity.trim() === '', children: t('entities.action') })] }), _jsxs("div", { className: css.entityHeading, children: [_jsx("span", { children: t('entities.top') }), _jsx("small", { children: t('entities.frequency') })] }), _jsx("div", { className: css.entityList, children: view.items.map(item => _jsxs("button", { type: "button", "aria-pressed": view.selected === item.entity, onClick: () => { setEntity(item.entity); void load(item.entity); }, children: [_jsx("span", { children: item.entity }), _jsx("strong", { children: item.count })] }, item.entity)) }), !loading && view.items.length === 0 && _jsx("p", { className: css.muted, children: t('entities.emptyRail') })] }), _jsxs("section", { className: css.entityResults, children: [error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), loading && _jsx("div", { className: css.loadingPanel, children: t('entities.loading') }), !loading && view.selected === undefined && _jsx(EmptyState, { glyph: "\u25CE", title: t('entities.selectTitle'), children: t('entities.selectText') }), !loading && view.selected !== undefined && _jsxs(_Fragment, { children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: view.selected }) }), _jsx("strong", { children: view.insights.length })] }), view.insights.length === 0 ? _jsx(EmptyState, { glyph: "0", title: t('entities.emptyTitle'), children: t('entities.emptyText') }) : view.insights.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: props.onForget, onRelated: () => props.onExplore(insight.content) }, insightKey(insight)))] })] })] })] }));
768
+ }
769
+ function RuntimePage(props) {
770
+ const t = useT();
771
+ const [snapshot, setSnapshot] = useState(null);
772
+ const [loading, setLoading] = useState(true);
773
+ const [error, setError] = useState(null);
774
+ const [notice, setNotice] = useState(null);
775
+ const [target, setTarget] = useState('memory');
776
+ const [importance, setImportance] = useState('normal');
777
+ const [content, setContent] = useState('');
778
+ const [saving, setSaving] = useState(false);
779
+ const [editing, setEditing] = useState(null);
780
+ const [editContent, setEditContent] = useState('');
781
+ const [editImportance, setEditImportance] = useState('normal');
782
+ const [removing, setRemoving] = useState(null);
783
+ const load = useCallback(async () => {
784
+ setLoading(true);
785
+ setError(null);
786
+ try {
787
+ setSnapshot(await props.client.runtimeMemory());
788
+ }
789
+ catch (reason) {
790
+ setError(message(reason));
791
+ }
792
+ finally {
793
+ setLoading(false);
794
+ }
795
+ }, [props.client]);
796
+ useEffect(() => { void load(); }, [load, props.revision]);
797
+ const entryKey = (entry) => `${entry.target}:${entry.created_at}:${entry.content}`;
798
+ const mutate = async (request) => {
799
+ setNotice(null);
800
+ setError(null);
801
+ const result = await props.client.mutateRuntimeMemory(request);
802
+ setNotice(result.maintenance === undefined
803
+ ? t(`runtime.result.${request.action}`, { target: t(`runtime.target.${request.target}`), count: result.entryCount })
804
+ : result.maintenance.kind === 'local-compaction'
805
+ ? t('runtime.result.localCompaction', { target: t(`runtime.target.${request.target}`), count: result.entryCount })
806
+ : t('runtime.result.maintenance', { target: t(`runtime.target.${request.target}`), count: result.entryCount, spaces: result.maintenance.memoryBodyIds.join(', ') || '—' }));
807
+ await load();
808
+ props.onMutate();
809
+ };
810
+ const add = async (event) => {
811
+ event.preventDefault();
812
+ if (content.trim() === '')
813
+ return;
814
+ setSaving(true);
815
+ try {
816
+ await mutate({ action: 'add', target, content, importance });
817
+ setContent('');
818
+ }
819
+ catch (reason) {
820
+ setError(message(reason));
821
+ }
822
+ finally {
823
+ setSaving(false);
824
+ }
825
+ };
826
+ const beginEdit = (entry) => {
827
+ setEditing(entryKey(entry));
828
+ setEditContent(entry.content);
829
+ setEditImportance(entry.importance);
830
+ setRemoving(null);
831
+ };
832
+ const replace = async (entry) => {
833
+ if (editContent.trim() === '')
834
+ return;
835
+ setSaving(true);
836
+ try {
837
+ await mutate({ action: 'replace', target: entry.target, old_text: entry.content, content: editContent, importance: editImportance });
838
+ setEditing(null);
839
+ }
840
+ catch (reason) {
841
+ setError(message(reason));
842
+ }
843
+ finally {
844
+ setSaving(false);
845
+ }
846
+ };
847
+ const remove = async (entry) => {
848
+ setSaving(true);
849
+ try {
850
+ await mutate({ action: 'remove', target: entry.target, old_text: entry.content });
851
+ setRemoving(null);
852
+ }
853
+ catch (reason) {
854
+ setError(message(reason));
855
+ }
856
+ finally {
857
+ setSaving(false);
858
+ }
859
+ };
860
+ const targetPanel = (value) => {
861
+ const view = snapshot?.targets[value];
862
+ const entries = snapshot?.entries.filter(entry => entry.target === value) ?? [];
863
+ const percentage = view === undefined || view.limit === 0 ? 0 : Math.min(100, Math.round(view.used / view.limit * 100));
864
+ return (_jsxs("section", { className: css.runtimeTarget, "aria-label": t(`runtime.target.${value}`), children: [_jsxs("header", { className: css.runtimeTargetHeader, children: [_jsxs("div", { children: [_jsx("span", { children: value === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("h3", { children: t(`runtime.target.${value}`) })] }), _jsx("strong", { children: view?.entryCount ?? 0 })] }), _jsxs("div", { className: css.capacityLine, children: [_jsx("div", { children: _jsx("i", { style: { width: `${percentage}%` } }) }), _jsx("span", { children: view === undefined ? '—' : `${humanBytes(view.used)} / ${humanBytes(view.limit)}` })] }), _jsx("p", { className: css.runtimeTargetDescription, children: t(`runtime.target.${value}.description`) }), _jsxs("div", { className: css.runtimeEntries, children: [entries.map(entry => {
865
+ const key = entryKey(entry);
866
+ const isEditing = editing === key;
867
+ const isRemoving = removing === key;
868
+ return _jsxs("article", { className: css.runtimeEntry, "data-importance": entry.importance, children: [_jsxs("div", { className: css.runtimeEntryMeta, children: [_jsx("span", { children: t(`runtime.importance.${entry.importance}`) }), _jsx("time", { dateTime: entry.updated_at, children: new Date(entry.updated_at).toLocaleString() })] }), isEditing ? _jsx("textarea", { "aria-label": t('runtime.editContent'), value: editContent, onChange: event => setEditContent(event.target.value), rows: 4 }) : _jsx("p", { children: entry.content }), isEditing && _jsxs("select", { "aria-label": t('runtime.importance'), value: editImportance, onChange: event => setEditImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] }), _jsx("footer", { children: isRemoving ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('runtime.removeConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void remove(entry), children: t('runtime.removeAction') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setRemoving(null), children: t('common.cancel') })] }) : isEditing ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.primaryButton, disabled: saving || editContent.trim() === '', onClick: () => void replace(entry), children: t('runtime.saveEdit') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditing(null), children: t('common.cancel') })] }) : props.writeEnabled ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.ghostButton, onClick: () => beginEdit(entry), children: t('runtime.editAction') }), _jsx("button", { type: "button", className: css.dangerButton, onClick: () => { setRemoving(key); setEditing(null); }, children: t('runtime.removeAction') })] }) : null })] }, key);
869
+ }), !loading && entries.length === 0 && _jsxs("div", { className: css.runtimeEmpty, children: [_jsx("span", { children: "\u25CB" }), _jsx("p", { children: t('runtime.empty') })] })] })] }));
870
+ };
871
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('runtime.title'), description: t('runtime.description'), meta: snapshot === null ? t('common.loading') : t('runtime.total', { count: snapshot.entries.length }), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: t('runtime.refresh') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), props.writeEnabled && _jsxs("form", { className: css.runtimeComposer, onSubmit: event => void add(event), children: [_jsxs("div", { className: css.runtimeComposerHeading, children: [_jsxs("div", { children: [_jsx("h3", { children: t('runtime.addTitle') }), _jsx("p", { children: t('runtime.addDescription') })] }), _jsx("span", { children: t('runtime.hotContext') })] }), _jsx("textarea", { "aria-label": t('runtime.content'), value: content, onChange: event => setContent(event.target.value), rows: 3, placeholder: t('runtime.placeholder') }), _jsxs("div", { className: css.runtimeComposerActions, children: [_jsxs("label", { children: [t('runtime.target'), _jsxs("select", { value: target, onChange: event => setTarget(event.target.value), children: [_jsx("option", { value: "memory", children: t('runtime.target.memory') }), _jsx("option", { value: "user", children: t('runtime.target.user') })] })] }), _jsxs("label", { children: [t('runtime.importance'), _jsxs("select", { value: importance, onChange: event => setImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] })] }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || content.trim() === '', children: saving ? t('runtime.saving') : t('runtime.addAction') })] })] }), !props.writeEnabled && _jsx("div", { className: css.runtimeReadOnly, children: t('runtime.readOnly') }), _jsxs("div", { className: css.runtimeGrid, children: [targetPanel('user'), targetPanel('memory')] }), _jsx("p", { className: css.runtimeFootnote, children: t('runtime.footnote') })] }));
872
+ }
873
+ function RememberPage(props) {
874
+ const t = useT();
875
+ const [content, setContent] = useState(props.seed);
876
+ const [category, setCategory] = useState('general');
877
+ const [importance, setImportance] = useState(3);
878
+ const [tags, setTags] = useState('');
879
+ const [entities, setEntities] = useState('');
880
+ const [memoryBodyId, setMemoryBodyId] = useState('');
881
+ const [supervising, setSupervising] = useState(false);
882
+ const [saving, setSaving] = useState(false);
883
+ const [result, setResult] = useState(null);
884
+ useEffect(() => { if (props.seed !== '')
885
+ setContent(props.seed); }, [props.seed]);
886
+ useEffect(() => {
887
+ if (memoryBodyId === '' && props.memoryBodies.length > 0)
888
+ setMemoryBodyId((props.memoryBodies.find(body => body.active) ?? props.memoryBodies[0]).id);
889
+ }, [memoryBodyId, props.memoryBodies]);
890
+ const supervise = async (event) => {
891
+ event.preventDefault();
892
+ if (content.trim() === '' || props.sessionId === undefined)
893
+ return;
894
+ setSupervising(true);
895
+ setResult(null);
896
+ try {
897
+ const response = await props.client.supervise(content);
898
+ setResult(`${t(response.action === 'skipped' ? 'remember.skipped' : 'remember.completed')}${response.memoryBodyIds.length === 0 ? '' : ` · ${response.memoryBodyIds.join(', ')}`}${response.summary === '' ? '' : ` · ${response.summary}`}`);
899
+ setContent('');
900
+ props.onMutate();
901
+ }
902
+ catch (reason) {
903
+ setResult(t('remember.dispatchFailed', { error: message(reason) }));
904
+ }
905
+ finally {
906
+ setSupervising(false);
907
+ }
908
+ };
909
+ const manualSave = async (event) => {
910
+ event.preventDefault();
911
+ if (content.trim() === '')
912
+ return;
913
+ setSaving(true);
914
+ setResult(null);
915
+ try {
916
+ const response = await props.client.remember({ content, category, importance, tags: tags.split(',').map(value => value.trim()).filter(Boolean), entities: entities.split(',').map(value => value.trim()).filter(Boolean), source: 'user', ...(memoryBodyId === '' ? {} : { memoryBodyId }) });
917
+ const action = typeof response.action === 'string' ? response.action : 'saved';
918
+ const summary = typeof response.summary === 'string' ? response.summary : '';
919
+ setResult(action === 'skipped' ? `${t('remember.skipped')}${summary === '' ? '' : ` · ${summary}`}` : `${t('remember.processed', { action })}${summary === '' ? '' : ` · ${summary}`}`);
920
+ if (action !== 'skipped') {
921
+ setContent('');
922
+ setTags('');
923
+ setEntities('');
924
+ props.onMutate();
925
+ }
926
+ }
927
+ catch (reason) {
928
+ setResult(t('remember.saveFailed', { error: message(reason) }));
929
+ }
930
+ finally {
931
+ setSaving(false);
932
+ }
933
+ };
934
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('remember.title'), description: t('remember.description'), meta: props.writeEnabled ? t('remember.worker') : t('common.readOnly') }), !props.writeEnabled ? _jsx(EmptyState, { glyph: "\u2298", title: t('remember.readOnlyTitle'), children: t('remember.readOnlyText') }) : (_jsxs("div", { className: css.writebackLayout, children: [_jsxs("aside", { className: css.writeGuide, children: [_jsx("h3", { children: t('remember.flowTitle') }), _jsxs("ol", { children: [_jsxs("li", { children: [_jsx("strong", { children: t('remember.routeTitle') }), _jsx("span", { children: t('remember.routeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.dedupeTitle') }), _jsx("span", { children: t('remember.dedupeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.writeTitle') }), _jsx("span", { children: t('remember.writeText') })] })] }), _jsx("p", { children: t('remember.flowText') })] }), _jsxs("section", { className: css.supervisedComposer, children: [_jsxs("form", { className: css.supervisedForm, onSubmit: event => void supervise(event), children: [_jsxs("div", { className: css.supervisedHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('remember.delegateTitle') }) }), _jsx("span", { className: props.sessionId === undefined ? css.sessionMissing : css.sessionReady, children: props.sessionId === undefined ? t('remember.noSession') : t('remember.ready') })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.candidate'), _jsx("textarea", { "aria-label": t('remember.candidateAria'), value: content, onChange: event => setContent(event.target.value), maxLength: 8000, rows: 8, placeholder: t('remember.placeholder') })] }), props.sessionId === undefined && _jsx("p", { className: css.sessionHint, children: t('remember.sessionHint') }), _jsxs("div", { className: css.formActions, children: [_jsx("button", { type: "submit", className: css.primaryButton, disabled: supervising || content.trim() === '' || props.sessionId === undefined, children: supervising ? t('remember.processing') : t('remember.action') }), result !== null && _jsx("span", { role: "status", children: result })] })] }), _jsxs("details", { className: css.advancedWrite, children: [_jsxs("summary", { children: [_jsxs("span", { children: [_jsx("strong", { children: t('remember.advanced') }), _jsx("small", { children: t('remember.advancedHint') })] }), _jsx("span", { children: t('remember.expand') })] }), _jsxs("form", { className: css.manualForm, onSubmit: event => void manualSave(event), children: [_jsxs("div", { className: css.formGrid, children: [_jsxs("label", { className: css.fieldWide, children: [t('remember.target'), _jsx("select", { "aria-label": t('remember.target'), value: memoryBodyId, onChange: event => setMemoryBodyId(event.target.value), children: props.memoryBodies.map(body => _jsxs("option", { value: body.id, children: [body.name, " \u00B7 ", body.id, body.active ? ` · ${t('common.active')}` : ''] }, body.id)) })] }), _jsxs("label", { children: [t('common.category'), _jsx("select", { value: category, onChange: event => setCategory(event.target.value), children: CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value)) })] }), _jsxs("label", { children: [t('common.importanceLabel'), _jsx("select", { value: importance, onChange: event => setImportance(Number(event.target.value)), children: [1, 2, 3, 4, 5].map(value => _jsxs("option", { value: value, children: [value, " / 5"] }, value)) })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.entities'), _jsx("input", { value: entities, onChange: event => setEntities(event.target.value), placeholder: "SQLite, DSH" })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.tags'), _jsx("input", { value: tags, onChange: event => setTags(event.target.value), placeholder: "architecture, local-first" })] })] }), _jsxs("div", { className: css.manualActions, children: [_jsx("p", { children: t('remember.advancedText') }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: saving || content.trim() === '' || props.sessionId === undefined || memoryBodyId === '', children: saving ? t('remember.saving') : t('remember.advancedAction') })] })] })] })] })] }))] }));
935
+ }
936
+ function ListPage(props) {
937
+ const PAGE_SIZE = 48;
938
+ const t = useT();
939
+ const [query, setQuery] = useState('');
940
+ const [category, setCategory] = useState('');
941
+ const [view, setView] = useState(null);
942
+ const [loading, setLoading] = useState(true);
943
+ const [error, setError] = useState(null);
944
+ const [visibleLimit, setVisibleLimit] = useState(PAGE_SIZE);
945
+ const load = useCallback(async () => {
946
+ setLoading(true);
947
+ setError(null);
948
+ try {
949
+ setView(await props.client.list({ ...(query.trim() === '' ? {} : { query }), ...(category === '' ? {} : { category }), limit: 1000 }));
950
+ }
951
+ catch (reason) {
952
+ setError(message(reason));
953
+ }
954
+ finally {
955
+ setLoading(false);
956
+ }
957
+ }, [category, props.client, query]);
958
+ useEffect(() => { void load(); }, [props.revision]);
959
+ const submit = (event) => { event.preventDefault(); setVisibleLimit(PAGE_SIZE); void load(); };
960
+ const forget = async (insight) => { await props.onForget(insight); setView(current => current === null ? current : { ...current, total: Math.max(0, current.total - 1), items: current.items.filter(item => insightKey(item) !== insightKey(insight)) }); };
961
+ const visibleItems = view?.items.slice(0, visibleLimit) ?? [];
962
+ const remaining = Math.max(0, (view?.items.length ?? 0) - visibleItems.length);
963
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('content.title'), description: t('content.description'), meta: t('content.count', { count: view?.total ?? '—' }) }), _jsxs("form", { className: css.listToolbar, onSubmit: submit, children: [_jsx("input", { "aria-label": t('content.filterAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('content.filterPlaceholder') }), _jsxs("select", { "aria-label": t('content.categoryAria'), value: category, onChange: event => setCategory(event.target.value), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading, children: loading ? t('common.loading') : t('content.apply') })] }), _jsx("div", { className: css.listNotice, children: t('content.notice') }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !loading && view?.items.length === 0 && _jsx(EmptyState, { glyph: "\u2261", title: t('content.emptyTitle'), children: t('content.emptyText') }), _jsx("div", { className: css.memoryList, children: visibleItems.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onClone: props.onClone, onRelated: () => props.onExplore(insight.content) }, insightKey(insight))) }), view !== null && view.items.length > 0 && _jsxs("div", { className: css.listProgress, children: [_jsx("span", { children: t('content.showing', { visible: visibleItems.length, total: view.items.length }) }), remaining > 0 && _jsx("button", { type: "button", className: css.secondaryButton, onClick: () => setVisibleLimit(value => value + PAGE_SIZE), children: t('content.showMore', { count: Math.min(PAGE_SIZE, remaining) }) })] })] }));
964
+ }
965
+ function DocumentsPage(props) {
966
+ const t = useT();
967
+ const [snapshot, setSnapshot] = useState(null);
968
+ const [items, setItems] = useState([]);
969
+ const [selectedId, setSelectedId] = useState(null);
970
+ const [selected, setSelected] = useState(null);
971
+ const [status, setStatus] = useState('active');
972
+ const [query, setQuery] = useState('');
973
+ const [loading, setLoading] = useState(true);
974
+ const [saving, setSaving] = useState(false);
975
+ const [error, setError] = useState(null);
976
+ const [notice, setNotice] = useState(null);
977
+ const [composing, setComposing] = useState(false);
978
+ const [editing, setEditing] = useState(false);
979
+ const [confirmArchive, setConfirmArchive] = useState(false);
980
+ const [title, setTitle] = useState('');
981
+ const [description, setDescription] = useState('');
982
+ const [content, setContent] = useState('');
983
+ const [sources, setSources] = useState('');
984
+ const display = useCallback(async (nextQuery, nextStatus) => {
985
+ setLoading(true);
986
+ setError(null);
987
+ try {
988
+ const current = await props.client.documents();
989
+ const records = nextQuery.trim() === ''
990
+ ? current.documents
991
+ : (await props.client.searchDocuments(nextQuery, nextStatus === 'archived')).results;
992
+ const filtered = records.filter(record => record.status === nextStatus);
993
+ setSnapshot(current);
994
+ setItems(filtered);
995
+ setSelectedId(previous => previous !== null && filtered.some(record => record.id === previous) ? previous : filtered[0]?.id ?? null);
996
+ }
997
+ catch (reason) {
998
+ setError(message(reason));
999
+ setSnapshot(null);
1000
+ setItems([]);
1001
+ setSelectedId(null);
1002
+ }
1003
+ finally {
1004
+ setLoading(false);
1005
+ }
1006
+ }, [props.client]);
1007
+ useEffect(() => { void display(query, status); }, [display, props.revision, status]);
1008
+ useEffect(() => {
1009
+ setSelected(null);
1010
+ if (selectedId === null)
1011
+ return;
1012
+ let active = true;
1013
+ void props.client.document(selectedId).then(value => { if (active)
1014
+ setSelected(value); }).catch(reason => { if (active)
1015
+ setError(message(reason)); });
1016
+ return () => { active = false; };
1017
+ }, [props.client, selectedId, props.revision]);
1018
+ const resetComposer = () => { setTitle(''); setDescription(''); setContent(''); setSources(''); setComposing(false); };
1019
+ const sourcePaths = (value) => value.split(/\r?\n|,/gu).map(path => path.trim()).filter(Boolean);
1020
+ const create = async (event) => {
1021
+ event.preventDefault();
1022
+ setSaving(true);
1023
+ setError(null);
1024
+ setNotice(null);
1025
+ try {
1026
+ const result = await props.client.mutateDocument({ action: 'create', title, description, content, sourcePaths: sourcePaths(sources) });
1027
+ setNotice(result.maintenance === undefined ? t('documents.created') : t('documents.createdAfterArchive', { count: result.maintenance.archivedDocumentIds.length }));
1028
+ setStatus('active');
1029
+ setQuery('');
1030
+ resetComposer();
1031
+ props.onMutate();
1032
+ await display('', 'active');
1033
+ setSelectedId(result.document.id);
1034
+ }
1035
+ catch (reason) {
1036
+ setError(message(reason));
1037
+ }
1038
+ finally {
1039
+ setSaving(false);
1040
+ }
1041
+ };
1042
+ const beginEdit = () => {
1043
+ if (selected === null)
1044
+ return;
1045
+ setTitle(selected.title);
1046
+ setDescription(selected.description);
1047
+ setContent(selected.content);
1048
+ setSources(selected.sourcePaths.join('\n'));
1049
+ setEditing(true);
1050
+ setComposing(false);
1051
+ setConfirmArchive(false);
1052
+ };
1053
+ const update = async (event) => {
1054
+ event.preventDefault();
1055
+ if (selected === null)
1056
+ return;
1057
+ setSaving(true);
1058
+ setError(null);
1059
+ setNotice(null);
1060
+ try {
1061
+ const result = await props.client.mutateDocument({ action: 'update', id: selected.id, title, description, content, sourcePaths: sourcePaths(sources) });
1062
+ setNotice(result.maintenance === undefined ? t('documents.updated') : t('documents.updatedAfterArchive', { count: result.maintenance.archivedDocumentIds.length }));
1063
+ setEditing(false);
1064
+ props.onMutate();
1065
+ await display(query, status);
1066
+ setSelectedId(result.document.id);
1067
+ }
1068
+ catch (reason) {
1069
+ setError(message(reason));
1070
+ }
1071
+ finally {
1072
+ setSaving(false);
1073
+ }
1074
+ };
1075
+ const archive = async () => {
1076
+ if (selected === null)
1077
+ return;
1078
+ setSaving(true);
1079
+ setError(null);
1080
+ setNotice(null);
1081
+ try {
1082
+ const result = await props.client.archiveDocument(selected.id);
1083
+ setNotice(t('documents.archived', { spaces: result.maintenance?.memoryBodyIds.join(', ') || '—' }));
1084
+ setConfirmArchive(false);
1085
+ setStatus('archived');
1086
+ setQuery('');
1087
+ props.onMutate();
1088
+ await display('', 'archived');
1089
+ setSelectedId(result.document.id);
1090
+ }
1091
+ catch (reason) {
1092
+ setError(message(reason));
1093
+ }
1094
+ finally {
1095
+ setSaving(false);
1096
+ }
1097
+ };
1098
+ const usage = snapshot === null ? 0 : Math.min(100, snapshot.activeBytes / snapshot.limitBytes * 100);
1099
+ const activeCount = snapshot?.activeCount ?? 0;
1100
+ const archivedCount = snapshot?.archivedCount ?? 0;
1101
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('documents.title'), description: t('documents.description'), meta: snapshot === null ? t('common.loading') : t('documents.capacity', { used: humanBytes(snapshot.activeBytes), limit: humanBytes(snapshot.limitBytes) }), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void display(query, status), children: t('documents.refresh') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), _jsxs("section", { className: css.documentSummary, "aria-label": t('documents.summary'), children: [_jsxs("article", { children: [_jsx("span", { children: t('documents.active') }), _jsx("strong", { children: activeCount }), _jsx("small", { children: t('documents.activeHint') })] }), _jsxs("article", { children: [_jsx("span", { children: t('documents.archivedCount') }), _jsx("strong", { children: archivedCount }), _jsx("small", { children: t('documents.archivedHint') })] }), _jsxs("article", { className: css.documentCapacity, children: [_jsx("span", { children: t('documents.activeCapacity') }), _jsx("strong", { children: snapshot === null ? '—' : `${usage.toFixed(1)}%` }), _jsx("div", { children: _jsx("i", { style: { width: `${usage}%` } }) }), _jsx("small", { children: t('documents.capacityHint') })] })] }), _jsxs("section", { className: css.documentToolbar, children: [_jsxs("form", { onSubmit: event => { event.preventDefault(); void display(query, status); }, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { "aria-label": t('documents.searchAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('documents.searchPlaceholder') }), _jsx("button", { type: "submit", className: css.secondaryButton, children: t('documents.search') })] }), _jsxs("div", { role: "group", "aria-label": t('documents.scope'), children: [_jsxs("button", { type: "button", "data-active": status === 'active' || undefined, onClick: () => setStatus('active'), children: [t('documents.active'), " ", _jsx("b", { children: activeCount })] }), _jsxs("button", { type: "button", "data-active": status === 'archived' || undefined, onClick: () => setStatus('archived'), children: [t('documents.archivedCount'), " ", _jsx("b", { children: archivedCount })] })] }), props.writeEnabled && props.sessionId !== undefined && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => { setComposing(value => !value); setEditing(false); }, children: composing ? t('common.cancel') : t('documents.new') })] }), composing && _jsxs("form", { className: css.documentEditor, onSubmit: event => void create(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.newTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("span", { children: t('documents.managedCopy') })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value), placeholder: t('documents.sourcesPlaceholder') })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 10, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, onClick: resetComposer, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || title.trim() === '' || content.trim() === '', children: saving ? t('documents.saving') : t('documents.create') })] })] }), _jsxs("div", { className: css.documentWorkspace, children: [_jsxs("aside", { className: css.documentList, "aria-label": t('documents.list'), children: [_jsxs("header", { children: [_jsx("span", { children: status === 'active' ? t('documents.activeList') : t('documents.archiveList') }), _jsx("code", { children: items.length })] }), items.map(document => _jsxs("button", { type: "button", "data-selected": selectedId === document.id || undefined, onClick: () => { setSelected(null); setSelectedId(document.id); setEditing(false); setConfirmArchive(false); }, children: [_jsxs("div", { children: [_jsx("strong", { children: document.title }), _jsx("time", { dateTime: document.updatedAt, children: new Date(document.updatedAt).toLocaleDateString() })] }), _jsx("p", { children: document.description || document.excerpt || t('documents.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: humanBytes(document.sizeBytes) }), _jsx("code", { children: document.id.slice(0, 8) }), document.healthy === false && _jsx("em", { children: t('documents.missing') })] })] }, document.id)), !loading && items.length === 0 && _jsxs("div", { className: css.documentListEmpty, children: [_jsx("span", { children: "\u25A4" }), _jsx("strong", { children: status === 'active' ? t('documents.emptyActive') : t('documents.emptyArchived') }), _jsx("p", { children: status === 'active' ? t('documents.emptyActiveText') : t('documents.emptyArchivedText') })] }), loading && _jsx("div", { className: css.loading, children: t('common.loading') })] }), _jsx("section", { className: css.documentReader, "aria-label": t('documents.reader'), children: selected === null ? _jsx(EmptyState, { glyph: "\u25A4", title: t('documents.selectTitle'), children: t('documents.selectText') }) : editing ? _jsxs("form", { className: css.documentEditor, onSubmit: event => void update(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.editTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("code", { children: selected.id })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value) })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 18, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditing(false), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving, children: saving ? t('documents.saving') : t('documents.save') })] })] }) : _jsxs("article", { className: css.documentDetail, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", { children: selected.status === 'active' ? t('documents.active') : t('documents.coldArchive') }), _jsx("h3", { children: selected.title }), _jsx("p", { children: selected.description || t('documents.noDescription') })] }), _jsx("div", { children: props.writeEnabled && selected.status === 'active' && _jsx("button", { type: "button", className: css.secondaryButton, onClick: beginEdit, children: t('documents.edit') }) })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: t('documents.path') }), _jsx("dd", { children: _jsx("code", { children: selected.relativePath }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.revision') }), _jsx("dd", { children: selected.revision })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.hash') }), _jsx("dd", { children: _jsx("code", { children: selected.contentHash.slice(0, 16) }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.size') }), _jsx("dd", { children: humanBytes(selected.sizeBytes) })] })] }), selected.sourcePaths.length > 0 && _jsxs("div", { className: css.documentSources, children: [_jsx("span", { children: t('documents.sources') }), selected.sourcePaths.map(path => _jsx("code", { children: path }, path))] }), selected.status === 'archived' && _jsxs("div", { className: css.documentArchiveReceipt, children: [_jsx("strong", { children: t('documents.archiveReceipt') }), _jsx("p", { children: selected.archiveSummary }), _jsx("div", { children: selected.memoryBodyIds.map(id => _jsx("code", { children: id }, id)) })] }), _jsx(DocumentMarkdown, { content: selected.content }), props.writeEnabled && selected.status === 'active' && _jsx("footer", { className: css.documentDanger, children: confirmArchive ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('documents.archiveConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void archive(), children: saving ? t('documents.archiving') : t('documents.archiveNow') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setConfirmArchive(false), children: t('common.cancel') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("strong", { children: t('documents.archiveTitle') }), _jsx("p", { children: t('documents.archiveDescription') })] }), _jsx("button", { type: "button", className: css.dangerButton, onClick: () => setConfirmArchive(true), children: t('documents.archive') })] }) })] }) })] }), _jsx("p", { className: css.runtimeFootnote, children: t('documents.footnote') })] }));
1102
+ }
1103
+ function StatusPage(props) {
1104
+ const t = useT();
1105
+ const status = props.status;
1106
+ const lifecycle = status?.lifecycle;
1107
+ const current = lifecycle?.current;
1108
+ const workers = lifecycle?.subagents;
1109
+ const documents = status?.documents;
1110
+ const reviewActivity = current?.reviewActivity;
1111
+ const reviewThreshold = reviewActivity?.threshold ?? QODERWORK_REVIEW_POLICY.reviewThreshold;
1112
+ const catalogKnown = status?.memoryBodies !== undefined;
1113
+ const memoryBodies = useMemo(() => status?.memoryBodies ?? [], [status]);
1114
+ const activeBodies = memoryBodies.filter(body => body.active).length;
1115
+ const phase = current?.lastPhase === undefined || current.lastPhase === 'idle' ? t('status.phaseIdle') : current.lastPhase === 'supervised' ? t('status.phaseSupervised') : current.lastPhase === 'error' ? t('status.phaseError') : current.lastPhase === 'prime' ? t('status.prime') : current.lastPhase === 'recall' ? t('status.recallWorker') : current.lastPhase === 'review' ? t('status.phaseReview') : t('status.writeWorker');
1116
+ const reviewState = current?.reviewRunning === true
1117
+ ? t('status.reviewRunning')
1118
+ : current?.idleReviewPending === true
1119
+ ? t('status.reviewPending')
1120
+ : reviewActivity?.eligible === true
1121
+ ? t('status.reviewQualified')
1122
+ : t('status.reviewAccumulating');
1123
+ const lastReview = current?.lastReviewAt === undefined ? t('status.noReview') : t('status.reviewAt', { time: new Date(current.lastReviewAt).toLocaleTimeString(), action: current.lastReviewAction ?? '—', score: current.lastReviewScore ?? '—' });
1124
+ const storage = status?.storage;
1125
+ const selectedScopeKind = storage?.activeKind ?? 'global';
1126
+ const selectedScope = storage?.scopes.find(scope => scope.kind === selectedScopeKind);
1127
+ const runtimeArea = selectedScope?.areas.find(area => area.kind === 'runtime');
1128
+ const runtimeUserEntries = runtimeArea === undefined ? 0 : Number(runtimeArea.details.userEntries ?? 0);
1129
+ const runtimeMemoryEntries = runtimeArea === undefined ? 0 : Number(runtimeArea.details.memoryEntries ?? 0);
1130
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('status.title'), description: t('status.description'), meta: status === null && props.loading ? t('common.loading') : status?.healthy === true && lifecycle?.sessionAvailable === true ? t('status.nominal') : t('status.checkRequired'), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: props.loading, onClick: props.onRefresh, children: props.loading ? t('status.rechecking') : t('status.recheck') }) }), _jsxs("section", { className: css.healthStrip, "aria-label": t('status.aria'), children: [_jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${status === null ? css.healthMuted : status.healthy ? css.healthGood : css.healthBad}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.engine') }), _jsx("strong", { children: status === null ? t('status.engineChecking') : status.healthy ? t('status.engineConnected') : t('status.engineUnavailable') }), _jsx("p", { children: status?.version === undefined ? t('status.versionWaiting') : `CLI ${status.version}` })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${runtimeArea === undefined ? css.healthMuted : runtimeArea.status === 'invalid' ? css.healthBad : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.runtime') }), _jsx("strong", { children: runtimeArea === undefined ? t('status.runtimeWaiting') : t('status.runtimeRatio', { user: runtimeUserEntries, memory: runtimeMemoryEntries }) }), _jsx("p", { children: runtimeArea === undefined ? t('status.runtimeWaitingDetail') : t('status.runtimeBytes', { bytes: humanBytes(runtimeArea.bytes) }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${activeBodies > 0 ? css.healthGood : css.healthMuted}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.spaces') }), _jsx("strong", { children: catalogKnown ? t('status.activeRatio', { active: activeBodies, total: memoryBodies.length }) : t('status.directoryUnsynced') }), _jsx("p", { children: t('status.activeMemories', { count: status?.stats?.totalInsights ?? 0 }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${documents === undefined ? css.healthMuted : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.documents') }), _jsx("strong", { children: documents === undefined ? t('status.documentsWaiting') : t('status.documentRatio', { active: documents.activeCount, archived: documents.archivedCount }) }), _jsx("p", { children: documents === undefined ? t('status.documentsSession') : t('status.documentUsage', { used: humanBytes(documents.activeBytes), limit: humanBytes(documents.limitBytes) }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${lifecycle === undefined ? css.healthMuted : lifecycle.sessionAvailable ? css.healthGood : css.healthBad}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.router') }), _jsx("strong", { children: lifecycle === undefined ? t('status.routerChecking') : lifecycle.sessionAvailable ? t('status.routerReady') : t('status.sessionMissing') }), _jsx("p", { children: workers === undefined ? t('status.orchestrationWaiting') : t('status.workerSummary', { recalls: workers.recalls, reviews: workers.reviews ?? 0, writes: workers.writes, failures: workers.failures ?? 0 }) })] })] })] }), _jsx(MemorySystemFlow, { status: status, reviewState: reviewState }), _jsx(StorageDomains, { catalog: storage, selected: selectedScope, selectedKind: selectedScopeKind }), _jsxs("section", { className: css.lifecyclePanel, children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.lifecycle') }), _jsx("p", { children: t('status.lifecycleText') })] }), _jsx("span", { className: css.phaseBadge, children: phase })] }), _jsxs("div", { className: css.lifecycleFlow, children: [_jsxs("article", { children: [_jsx("span", { children: "01" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.prime') }), _jsx("p", { children: t('status.primeText') })] }), _jsx("code", { children: lifecycle?.counters.primes ?? 0 })] }), _jsxs("article", { "data-disabled": lifecycle?.recallMode === 'off' || undefined, children: [_jsx("span", { children: "02" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.recallWorker') }), _jsx("p", { children: lifecycle?.recallMode === 'guided' ? t('status.recallText') : t('status.recallOff') })] }), _jsx("code", { children: workers?.recalls ?? 0 })] }), _jsxs("article", { "data-disabled": lifecycle?.writebackMode === 'off' || undefined, children: [_jsx("span", { children: "03" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.writeWorker') }), _jsx("p", { children: lifecycle?.writebackMode === 'guided' ? t('status.writeText', { threshold: reviewThreshold, seconds: Math.round((lifecycle.idleReviewMs ?? 0) / 1000) }) : t('status.writeOff') })] }), _jsx("code", { children: workers?.reviews ?? 0 })] })] }), _jsxs("div", { className: css.lifecycleFoot, children: [_jsxs("span", { children: [t('status.activitySignals'), " ", _jsx("strong", { children: t('status.activitySignalValues', { chars: reviewActivity?.totalUserTextLength ?? 0, turns: reviewActivity?.turnCount ?? 0, tools: reviewActivity?.toolCallCount ?? 0, unique: reviewActivity?.uniqueToolCount ?? 0 }) })] }), _jsxs("span", { children: [t('status.lastReview'), " ", _jsx("strong", { children: lastReview })] }), _jsxs("span", { children: [t('status.workerFailures'), " ", _jsx("strong", { children: workers?.failures ?? 0 })] })] }), current?.lastError !== undefined && _jsxs("div", { className: css.inlineError, role: "alert", children: ["Lifecycle\uFF1A", current.lastError] })] })] }));
1131
+ }
1132
+ function storageScopeLabel(t, kind) {
1133
+ return t(kind === 'global' ? 'status.storageGlobal' : kind === 'workspace' ? 'status.storageWorkspace' : 'status.storageCustom');
1134
+ }
1135
+ function storageAreaLabel(t, kind) {
1136
+ return t(kind === 'runtime' ? 'status.storageRuntime' : kind === 'memory-bodies' ? 'status.storageBodies' : kind === 'documents' ? 'status.storageDocuments' : 'status.storageState');
1137
+ }
1138
+ function storageAreaDetails(t, area) {
1139
+ if (area.kind === 'runtime')
1140
+ return t('status.storageRuntimeDetail', { user: area.details.userEntries ?? 0, memory: area.details.memoryEntries ?? 0 });
1141
+ if (area.kind === 'memory-bodies')
1142
+ return t('status.storageBodiesDetail', { active: area.details.activeBodies ?? 0, databases: area.details.databases ?? 0 });
1143
+ if (area.kind === 'documents')
1144
+ return t('status.storageDocumentsDetail', { active: area.details.activeDocuments ?? 0, archived: area.details.archivedDocuments ?? 0 });
1145
+ return area.details.reviewLedger === true ? t('status.storageStateReady') : t('status.storageStateVolatile');
1146
+ }
1147
+ function StorageDomains(props) {
1148
+ const t = useT();
1149
+ const areaStatus = (status) => t(status === 'ready' ? 'status.storageReady' : status === 'empty' ? 'status.storageEmpty' : status === 'missing' ? 'status.storageMissing' : 'status.storageInvalid');
1150
+ return (_jsxs("section", { className: css.storageDomains, "aria-label": t('status.storageDomains'), children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.storageDomains') }), _jsx("p", { children: t('status.storageDomainsText') })] }), _jsx("span", { className: css.phaseBadge, children: storageScopeLabel(t, props.selectedKind) })] }), props.catalog === undefined ? _jsx("div", { className: css.storageUnavailable, children: t('status.storageWaiting') }) : props.selected?.root === undefined ? _jsxs("div", { className: css.storageUnavailable, children: [_jsx("strong", { children: storageScopeLabel(t, props.selectedKind) }), _jsx("p", { children: props.selectedKind === 'custom' ? t('status.storageCustomUnset') : t('status.storageWorkspaceUnavailable') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.storageRoot, children: [_jsxs("div", { children: [_jsxs("span", { children: [storageScopeLabel(t, props.selectedKind), " \u00B7 ", t('status.storageActiveRoot')] }), _jsx("code", { children: props.selected.root })] }), _jsxs("div", { children: [_jsx("strong", { children: humanBytes(props.selected.totalBytes) }), _jsx("small", { children: props.selected.available ? t('status.storageAvailable') : t('status.storageNotCreated') })] })] }), _jsx("div", { className: css.storageAreaGrid, children: props.selected.areas.map(area => _jsxs("article", { "data-status": area.status, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", {}), " ", _jsx("strong", { children: storageAreaLabel(t, area.kind) })] }), _jsx("em", { children: areaStatus(area.status) })] }), _jsxs("div", { className: css.storageAreaMetric, children: [_jsx("strong", { children: area.itemCount }), _jsx("span", { children: t('status.storageItems') }), _jsx("code", { children: humanBytes(area.bytes) })] }), _jsx("p", { children: storageAreaDetails(t, area) }), _jsx("code", { className: css.storagePath, children: area.path }), area.issue !== undefined && _jsx("small", { children: area.issue })] }, area.kind)) })] }), props.catalog !== undefined && _jsx("p", { className: css.storageFootnote, children: t('status.storageFootnote', { root: props.catalog.activeRoot }) })] }));
1151
+ }
1152
+ function MemorySystemFlow(props) {
1153
+ const t = useT();
1154
+ const status = props.status;
1155
+ const activeScope = status?.storage?.scopes.find(scope => scope.active);
1156
+ const runtime = activeScope?.areas.find(area => area.kind === 'runtime');
1157
+ const documentArea = activeScope?.areas.find(area => area.kind === 'documents');
1158
+ const documents = status?.documents;
1159
+ const bodies = status?.memoryBodies ?? [];
1160
+ const activeBodies = bodies.filter(body => body.active).length;
1161
+ const score = status?.lifecycle?.current?.reviewActivity?.score ?? 0;
1162
+ const threshold = status?.lifecycle?.current?.reviewActivity?.threshold ?? QODERWORK_REVIEW_POLICY.reviewThreshold;
1163
+ const idleSeconds = Math.round((status?.lifecycle?.idleReviewMs ?? 30_000) / 1000);
1164
+ const hostReady = status?.lifecycle?.sessionAvailable === true;
1165
+ const flowActive = status?.healthy === true && hostReady;
1166
+ const areaState = (area) => area?.status === 'invalid' ? 'error' : area?.status === 'ready' ? 'ready' : 'idle';
1167
+ const current = status?.lifecycle?.current;
1168
+ const workers = status?.lifecycle?.subagents;
1169
+ const lastWorkerOperation = workers?.lastOperation;
1170
+ const spawnRecent = lastWorkerOperation !== undefined && lastWorkerOperation !== 'review';
1171
+ const archiveRecent = lastWorkerOperation === 'document-archive' || lastWorkerOperation === 'migration';
1172
+ const reviewState = current?.reviewRunning === true ? 'running' : current?.idleReviewPending === true ? 'pending' : 'idle';
1173
+ const rootRunning = current?.status === 'running';
1174
+ const semanticActive = rootRunning && (current?.lastPhase === 'recall' || current?.lastPhase === 'supervised');
1175
+ const runtimeCount = runtime?.itemCount ?? 0;
1176
+ const insights = status?.stats?.totalInsights ?? 0;
1177
+ const flowNode = (title, meta, state = 'idle', kind = 'host', emphasis) => (_jsxs("article", { className: css.flowArchitectureNode, "data-state": state, "data-kind": kind, "data-emphasis": emphasis, children: [_jsx("span", { className: css.flowArchitectureSignal, "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("strong", { children: title }), _jsx("small", { children: meta })] })] }));
1178
+ const connector = (label, kind = 'host', active = false) => _jsxs("div", { className: css.flowConnector, "data-kind": kind, "data-active": active || undefined, children: [_jsx("span", { children: label }), _jsx("i", { "aria-hidden": "true" })] });
1179
+ return (_jsxs("section", { className: css.memoryFlow, "aria-label": t('status.flowTitle'), children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.flowTitle') }), _jsx("p", { children: t('status.flowDescription') })] }), _jsx("span", { className: `${css.runtimeBadge} ${status === null ? css.runtimePending : flowActive ? css.runtimeOnline : css.runtimeOffline}`, children: status === null ? t('common.loading') : flowActive ? t('status.flowLive') : t('status.flowDegraded') })] }), _jsx("div", { className: css.memoryFlowCanvas, children: _jsxs("div", { className: css.flowArchitecture, role: "img", "aria-label": `${t('status.flowTitle')}:${t('status.flowDescription')}`, children: [_jsxs("section", { className: css.flowLane, "data-lane": "context", children: [_jsxs("header", { children: [_jsx("span", { children: "01" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.flowLaneContext') }), _jsx("small", { children: t('status.flowLaneContextDetail') })] })] }), _jsxs("div", { className: css.flowTrack, children: [flowNode(t('status.storageRuntime'), t('status.flowRuntimeMeta', { count: runtimeCount }), areaState(runtime), 'store'), connector(t('status.flowPromptAssembly'), 'host'), flowNode(t('status.flowSupervisor'), t('status.flowRootMeta'), hostReady ? 'ready' : 'idle', 'host', rootRunning ? 'active' : undefined), connector('↔', 'host', rootRunning), flowNode(t('status.flowConversation'), t('status.flowTurns'), hostReady ? 'ready' : 'idle', 'task')] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowSecondaryTrack}`, children: [flowNode(t('status.flowSupervisor'), t('status.flowRootMeta'), hostReady ? 'ready' : 'idle', 'host'), connector(t('status.flowOnDemand'), 'host'), flowNode(t('status.flowDocumentSearch'), t('status.flowOnDemand'), areaState(documentArea), 'host'), connector('→', 'host'), flowNode(t('status.storageDocuments'), t('status.flowDocuments', { active: documents?.activeCount ?? 0, archived: documents?.archivedCount ?? 0 }), areaState(documentArea), 'store'), connector('↩', 'host'), flowNode(t('status.flowContext'), t('status.flowEvidenceMeta'), hostReady ? 'ready' : 'idle', 'task')] })] }), _jsxs("section", { className: css.flowLane, "data-lane": "semantic", children: [_jsxs("header", { children: [_jsx("span", { children: "02" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.flowLaneSemantic') }), _jsx("small", { children: t('status.flowLaneSemanticDetail') })] })] }), _jsxs("div", { className: css.flowTrack, children: [flowNode(t('status.flowSupervisor'), t('status.flowSemanticRequest'), hostReady ? 'ready' : 'idle', 'task'), connector('→', 'worker', semanticActive), flowNode(t('status.flowSpawnWorker'), `${t('status.flowSpawnMeta')}${spawnRecent ? ` · ${t('status.flowRecent')}` : ''}`, hostReady ? 'ready' : 'idle', 'worker', semanticActive ? 'active' : spawnRecent ? 'recent' : undefined), connector('→', 'worker', semanticActive), flowNode(t('status.flowHostBridge'), t('status.flowHostBridgeMeta'), status?.healthy === false ? 'error' : status?.healthy === true ? 'ready' : 'idle', 'host'), connector('↔', 'host', semanticActive), flowNode(t('term.spaces'), t('status.flowSpacesMeta', { active: activeBodies, total: bodies.length, insights }), status?.healthy === false ? 'error' : activeBodies > 0 ? 'ready' : 'idle', 'store'), connector('↩', 'host', semanticActive), flowNode(t('status.flowContext'), t('status.flowEvidenceMeta'), hostReady ? 'ready' : 'idle', 'task')] })] }), _jsxs("section", { className: css.flowLane, "data-lane": "maintenance", children: [_jsxs("header", { children: [_jsx("span", { children: "03" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.flowLaneMaintenance') }), _jsx("small", { children: t('status.flowLaneMaintenanceDetail') })] })] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowMaintenanceTrack}`, children: [flowNode(t('status.flowDeterministicWrites'), t('status.flowDeterministicWritesMeta'), hostReady ? 'ready' : 'idle', 'task'), connector('→', 'host'), flowNode(t('status.flowControlPlane'), t('status.flowControlPlaneMeta'), areaState(runtime), 'host'), connector('→', 'host'), flowNode(t('status.storageRuntime'), t('status.storageDocuments'), areaState(documentArea), 'store')] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowReviewTrack}`, children: [flowNode(t('status.flowReviewGate'), t('status.flowReviewGateMeta', { score, threshold, seconds: idleSeconds }), current?.reviewActivity?.eligible === true ? 'ready' : 'idle', 'task', reviewState === 'pending' ? 'pending' : undefined), connector('→', 'worker', reviewState === 'running'), flowNode(t('status.flowForkReview'), t('status.flowForkReviewMeta', { state: props.reviewState }), current?.lastError === undefined ? hostReady ? 'ready' : 'idle' : 'error', 'worker', reviewState === 'running' ? 'active' : reviewState === 'pending' ? 'pending' : undefined), connector('→', 'host', reviewState === 'running'), flowNode(t('status.flowReviewTargets'), t('status.flowReviewTargetsMeta'), areaState(documentArea), 'host'), connector('↩', 'host', reviewState === 'running'), flowNode(t('status.flowContext'), t('status.flowEvidenceMeta'), hostReady ? 'ready' : 'idle', 'task')] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowArchiveTrack}`, children: [flowNode(t('status.storageDocuments'), t('status.flowDocuments', { active: documents?.activeCount ?? 0, archived: documents?.archivedCount ?? 0 }), areaState(documentArea), 'store'), connector('→', 'worker'), flowNode(t('status.flowColdArchive'), `${t('status.flowColdArchiveMeta')}${archiveRecent ? ` · ${t('status.flowRecent')}` : ''}`, hostReady ? 'ready' : 'idle', 'worker', archiveRecent ? 'recent' : undefined), connector('→', 'worker'), flowNode(t('status.flowHostBridge'), t('status.flowHostBridgeMeta'), status?.healthy === false ? 'error' : status?.healthy === true ? 'ready' : 'idle', 'host'), connector('→', 'host'), flowNode(t('status.flowIndexFirst'), t('term.spaces'), status?.healthy === false ? 'error' : activeBodies > 0 ? 'ready' : 'idle', 'store'), connector('→', 'host'), flowNode(t('status.flowRevisionMove'), t('status.flowRevisionMoveMeta', { archived: documents?.archivedCount ?? 0 }), areaState(documentArea), 'store')] })] })] }) }), _jsxs("div", { className: css.flowLegend, children: [_jsxs("span", { children: [_jsx("i", {}), t('status.flowReadWrite')] }), _jsxs("span", { children: [_jsx("i", {}), t('status.flowArchive')] }), _jsxs("span", { children: [_jsx("b", {}), t('status.flowLegendRecent')] }), _jsx("span", { children: t('status.flowCurrentScope', { scope: storageScopeLabel(t, status?.storage?.activeKind ?? 'global') }) })] })] }));
1180
+ }
1181
+ export function MnemonView(props) {
1182
+ return _jsx(I18nContext.Provider, { value: props.t ?? translateZh, children: _jsx(MnemonWorkspace, { ...props }) });
1183
+ }
1184
+ function MnemonWorkspace({ connection, sessionId }) {
1185
+ const t = useT();
1186
+ const client = useMemo(() => new MnemonClient(connection, sessionId), [connection, sessionId]);
1187
+ const [page, setPage] = useState('status');
1188
+ const canvasRef = useRef(null);
1189
+ /** Pages share one scroll container; drop any carried-over offset so each tab opens at its top. */
1190
+ const resetViewportScroll = useCallback(() => {
1191
+ const canvas = canvasRef.current;
1192
+ if (canvas !== null)
1193
+ canvas.scrollTop = 0;
1194
+ let node = canvas?.parentElement ?? null;
1195
+ while (node !== null && node !== document.body && node !== document.documentElement) {
1196
+ if (node.scrollTop > 0)
1197
+ node.scrollTop = 0;
1198
+ node = node.parentElement;
1199
+ }
1200
+ }, []);
1201
+ useEffect(() => { resetViewportScroll(); }, [page, resetViewportScroll]);
1202
+ useEffect(() => {
1203
+ // DSH's resident scrollport may restore a stale offset when this view remounts; clear it after layout.
1204
+ const frame = window.requestAnimationFrame(() => resetViewportScroll());
1205
+ return () => window.cancelAnimationFrame(frame);
1206
+ }, [resetViewportScroll]);
1207
+ const [status, setStatus] = useState(null);
1208
+ const [statusLoading, setStatusLoading] = useState(true);
1209
+ const [statusError, setStatusError] = useState(null);
1210
+ const [revision, setRevision] = useState(0);
1211
+ const [searchSeed, setSearchSeed] = useState('');
1212
+ const [rememberSeed, setRememberSeed] = useState('');
1213
+ const loadStatus = useCallback(async () => {
1214
+ setStatusLoading(true);
1215
+ setStatusError(null);
1216
+ try {
1217
+ setStatus(await client.status());
1218
+ }
1219
+ catch (reason) {
1220
+ setStatusError(message(reason));
1221
+ }
1222
+ finally {
1223
+ setStatusLoading(false);
1224
+ }
1225
+ }, [client]);
1226
+ useEffect(() => { void loadStatus(); }, [loadStatus]);
1227
+ const mutate = useCallback(() => { setRevision(value => value + 1); void loadStatus(); }, [loadStatus]);
1228
+ const forget = useCallback(async (insight) => { await client.forget(insight.id, insight.memoryBodyId); mutate(); }, [client, mutate]);
1229
+ const explore = useCallback((query) => { setSearchSeed(query); setPage('explore'); }, []);
1230
+ const clone = useCallback((insight) => { setRememberSeed(insight.content); setPage('remember'); }, []);
1231
+ const refreshAll = () => { setRevision(value => value + 1); void loadStatus(); };
1232
+ const writeEnabled = status?.writeEnabled === true;
1233
+ const stats = status?.stats;
1234
+ const catalogKnown = status?.memoryBodies !== undefined;
1235
+ const memoryBodies = useMemo(() => status?.memoryBodies ?? [], [status]);
1236
+ const activeBodies = memoryBodies.filter(body => body.active).length;
1237
+ return (_jsxs("main", { className: css.shell, children: [_jsxs("header", { className: css.masthead, children: [_jsxs("div", { className: css.brand, children: [_jsx(MnemonLogo, { className: css.brandLogo }), _jsx("h1", { children: "Mnemon" })] }), _jsxs("section", { className: css.telemetry, "aria-label": t('telemetry.aria'), children: [_jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.memories') }), _jsx("strong", { children: stats?.totalInsights ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.graph') }), _jsx("strong", { children: stats?.edgeCount ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.entities') }), _jsx("strong", { children: stats?.topEntities.length ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.spaces') }), _jsx("strong", { children: status === null || !catalogKnown ? '—' : activeBodies })] })] }), _jsxs("div", { className: css.statusCluster, children: [_jsx("span", { className: `${css.statusDot} ${statusLoading && status === null ? css.checking : status?.healthy === true ? css.online : css.offline}` }), _jsx("span", { children: statusLoading ? t('header.checking') : status?.healthy === true ? catalogKnown ? t('header.connected', { count: activeBodies }) : t('header.directoryPending') : t('header.unavailable') }), _jsx("button", { type: "button", className: css.iconButton, disabled: statusLoading, onClick: refreshAll, "aria-label": t('common.refresh'), children: "\u21BB" })] })] }), (statusError !== null || status?.healthy === false) && _jsxs("div", { className: css.alert, role: "alert", children: [_jsx("strong", { children: t('header.notReady') }), _jsx("span", { children: statusError ?? status?.error })] }), _jsxs("div", { className: css.workspace, children: [_jsxs("div", { className: css.topNavigation, children: [_jsx("nav", { className: css.nav, "aria-label": t('nav.aria'), children: PAGE_NAV.map((group, groupIndex) => _jsxs(Fragment, { children: [_jsx("div", { className: css.navGroup, role: "group", "aria-label": t(group.aria), children: group.entries.map(item => _jsxs("button", { type: "button", "aria-current": page === item.id ? 'page' : undefined, onClick: () => setPage(item.id), children: [_jsx("span", { className: css.navGlyph, "aria-hidden": "true", children: item.glyph }), _jsxs("span", { children: [_jsx("strong", { children: t(item.label) }), _jsx("small", { children: t(item.detail) })] })] }, item.id)) }), groupIndex < PAGE_NAV.length - 1 && _jsx("span", { className: css.navGroupDivider, "aria-hidden": "true" })] }, group.aria)) }), _jsxs("div", { className: css.spaceSummary, children: [_jsx("span", { children: t('sidebar.activeSpaces') }), _jsx("code", { children: catalogKnown ? `${activeBodies} / ${memoryBodies.length}` : '— / —' }), _jsx("small", { children: writeEnabled ? t('common.agentSupervised') : t('common.readOnly') })] })] }), _jsxs("section", { className: css.canvas, ref: canvasRef, "data-testid": "mnemon-canvas", children: [page === 'overview' && _jsx(OverviewPage, { client: client, revision: revision, writeEnabled: writeEnabled, fallbackBodies: memoryBodies, fallbackDirectory: status?.memoryBodyDirectory, catalogKnown: catalogKnown, onMutate: mutate, onExplore: explore }), page === 'runtime' && _jsx(RuntimePage, { client: client, revision: revision, writeEnabled: writeEnabled, onMutate: mutate }), page === 'documents' && _jsx(DocumentsPage, { client: client, revision: revision, writeEnabled: writeEnabled, ...(sessionId === undefined ? {} : { sessionId }), onMutate: mutate }), page === 'explore' && _jsx(ExplorePage, { client: client, status: status, seed: searchSeed, writeEnabled: writeEnabled, onForget: forget }), page === 'entities' && _jsx(EntitiesPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onExplore: explore }), page === 'remember' && _jsx(RememberPage, { client: client, sessionId: sessionId, memoryBodies: status?.memoryBodies ?? [], writeEnabled: writeEnabled, seed: rememberSeed, onMutate: mutate }), page === 'list' && _jsx(ListPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onClone: clone, onExplore: explore }), page === 'status' && _jsx(StatusPage, { status: status, loading: statusLoading, onRefresh: () => void loadStatus() })] })] })] }));
1238
+ }
1239
+ //# sourceMappingURL=MnemonView.js.map