dsh-plugin-xujie-memory 0.6.0 → 0.7.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 (61) hide show
  1. package/ACCEPTANCE.md +37 -0
  2. package/BUILD-PROVENANCE.json +53 -0
  3. package/CHANGELOG.md +12 -0
  4. package/README.md +48 -23
  5. package/README_EN.md +32 -9
  6. package/lib/client.js +17219 -0
  7. package/lib/controller.js +307 -0
  8. package/lib/index.js +87 -22
  9. package/lib/typert.host.d.ts +3 -0
  10. package/lib/typert.host.js +15104 -0
  11. package/lib/typert.remote-client.d.ts +40 -0
  12. package/lib/typert.remote-client.d.ts.map +1 -0
  13. package/lib/typert.remote-client.js +15093 -0
  14. package/lib/types.d.ts +13 -0
  15. package/lib/types.js +1 -0
  16. package/package.json +49 -5
  17. package/python/xujie_memory/__init__.py +123 -123
  18. package/python/xujie_memory/engine.py +1333 -1312
  19. package/python/xujie_memory/management.py +203 -0
  20. package/python/xujie_memory/memory.py +27 -21
  21. package/python/xujie_memory/privacy.py +105 -0
  22. package/python/xujie_memory/schema.sql +492 -480
  23. package/python/xujie_memory/server.py +52 -0
  24. package/scripts/build.mjs +19 -0
  25. package/scripts/prepack.mjs +9 -0
  26. package/scripts/test-all.mjs +9 -0
  27. package/scripts/test-node.mjs +10 -0
  28. package/src/client/MemoryPanel.tsx +536 -0
  29. package/src/client/NeuralField.tsx +88 -0
  30. package/src/client/css.d.ts +4 -0
  31. package/src/client/icons.tsx +28 -0
  32. package/src/client/index.tsx +59 -0
  33. package/src/client/memory-adapter.ts +67 -0
  34. package/src/client/model.ts +79 -0
  35. package/src/client/neural/engine.ts +999 -0
  36. package/src/client/neural/morphology.ts +219 -0
  37. package/src/client/panel.css +337 -0
  38. package/src/client/types.ts +6 -0
  39. package/src/controller.ts +88 -0
  40. package/src/generated/typert.host.js +449 -0
  41. package/src/generated/typert.remote-client.js +438 -0
  42. package/src/index.ts +2 -0
  43. package/src/types.ts +13 -0
  44. package/test/cordis-fixture.mjs +5 -0
  45. package/test/management_regression.py +187 -0
  46. package/test/node-regression.entry.mjs +52 -0
  47. package/vendor/DSH-LICENSE.txt +21 -0
  48. package/vendor/THIRD-PARTY.md +6 -0
  49. package/vendor/dsh-protocol/index.ts +484 -0
  50. package/vendor/dsh-protocol/json-value.ts +53 -0
  51. package/vendor/dsh-protocol/owned-value.ts +39 -0
  52. package/vendor/dsh-protocol/principal.ts +17 -0
  53. package/vendor/dsh-protocol/remote-access.ts +38 -0
  54. package/vendor/dsh-protocol/remote-error.ts +49 -0
  55. package/vendor/dsh-protocol/remote-info.ts +161 -0
  56. package/vendor/dsh-protocol/types.ts +939 -0
  57. package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
  58. package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
  59. package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
  60. package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
  61. package/vendor/licenses/zod-MIT.txt +21 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Procedural 3D morphology of one pyramidal neuron spanning three memory strata.
3
+ *
4
+ * The fibres are structure only: they never represent a memory record. Real
5
+ * records are placed later as synaptic boutons on vertices of the stratum that
6
+ * matches their layer, and real relations are drawn as separate connections.
7
+ */
8
+
9
+ export type LayerIndex = 0 | 1 | 2
10
+ /** 0 semantic (apical tuft), 1 episodic (equatorial), 2 working (basal), 3 neutral trunk. */
11
+ export type FibreGroup = LayerIndex | 3
12
+
13
+ export interface Stratum { y: number; radius: number; half: number }
14
+
15
+ /** World-space strata. Screen-up is negative y. */
16
+ export const STRATA: readonly Stratum[] = [
17
+ { y: -262, radius: 340, half: 48 },
18
+ { y: 0, radius: 390, half: 44 },
19
+ { y: 258, radius: 330, half: 48 },
20
+ ]
21
+
22
+ export const SOMA_RADIUS = 40
23
+
24
+ export interface Morphology {
25
+ count: number
26
+ x: Float32Array
27
+ y: Float32Array
28
+ z: Float32Array
29
+ /** Fibre radius at the vertex in world units. */
30
+ r: Float32Array
31
+ parent: Int32Array
32
+ group: Uint8Array
33
+ /** 0 dendrite, 1 spine, 2 axon. */
34
+ kind: Uint8Array
35
+ segA: Int32Array
36
+ segB: Int32Array
37
+ segCount: number
38
+ /** Vertex indices suitable for synaptic boutons, per stratum. */
39
+ sites: [Int32Array, Int32Array, Int32Array]
40
+ /** Diffuse extracellular particles per stratum: xyz + phase. */
41
+ dust: [Float32Array, Float32Array, Float32Array]
42
+ }
43
+
44
+ function mulberry32(seed: number): () => number {
45
+ let a = seed >>> 0
46
+ return () => {
47
+ a = (a + 0x6d2b79f5) >>> 0
48
+ let t = a
49
+ t = Math.imul(t ^ (t >>> 15), t | 1)
50
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
51
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296
52
+ }
53
+ }
54
+
55
+ interface V { x: number; y: number; z: number }
56
+ const norm = (v: V): V => { const l = Math.hypot(v.x, v.y, v.z) || 1; return { x: v.x / l, y: v.y / l, z: v.z / l } }
57
+ const cross = (a: V, b: V): V => ({ x: a.y * b.z - a.z * b.y, y: a.z * b.x - a.x * b.z, z: a.x * b.y - a.y * b.x })
58
+
59
+ export function buildMorphology(seed = 0x7a11e): Morphology {
60
+ const rnd = mulberry32(seed)
61
+ const gauss = () => (rnd() + rnd() + rnd() - 1.5) / 1.5
62
+ const cap = 40000
63
+ const x = new Float32Array(cap), y = new Float32Array(cap), z = new Float32Array(cap)
64
+ const r = new Float32Array(cap), parent = new Int32Array(cap)
65
+ const group = new Uint8Array(cap), kind = new Uint8Array(cap)
66
+ const segA = new Int32Array(cap), segB = new Int32Array(cap)
67
+ const siteLists: number[][] = [[], [], []]
68
+ const branchDepth = new Uint8Array(cap)
69
+ let n = 0, s = 0
70
+
71
+ const add = (p: V, rad: number, par: number, g: FibreGroup, k: number, depth: number): number => {
72
+ if (n >= cap) return -1
73
+ x[n] = p.x; y[n] = p.y; z[n] = p.z; r[n] = rad; parent[n] = par; kind[n] = k; branchDepth[n] = depth
74
+ // Fibres close to the soma read as one neutral trunk system.
75
+ group[n] = Math.hypot(p.x, p.y, p.z) < SOMA_RADIUS * 2.1 && k !== 1 ? 3 : g
76
+ if (par >= 0 && s < cap) { segA[s] = par; segB[s] = n; s++ }
77
+ return n++
78
+ }
79
+
80
+ const perpendicular = (d: V): V => {
81
+ const helper = Math.abs(d.y) < 0.9 ? { x: 0, y: 1, z: 0 } : { x: 1, y: 0, z: 0 }
82
+ const a = norm(cross(d, helper)), b = cross(d, a)
83
+ const t = rnd() * Math.PI * 2
84
+ return norm({ x: a.x * Math.cos(t) + b.x * Math.sin(t), y: a.y * Math.cos(t) + b.y * Math.sin(t), z: a.z * Math.cos(t) + b.z * Math.sin(t) })
85
+ }
86
+
87
+ interface Branch { from: number; pos: V; dir: V; len: number; rad: number; depth: number; g: FibreGroup; target: Stratum | null; maxDepth: number; jitter: number; k: number }
88
+ const stack: Branch[] = []
89
+
90
+ const grow = (b: Branch) => {
91
+ let pos = b.pos, dir = b.dir, rad = b.rad, prev = b.from, travelled = 0
92
+ let sinceBranch = 0
93
+ // Branches born inside their stratum stay confined to it.
94
+ const confined = b.target !== null && Math.abs(b.pos.y - b.target.y) < b.target.half * 1.5
95
+ while (travelled < b.len && rad > 0.16) {
96
+ const step = (b.depth === 0 ? 10 : 8) + rnd() * 4
97
+ dir = norm({ x: dir.x + gauss() * b.jitter, y: dir.y + gauss() * b.jitter, z: dir.z + gauss() * b.jitter })
98
+ if (b.target) {
99
+ const t = b.target
100
+ const dy = (t.y - pos.y) / (t.half * (confined ? 3 : 4.5))
101
+ dir = norm({ x: dir.x, y: dir.y + Math.max(-0.5, Math.min(0.6, dy)) * (confined ? 1.5 : 0.32), z: dir.z })
102
+ const rr = Math.hypot(pos.x, pos.z) || 1
103
+ const inside = Math.abs(pos.y - t.y) < t.half
104
+ if (inside) {
105
+ // Inside the stratum fibres flatten out and spread to fill its disc.
106
+ dir = norm({ x: dir.x + (rr < t.radius * 0.72 ? (pos.x / rr) * 0.2 : 0), y: dir.y * 0.55, z: dir.z + (rr < t.radius * 0.72 ? (pos.z / rr) * 0.2 : 0) })
107
+ }
108
+ if (rr > t.radius * 0.9) {
109
+ const pull = 0.5 * (rr / t.radius - 0.9) * 4
110
+ dir = norm({ x: dir.x - (pos.x / rr) * pull, y: dir.y, z: dir.z - (pos.z / rr) * pull })
111
+ }
112
+ }
113
+ pos = { x: pos.x + dir.x * step, y: pos.y + dir.y * step, z: pos.z + dir.z * step }
114
+ travelled += step; sinceBranch += step
115
+ rad *= b.depth === 0 ? 0.984 : 0.975
116
+ const idx = add(pos, rad, prev, b.g, b.k, b.depth)
117
+ if (idx < 0) return
118
+ prev = idx
119
+ const inStratum = b.target && Math.abs(pos.y - b.target.y) < b.target.half * 1.7
120
+ if (inStratum && b.k === 0 && b.depth >= 1 && rnd() < 0.6) siteLists[b.g as number]?.push(idx)
121
+ // Dendritic spines: short, very fine side processes that give the field its texture.
122
+ if (b.k !== 2 && b.depth >= 1 && rnd() < 0.5) {
123
+ const sp = perpendicular(dir), len = 3 + rnd() * 6
124
+ add({ x: pos.x + sp.x * len, y: pos.y + sp.y * len, z: pos.z + sp.z * len }, 0.22, idx, b.g, 1, b.depth + 1)
125
+ }
126
+ const remaining = b.len - travelled
127
+ if (b.depth < b.maxDepth && sinceBranch > 26 && remaining > 40 && rnd() < 0.17 + b.depth * 0.015) {
128
+ sinceBranch = 0
129
+ const p = perpendicular(dir), ang = 0.45 + rnd() * 0.55
130
+ const cd = norm({ x: dir.x * Math.cos(ang) + p.x * Math.sin(ang), y: dir.y * Math.cos(ang) + p.y * Math.sin(ang), z: dir.z * Math.cos(ang) + p.z * Math.sin(ang) })
131
+ // Bifurcation also deflects the parent slightly away.
132
+ dir = norm({ x: dir.x - p.x * 0.25, y: dir.y - p.y * 0.25, z: dir.z - p.z * 0.25 })
133
+ stack.push({ ...b, from: idx, pos, dir: cd, len: remaining * (0.62 + rnd() * 0.3), rad: Math.max(0.3, rad * 0.74), depth: b.depth + 1, jitter: Math.min(0.42, b.jitter + 0.04) })
134
+ }
135
+ }
136
+ if (prev >= 0 && b.k === 0 && b.target) siteLists[b.g as number]?.push(prev)
137
+ }
138
+
139
+ const drain = () => { while (stack.length) grow(stack.pop()!) }
140
+ const somaAt = (d: V): V => ({ x: d.x * SOMA_RADIUS * 0.92, y: d.y * SOMA_RADIUS * 0.92, z: d.z * SOMA_RADIUS * 0.92 })
141
+
142
+ // Apical trunk: a thick, slightly wandering shaft to the semantic tuft.
143
+ {
144
+ let pos: V = somaAt({ x: 0, y: -1, z: 0 })
145
+ let prev = add(pos, 6.2, -1, 3, 0, 0)
146
+ let rad = 6.2
147
+ const top = STRATA[0]!.y + 18
148
+ let dir: V = { x: 0.04, y: -1, z: 0.02 }
149
+ const obliqueAt = new Set([5, 9, 13, 16])
150
+ for (let i = 0; pos.y > top; i++) {
151
+ dir = norm({ x: dir.x + gauss() * 0.05 - pos.x * 0.002, y: -1, z: dir.z + gauss() * 0.05 - pos.z * 0.002 })
152
+ pos = { x: pos.x + dir.x * 11, y: pos.y + dir.y * 11, z: pos.z + dir.z * 11 }
153
+ rad = Math.max(3.2, rad * 0.975)
154
+ prev = add(pos, rad, prev, 0, 0, 0)
155
+ if (obliqueAt.has(i)) {
156
+ const a = rnd() * Math.PI * 2
157
+ stack.push({ from: prev, pos, dir: norm({ x: Math.cos(a), y: -0.55, z: Math.sin(a) }), len: 240 + rnd() * 80, rad: 1.7, depth: 1, g: 0, target: STRATA[0]!, maxDepth: 4, jitter: 0.2, k: 0 })
158
+ }
159
+ }
160
+ for (let i = 0; i < 8; i++) {
161
+ const a = (i / 8) * Math.PI * 2 + rnd() * 0.5
162
+ stack.push({ from: prev, pos, dir: norm({ x: Math.cos(a), y: -0.18, z: Math.sin(a) }), len: 300 + rnd() * 90, rad: 2.6, depth: 1, g: 0, target: STRATA[0]!, maxDepth: 5, jitter: 0.17, k: 0 })
163
+ }
164
+ drain()
165
+ }
166
+
167
+ // Equatorial dendrites into the episodic stratum.
168
+ for (let i = 0; i < 10; i++) {
169
+ const a = (i / 10) * Math.PI * 2 + rnd() * 0.4
170
+ const d = norm({ x: Math.cos(a), y: gauss() * 0.12, z: Math.sin(a) })
171
+ const start = somaAt(d)
172
+ const root = add(start, 4.2, -1, 3, 0, 0)
173
+ stack.push({ from: root, pos: start, dir: d, len: 400 + rnd() * 70, rad: 3.6, depth: 0, g: 1, target: STRATA[1]!, maxDepth: 5, jitter: 0.15, k: 0 })
174
+ drain()
175
+ }
176
+
177
+ // Basal dendrites descending into the working stratum.
178
+ for (let i = 0; i < 7; i++) {
179
+ const a = (i / 7) * Math.PI * 2 + rnd() * 0.5
180
+ const d = norm({ x: Math.cos(a) * 0.8, y: 0.6, z: Math.sin(a) * 0.8 })
181
+ const start = somaAt(d)
182
+ const root = add(start, 3.8, -1, 3, 0, 0)
183
+ stack.push({ from: root, pos: start, dir: d, len: 440 + rnd() * 70, rad: 3.2, depth: 0, g: 2, target: STRATA[2]!, maxDepth: 5, jitter: 0.15, k: 0 })
184
+ drain()
185
+ }
186
+
187
+ // Axon: a fine straight process with collaterals into the working stratum.
188
+ {
189
+ let pos = somaAt(norm({ x: 0.18, y: 1, z: -0.12 }))
190
+ let prev = add(pos, 1.9, -1, 3, 2, 0)
191
+ let dir = norm({ x: 0.12, y: 1, z: -0.08 })
192
+ for (let i = 0; i < 31; i++) {
193
+ dir = norm({ x: dir.x + gauss() * 0.03, y: 1, z: dir.z + gauss() * 0.03 })
194
+ pos = { x: pos.x + dir.x * 11, y: pos.y + dir.y * 11, z: pos.z + dir.z * 11 }
195
+ prev = add(pos, 1.4, prev, 2, 2, 0)
196
+ if (i === 17 || i === 21 || i === 25 || i === 30) {
197
+ const a = rnd() * Math.PI * 2
198
+ stack.push({ from: prev, pos, dir: norm({ x: Math.cos(a), y: 0.05, z: Math.sin(a) }), len: 200 + rnd() * 80, rad: 1.0, depth: 2, g: 2, target: STRATA[2]!, maxDepth: 4, jitter: 0.2, k: 0 })
199
+ }
200
+ }
201
+ drain()
202
+ }
203
+
204
+ const sites = siteLists.map(list => Int32Array.from(new Set(list))) as [Int32Array, Int32Array, Int32Array]
205
+ const dust = STRATA.map((st, li) => {
206
+ const count = li === 1 ? 520 : 440
207
+ const out = new Float32Array(count * 4)
208
+ for (let i = 0; i < count; i++) {
209
+ const rr = st.radius * Math.sqrt(rnd()) * 1.08, a = rnd() * Math.PI * 2
210
+ out[i * 4] = Math.cos(a) * rr
211
+ out[i * 4 + 1] = st.y + gauss() * st.half * 0.9
212
+ out[i * 4 + 2] = Math.sin(a) * rr
213
+ out[i * 4 + 3] = rnd() * Math.PI * 2
214
+ }
215
+ return out
216
+ }) as [Float32Array, Float32Array, Float32Array]
217
+
218
+ return { count: n, x, y, z, r, parent, group, kind, segA, segB, segCount: s, sites, dust }
219
+ }
@@ -0,0 +1,337 @@
1
+ /* xujie-memory panel. Every selector is prefixed .xjm-*, mounted while the plugin lives. */
2
+ .xjm-root {
3
+ --xjm-fg: var(--dsw-alias-label-primary, #1b2230);
4
+ --xjm-fg2: var(--dsw-alias-label-secondary, #4a5468);
5
+ /* Host tertiary (#81858c) is 3.7:1 on white; blend toward secondary so muted text stays >= 4.5:1 on white and hover fills. */
6
+ --xjm-fg3: color-mix(in srgb, var(--dsw-alias-label-tertiary, #81858c) 25%, var(--dsw-alias-label-secondary, #61666b));
7
+ --xjm-bg: var(--dsw-alias-bg-base, #ffffff);
8
+ --xjm-surface: var(--dsw-alias-bg-layer-1, #ffffff);
9
+ --xjm-line: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
10
+ --xjm-line-soft: var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.05));
11
+ --xjm-hover: var(--dsw-alias-interactive-bg-hover, rgba(38, 49, 72, 0.06));
12
+ --xjm-danger: var(--dsw-alias-state-error-primary, #ec1313);
13
+ --xjm-danger-ink: color-mix(in srgb, var(--xjm-danger) 85%, #000); /* text on light surfaces */
14
+ --xjm-danger-fill: color-mix(in srgb, var(--xjm-danger) 75%, #000); /* filled button behind white text */
15
+ --xjm-warn: var(--dsw-alias-state-warn-primary, #d68a12);
16
+ --xjm-sem: #3a66d6;
17
+ --xjm-epi: #8f6210;
18
+ --xjm-work: #0f7a6a;
19
+ container: xjm / inline-size;
20
+ position: relative;
21
+ width: 100%;
22
+ height: 100%;
23
+ min-width: 0;
24
+ min-height: 0;
25
+ /* The root is its own query container, so a container query cannot switch its overflow.
26
+ It always owns vertical scrolling; on desktop the shell is exactly 100% tall, so nothing scrolls. */
27
+ overflow-x: hidden;
28
+ overflow-y: auto;
29
+ overscroll-behavior: contain;
30
+ -webkit-overflow-scrolling: touch;
31
+ color: var(--xjm-fg);
32
+ background: var(--xjm-bg);
33
+ font-family: var(--dsw-font-family, ui-sans-serif, system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif);
34
+ font-size: 14px;
35
+ line-height: 1.5;
36
+ -webkit-font-smoothing: antialiased;
37
+ }
38
+ body[data-ds-dark-theme] .xjm-root { --xjm-sem: #8aaeff; --xjm-epi: #efc683; --xjm-work: #62dcc8; --xjm-danger-ink: var(--xjm-danger); }
39
+ .xjm-root *, .xjm-root *::before, .xjm-root *::after { box-sizing: border-box; }
40
+ :where(.xjm-root) :where(button, input, select, textarea) { font: inherit; color: inherit; }
41
+ .xjm-root :focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, #4c8dff); outline-offset: 2px; }
42
+ .xjm-root [data-layer="0"] { --xjm-lc: var(--xjm-sem); }
43
+ .xjm-root [data-layer="1"] { --xjm-lc: var(--xjm-epi); }
44
+ .xjm-root [data-layer="2"] { --xjm-lc: var(--xjm-work); }
45
+ .xjm-visually-hidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
46
+
47
+ .xjm-shell { display: flex; width: 100%; height: 100%; min-height: 0; }
48
+
49
+ /* ---------- stage: an always-dark instrument viewport ---------- */
50
+ .xjm-stage {
51
+ position: relative;
52
+ flex: 1 1 auto;
53
+ min-width: 0;
54
+ margin: 10px 0 10px 10px;
55
+ border-radius: 12px;
56
+ overflow: hidden;
57
+ isolation: isolate;
58
+ color-scheme: dark;
59
+ background:
60
+ radial-gradient(70% 60% at 50% 48%, rgba(74, 96, 150, 0.16) 0%, rgba(74, 96, 150, 0) 70%),
61
+ radial-gradient(120% 95% at 50% 45%, #10151f 0%, #0a0d13 55%, #06070a 100%);
62
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
63
+ }
64
+ .xjm-stage::after {
65
+ content: "";
66
+ position: absolute;
67
+ inset: 0;
68
+ pointer-events: none;
69
+ background: radial-gradient(110% 90% at 50% 50%, transparent 62%, rgba(0, 0, 0, 0.38) 100%);
70
+ z-index: 1;
71
+ }
72
+ .xjm-field { position: absolute; inset: 0; }
73
+ .xjm-stage::before {
74
+ content: "";
75
+ position: absolute;
76
+ inset: 0;
77
+ z-index: 1;
78
+ pointer-events: none;
79
+ background:
80
+ linear-gradient(180deg, rgba(7, 9, 13, 0.78) 0, rgba(7, 9, 13, 0.32) 76px, rgba(7, 9, 13, 0) 120px),
81
+ linear-gradient(0deg, rgba(7, 9, 13, 0.8) 0, rgba(7, 9, 13, 0.3) 40px, rgba(7, 9, 13, 0) 76px);
82
+ }
83
+ .xjm-canvas { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; outline: none; }
84
+ .xjm-canvas:active { cursor: grabbing; }
85
+ .xjm-canvas:focus-visible { box-shadow: inset 0 0 0 2px rgba(138, 174, 255, 0.55); }
86
+ .xjm-field-fallback { position: absolute; inset: auto 24px 50% 24px; margin: 0; text-align: center; color: rgba(225, 232, 245, 0.75); font-size: 14px; }
87
+
88
+ .xjm-tip {
89
+ position: absolute; left: 0; top: 0; z-index: 4;
90
+ display: flex; flex-direction: column; gap: 2px;
91
+ max-width: 280px; padding: 8px 10px;
92
+ border-radius: 8px;
93
+ background: rgba(13, 17, 26, 0.9);
94
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 8px 24px rgba(0, 0, 0, 0.35);
95
+ backdrop-filter: blur(8px);
96
+ color: rgba(236, 241, 250, 0.94);
97
+ font-size: 14px; line-height: 1.5;
98
+ pointer-events: none;
99
+ }
100
+ .xjm-tip[hidden] { display: none; }
101
+ .xjm-tip-kind { font-size: 14px; font-weight: 600; letter-spacing: 0.04em; }
102
+ .xjm-tip-kind[data-layer="0"] { color: #a3bdff; }
103
+ .xjm-tip-kind[data-layer="1"] { color: #f1cd92; }
104
+ .xjm-tip-kind[data-layer="2"] { color: #7fe6d4; }
105
+ .xjm-tip-text { word-break: break-word; }
106
+
107
+ .xjm-stage-top {
108
+ position: absolute; top: 14px; left: 22px; right: 14px; z-index: 2;
109
+ display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 8px 16px;
110
+ pointer-events: none;
111
+ }
112
+ .xjm-stage-head { min-width: 0; padding-top: 4px; pointer-events: none; }
113
+ .xjm-eyebrow { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: 0.12em; color: rgba(170, 188, 220, 0.86); }
114
+ .xjm-title { margin: 2px 0 0; font-size: 22px; line-height: 1.3; font-weight: 600; color: #eef2f9; }
115
+ .xjm-sub { margin: 2px 0 0; font-size: 14px; color: rgba(206, 215, 232, 0.8); font-variant-numeric: tabular-nums; }
116
+
117
+ .xjm-controls {
118
+ margin-left: auto; pointer-events: auto;
119
+ display: flex; align-items: center; gap: 2px; padding: 3px;
120
+ border-radius: 10px;
121
+ background: rgba(15, 19, 28, 0.72);
122
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
123
+ backdrop-filter: blur(10px);
124
+ }
125
+ .xjm-tool { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 8px; background: transparent; color: rgba(224, 231, 244, 0.82); cursor: pointer; }
126
+ .xjm-tool:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); color: #fff; }
127
+ .xjm-tool:disabled { opacity: 0.38; cursor: default; }
128
+ .xjm-tool-sep { width: 1px; height: 22px; margin: 0 3px; background: rgba(255, 255, 255, 0.12); }
129
+
130
+ .xjm-legend {
131
+ position: absolute; left: 22px; bottom: 16px; z-index: 2;
132
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px;
133
+ max-width: calc(100% - 44px);
134
+ font-size: 14px; color: rgba(214, 222, 236, 0.86);
135
+ pointer-events: none;
136
+ }
137
+ .xjm-legend span { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
138
+ .xjm-legend .xjm-legend-quiet { color: rgba(190, 202, 224, 0.76); white-space: normal; }
139
+ .xjm-g-dot { width: 7px; height: 7px; border-radius: 50%; background: #e4eaf7; box-shadow: 0 0 6px rgba(200, 215, 255, 0.7); }
140
+ .xjm-g-diamond { width: 7px; height: 7px; transform: rotate(45deg); box-shadow: inset 0 0 0 1.5px #e4eaf7; }
141
+ .xjm-g-link { width: 18px; height: 1.5px; border-radius: 1px; background: linear-gradient(90deg, #efc683, #8aaeff); }
142
+
143
+ .xjm-overlay { position: absolute; inset: 0; z-index: 3; display: grid; place-items: center; padding: 24px; pointer-events: none; }
144
+ .xjm-overlay .xjm-status {
145
+ pointer-events: auto;
146
+ max-width: 360px; padding: 22px 26px;
147
+ border-radius: 12px;
148
+ background: rgba(11, 14, 21, 0.78);
149
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 16px 40px rgba(0, 0, 0, 0.35);
150
+ backdrop-filter: blur(12px);
151
+ color: #e9eef7;
152
+ }
153
+ .xjm-overlay .xjm-status-body { color: rgba(214, 222, 238, 0.72); }
154
+ .xjm-overlay .xjm-status-icon { color: #f0c987; }
155
+
156
+ /* ---------- inspector ---------- */
157
+ .xjm-inspector { position: relative; display: flex; flex-direction: column; flex: 0 0 344px; width: 344px; min-width: 0; min-height: 0; }
158
+ .xjm-insp-head { display: flex; flex-direction: column; gap: 10px; padding: 14px 14px 0; }
159
+ .xjm-scope { display: flex; align-items: center; gap: 10px; min-width: 0; }
160
+ .xjm-scope-label { flex: none; font-size: 14px; color: var(--xjm-fg3); }
161
+ .xjm-select {
162
+ flex: 1 1 auto; min-width: 0; height: 44px; padding: 0 30px 0 12px;
163
+ border: 1px solid var(--xjm-line); border-radius: 8px;
164
+ background-color: var(--xjm-surface);
165
+ background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
166
+ background-position: calc(100% - 15px) 14px, calc(100% - 11px) 14px;
167
+ background-size: 4px 4px; background-repeat: no-repeat;
168
+ appearance: none; -webkit-appearance: none;
169
+ font-size: 14px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; cursor: pointer;
170
+ }
171
+ .xjm-select:disabled { opacity: 0.55; cursor: default; }
172
+ .xjm-search {
173
+ display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 0 0 12px;
174
+ border: 1px solid var(--xjm-line); border-radius: 8px; background: var(--xjm-surface); color: var(--xjm-fg3);
175
+ }
176
+ .xjm-search:focus-within { border-color: var(--xjm-fg3); box-shadow: 0 0 0 2px color-mix(in srgb, var(--xjm-fg3) 28%, transparent); }
177
+ .xjm-search-input:focus-visible { outline: none; }
178
+ .xjm-search-input { flex: 1 1 auto; min-width: 0; height: calc(100% + 2px); margin: -1px 0; padding: 0; border: 0; outline: none; background: transparent; color: var(--xjm-fg); font-size: 14px; }
179
+ .xjm-search-input::placeholder { color: var(--xjm-fg3); opacity: 1; }
180
+ .xjm-search-input::-webkit-search-cancel-button { display: none; }
181
+ .xjm-search-input:disabled { cursor: default; }
182
+ .xjm-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--xjm-line); }
183
+ .xjm-tab { display: inline-flex; align-items: center; gap: 6px; height: 44px; margin-bottom: -1px; padding: 0 10px; border: 0; border-bottom: 2px solid transparent; background: transparent; color: var(--xjm-fg2); font-size: 14px; cursor: pointer; }
184
+ .xjm-tab:hover { color: var(--xjm-fg); }
185
+ .xjm-tab[aria-selected="true"] { color: var(--xjm-fg); font-weight: 600; border-bottom-color: var(--xjm-fg); }
186
+ .xjm-tab-count { min-width: 22px; padding: 0 6px; border-radius: 11px; background: var(--xjm-hover); font-size: 14px; line-height: 22px; text-align: center; font-weight: 500; }
187
+ .xjm-insp-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 14px 72px; scrollbar-gutter: stable; }
188
+
189
+ .xjm-section + .xjm-section { margin-top: 22px; }
190
+ .xjm-h2 { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: var(--xjm-fg3); letter-spacing: 0.02em; }
191
+ .xjm-note + .xjm-section { margin-top: 16px; }
192
+ .xjm-note { margin: 10px 0 0; font-size: 14px; line-height: 1.6; color: var(--xjm-fg3); }
193
+ .xjm-empty-line { margin: 6px 0; font-size: 14px; color: var(--xjm-fg3); }
194
+ .xjm-inline-error { margin: 6px 0; font-size: 14px; color: var(--xjm-danger-ink); }
195
+ .xjm-result-count { margin: 0 0 6px; font-size: 14px; color: var(--xjm-fg3); }
196
+ .xjm-spacer { flex: 1 1 auto; }
197
+
198
+ .xjm-layers { display: flex; flex-direction: column; gap: 6px; }
199
+ .xjm-layer {
200
+ display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 12px;
201
+ border: 1px solid var(--xjm-line); border-radius: 10px; background: var(--xjm-surface);
202
+ text-align: left; cursor: pointer;
203
+ }
204
+ .xjm-layer:hover:not(:disabled) { background: var(--xjm-hover); }
205
+ .xjm-layer:disabled { cursor: default; opacity: 0.6; }
206
+ .xjm-layer[aria-pressed="true"] { border-color: var(--xjm-lc); background: color-mix(in srgb, var(--xjm-lc) 9%, var(--xjm-surface)); }
207
+ .xjm-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--xjm-lc); box-shadow: 0 0 0 3px color-mix(in srgb, var(--xjm-lc) 18%, transparent); }
208
+ .xjm-layer-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
209
+ .xjm-layer-name { font-size: 14px; font-weight: 500; }
210
+ .xjm-layer-hint { font-size: 14px; color: var(--xjm-fg3); }
211
+ .xjm-layer-count { font-size: 15px; font-weight: 600; color: var(--xjm-fg2); font-variant-numeric: tabular-nums; }
212
+
213
+ .xjm-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin: 0; overflow: hidden; border: 1px solid var(--xjm-line); border-radius: 10px; background: var(--xjm-line); }
214
+ .xjm-stats > div { padding: 8px 10px; background: var(--xjm-bg); }
215
+ .xjm-stats dt { font-size: 14px; color: var(--xjm-fg3); }
216
+ .xjm-stats dd { margin: 0; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
217
+
218
+ .xjm-list { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
219
+ .xjm-row { display: flex; flex-direction: column; gap: 3px; width: 100%; min-height: 44px; padding: 10px 10px; border: 0; border-radius: 8px; background: transparent; text-align: left; cursor: pointer; }
220
+ .xjm-row:hover { background: var(--xjm-hover); }
221
+ .xjm-row[aria-current="true"] { background: var(--xjm-hover); box-shadow: inset 2px 0 0 var(--xjm-fg2); }
222
+ .xjm-row-compact { gap: 2px; padding: 8px 10px; }
223
+ .xjm-row-lead { display: flex; align-items: center; gap: 8px; }
224
+ .xjm-row-text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; font-size: 14px; line-height: 1.5; word-break: break-word; }
225
+ .xjm-row-text-1 { -webkit-line-clamp: 1; font-size: 14px; color: var(--xjm-fg2); }
226
+ .xjm-row-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 14px; color: var(--xjm-fg3); font-variant-numeric: tabular-nums; }
227
+ .xjm-row-meta svg { margin-left: auto; }
228
+ .xjm-badge { display: inline-flex; align-items: center; height: 22px; padding: 0 7px; border-radius: 5px; background: color-mix(in srgb, var(--xjm-fg) 7%, transparent); color: var(--xjm-fg2); font-size: 14px; font-weight: 500; white-space: nowrap; }
229
+ .xjm-badge-muted { background: transparent; box-shadow: inset 0 0 0 1px var(--xjm-line); color: var(--xjm-fg3); }
230
+ .xjm-mark { padding: 0 1px; border-radius: 2px; background: color-mix(in srgb, #efc683 45%, transparent); color: inherit; }
231
+
232
+ .xjm-detail-head { display: flex; align-items: center; gap: 8px; margin: -4px 0 12px; min-width: 0; }
233
+ .xjm-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; color: var(--xjm-lc); white-space: nowrap; }
234
+ .xjm-chip .xjm-dot { width: 6px; height: 6px; box-shadow: none; }
235
+ .xjm-kind { font-size: 14px; color: var(--xjm-fg2); white-space: nowrap; }
236
+ .xjm-icon-btn { display: grid; place-items: center; flex: none; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 8px; background: transparent; color: var(--xjm-fg2); cursor: pointer; }
237
+ .xjm-icon-btn:hover { background: var(--xjm-hover); color: var(--xjm-fg); }
238
+ .xjm-icon-btn-sm { width: 44px; height: 44px; }
239
+ .xjm-content {
240
+ margin: 0 0 12px; padding: 12px 14px;
241
+ border: 1px solid var(--xjm-line-soft); border-radius: 10px;
242
+ background: color-mix(in srgb, var(--xjm-fg) 3.5%, transparent);
243
+ font-size: 15px; line-height: 1.65; white-space: pre-wrap; word-break: break-word;
244
+ }
245
+ .xjm-editor { margin: 0 0 16px; }
246
+ .xjm-textarea {
247
+ display: block; width: 100%; min-height: 156px; resize: vertical;
248
+ padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l3, var(--xjm-line)); border-radius: 10px;
249
+ background: var(--xjm-surface); font-size: 15px; line-height: 1.6;
250
+ }
251
+ .xjm-textarea:focus { outline: none; border-color: var(--xjm-fg3); box-shadow: 0 0 0 3px color-mix(in srgb, var(--xjm-fg) 8%, transparent); }
252
+ .xjm-editor-foot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
253
+ .xjm-count { font-size: 14px; color: var(--xjm-fg3); font-variant-numeric: tabular-nums; }
254
+ .xjm-count-bad { color: var(--xjm-danger-ink); }
255
+ .xjm-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
256
+
257
+ .xjm-btn {
258
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px; padding: 0 16px;
259
+ border: 1px solid var(--xjm-line); border-radius: 8px; background: var(--xjm-surface);
260
+ font-size: 14px; font-weight: 500; white-space: nowrap; cursor: pointer;
261
+ }
262
+ .xjm-btn:hover:not(:disabled) { background: var(--xjm-hover); }
263
+ .xjm-btn:disabled { opacity: 0.45; cursor: default; }
264
+ .xjm-btn-primary { border-color: transparent; background: var(--dsw-alias-button-primary-fill, var(--xjm-fg)); color: var(--dsw-alias-label-primary-inverted, #fff); }
265
+ .xjm-btn-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-fill, var(--xjm-fg)); filter: brightness(1.12); }
266
+ .xjm-btn-danger { border-color: transparent; background: var(--xjm-danger-fill); color: #fff; }
267
+ .xjm-btn-danger:hover:not(:disabled) { background: var(--xjm-danger-fill); filter: brightness(1.08); }
268
+ .xjm-btn-danger-quiet { color: var(--xjm-danger-ink); }
269
+ .xjm-btn-danger-quiet:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover-danger, color-mix(in srgb, var(--xjm-danger) 9%, transparent)); }
270
+ .xjm-btn-ghost-dark { margin-top: 6px; border-color: rgba(255, 255, 255, 0.18); background: rgba(255, 255, 255, 0.06); color: #eef2f9; }
271
+ .xjm-btn-ghost-dark:hover:not(:disabled) { background: rgba(255, 255, 255, 0.12); }
272
+
273
+ .xjm-confirm { margin: 0 0 16px; padding: 12px 14px; border-radius: 10px; border: 1px solid color-mix(in srgb, var(--xjm-danger) 35%, transparent); background: color-mix(in srgb, var(--xjm-danger) 6%, transparent); }
274
+ .xjm-confirm-title { margin: 0; font-size: 14px; font-weight: 600; }
275
+ .xjm-confirm-body { margin: 4px 0 12px; font-size: 14px; line-height: 1.6; color: var(--xjm-fg2); }
276
+ .xjm-confirm .xjm-actions { margin: 0; justify-content: flex-end; }
277
+
278
+ .xjm-banner { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0 0 12px; padding: 10px 12px; border-radius: 10px; font-size: 14px; line-height: 1.55; }
279
+ .xjm-banner p { margin: 0; color: var(--xjm-fg2); }
280
+ .xjm-banner b { display: block; color: var(--xjm-fg); font-weight: 600; }
281
+ .xjm-banner-warn { border: 1px solid color-mix(in srgb, var(--xjm-warn) 45%, transparent); background: color-mix(in srgb, var(--xjm-warn) 11%, transparent); }
282
+ .xjm-banner-error { border: 1px solid color-mix(in srgb, var(--xjm-danger) 40%, transparent); background: color-mix(in srgb, var(--xjm-danger) 8%, transparent); }
283
+
284
+ .xjm-meta { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 16px; margin: 0 0 18px; font-size: 14px; }
285
+ .xjm-meta > div { display: contents; }
286
+ .xjm-meta dt { color: var(--xjm-fg3); }
287
+ .xjm-meta dd { margin: 0; color: var(--xjm-fg2); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
288
+ .xjm-mono { font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); font-size: 14px; }
289
+
290
+ .xjm-status { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 28px 12px; text-align: center; }
291
+ .xjm-status-icon { color: var(--xjm-fg3); }
292
+ .xjm-status-title { margin: 0; font-size: 15px; font-weight: 600; }
293
+ .xjm-status-body { margin: 0; font-size: 14px; line-height: 1.6; color: var(--xjm-fg2); }
294
+ .xjm-status .xjm-btn { margin-top: 8px; }
295
+
296
+ .xjm-card { margin-top: 10px; padding: 12px 14px; border: 1px solid var(--xjm-line); border-radius: 10px; background: var(--xjm-surface); }
297
+ .xjm-card-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
298
+ .xjm-avatar { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border-radius: 50%; background: color-mix(in srgb, var(--xjm-sem) 15%, transparent); color: var(--xjm-sem); font-size: 14px; font-weight: 600; }
299
+ .xjm-card-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 600; }
300
+ .xjm-card-status { margin-left: auto; font-size: 14px; color: var(--xjm-fg3); white-space: nowrap; }
301
+ .xjm-tag-group { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 8px; margin-top: 10px; align-items: start; }
302
+ .xjm-tag-kind { padding-top: 2px; font-size: 14px; color: var(--xjm-fg3); }
303
+ .xjm-tags { display: flex; flex-wrap: wrap; gap: 4px; }
304
+ .xjm-tag { padding: 2px 8px; border-radius: 6px; background: color-mix(in srgb, var(--xjm-fg) 6%, transparent); font-size: 14px; line-height: 1.5; word-break: break-word; }
305
+
306
+ .xjm-skeleton { padding: 4px 0; }
307
+ .xjm-skeleton i { display: block; height: 12px; margin: 12px 0; border-radius: 6px; background: var(--dsw-alias-bg-skeleton, color-mix(in srgb, var(--xjm-fg) 8%, transparent)); animation: xjm-pulse 1.6s ease-in-out infinite; }
308
+ .xjm-skeleton i:nth-child(1) { width: 92%; } .xjm-skeleton i:nth-child(2) { width: 76%; } .xjm-skeleton i:nth-child(3) { width: 84%; } .xjm-skeleton i:nth-child(4) { width: 58%; }
309
+ .xjm-spinner { display: inline-block; width: 18px; height: 18px; border-radius: 50%; border: 2px solid color-mix(in srgb, currentColor 22%, transparent); border-top-color: currentColor; animation: xjm-spin 0.9s linear infinite; }
310
+ .xjm-overlay .xjm-spinner { color: #a9c0ff; }
311
+ .xjm-spinner-sm { flex: none; width: 14px; height: 14px; }
312
+ @keyframes xjm-spin { to { transform: rotate(360deg); } }
313
+ @keyframes xjm-pulse { 50% { opacity: 0.45; } }
314
+
315
+ .xjm-toast-slot { position: absolute; left: 14px; right: 14px; bottom: 14px; pointer-events: none; }
316
+ .xjm-toast { padding: 10px 12px; border-radius: 10px; background: var(--xjm-fg); color: var(--xjm-bg); font-size: 14px; line-height: 1.5; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 8px 20px rgba(0, 0, 0, 0.14); }
317
+
318
+ @media (prefers-reduced-motion: reduce) {
319
+ .xjm-spinner, .xjm-skeleton i { animation: none; }
320
+ }
321
+
322
+ @container xjm (max-width: 1040px) {
323
+ .xjm-inspector { flex-basis: 304px; width: 304px; }
324
+ }
325
+ @container xjm (max-width: 760px) {
326
+ .xjm-shell { flex-direction: column; height: auto; min-height: 100%; }
327
+ .xjm-stage { flex: none; height: 580px; margin: 8px 8px 0; }
328
+ /* Narrow/touch: vertical swipes on the neuron scroll the panel; horizontal drags still rotate. Zoom via toolbar. */
329
+ .xjm-canvas { touch-action: pan-y; }
330
+ .xjm-stage-top { top: 10px; left: 16px; right: 10px; }
331
+ .xjm-title { font-size: 20px; }
332
+ .xjm-legend { left: 16px; bottom: 12px; max-width: calc(100% - 32px); gap: 4px 12px; }
333
+ .xjm-inspector { flex: none; width: auto; }
334
+ .xjm-insp-head { padding: 14px 16px 0; }
335
+ .xjm-insp-body { overflow: visible; padding: 14px 16px 88px; }
336
+ .xjm-toast-slot { position: fixed; left: 16px; right: 16px; bottom: 16px; }
337
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Client-side mirror of the Host DTOs (release src/types.ts). Kept structural so the
3
+ * client tree compiles standalone; the parent may replace this import with
4
+ * `../types.ts` when merging into release-0.7.0/src/client.
5
+ */
6
+ export type { Catalog, Snapshot, Trace, SearchHit, EditResult, ForgetResult, PersonCard, PersonTag, Node, Edge, Stats, ScopeRow } from '../types.ts'
@@ -0,0 +1,88 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
3
+ import type { Catalog, Snapshot, Trace, SearchHit, EditResult, ForgetResult, PersonCard } from './types.ts'
4
+
5
+ /** Trusted Node-only bridge; it is never exported to the browser. */
6
+ export interface KernelBridge {
7
+ request(path: string, body: Record<string, string | number>): Promise<any>
8
+ }
9
+
10
+ declare module '@deepseek-ai/dsh-typert-protocol' {
11
+ interface RemoteErrorDetailsMap {
12
+ 'xujie-memory/owner-required': { action: string }
13
+ 'xujie-memory/rejected': { reason: string }
14
+ }
15
+ }
16
+
17
+ /** Memory management is private to the authenticated DSH owner, not shared sessions. */
18
+ export class MemoryController extends TypertRemoteService {
19
+ constructor(ctx: Context, private readonly bridge: KernelBridge) {
20
+ super(ctx, 'xujieMemory')
21
+ }
22
+
23
+ private owner(): void {
24
+ if (this.ctx.invocation?.peer.principal.kind !== 'owner') {
25
+ throw new RemoteError('xujie-memory/owner-required', '只有 DSH 所有者可以管理记忆库', { action: 'memory/manage' })
26
+ }
27
+ }
28
+
29
+ private async call(path: string, body: Record<string, string | number>): Promise<any> {
30
+ this.owner()
31
+ try { return await this.bridge.request(path, body) }
32
+ catch (error) {
33
+ throw new RemoteError('xujie-memory/rejected', '记忆库操作未完成', {
34
+ reason: error instanceof Error ? error.message : 'kernel_unavailable',
35
+ })
36
+ }
37
+ }
38
+
39
+ @Remote
40
+ async catalog(): Promise<Catalog> {
41
+ const r = await this.call('/manage/catalog', {})
42
+ return { scope_mode: r.scope_mode, scopes: r.scopes }
43
+ }
44
+
45
+ @Remote
46
+ async snapshot(scopeId: string): Promise<Snapshot> {
47
+ const r = await this.call('/manage/snapshot', { scope_id: scopeId })
48
+ const cards: PersonCard[] = r.cards.map((c: any) => ({
49
+ account_id: c.profile.account_id, display_name: c.profile.display_name ?? c.profile.account_id,
50
+ status: c.profile.status, tags: c.tags.map((t: any) => ({
51
+ tag_id: t.tag_id, kind: t.kind, value: t.value, source: t.source, confidence: t.confidence,
52
+ })),
53
+ }))
54
+ const { scope_id, scope_mode, nodes, edges, truncated, limit } = r
55
+ // Lifecycle statistics may carry an internal scope key; do not copy it to DTO.
56
+ const { traces, traces_erased, events, states, anchors, clarifications_open, summaries } = r.stats
57
+ return { scope_id, scope_mode, nodes, edges, cards, truncated, limit,
58
+ stats: { traces, traces_erased, events, states, anchors, clarifications_open, summaries } }
59
+ }
60
+
61
+ @Remote
62
+ async detail(scopeId: string, traceId: string): Promise<Trace> {
63
+ return (await this.call('/manage/detail', { scope_id: scopeId, trace_id: traceId })).trace
64
+ }
65
+
66
+ @Remote
67
+ async search(scopeId: string, query: string): Promise<SearchHit[]> {
68
+ const r = await this.call('/manage/search', { scope_id: scopeId, query })
69
+ return r.hits.map((h: any) => ({
70
+ trace_id: h.trace_id, content: h.content, content_hash: h.content_hash,
71
+ score: h.score, priority: Number(h.priority ?? 0), importance: Number(h.importance ?? 1),
72
+ anchor: Boolean(h.anchor), recorded_at: h.recorded_at ?? '', layer: h.layer,
73
+ }))
74
+ }
75
+
76
+ @Remote
77
+ async edit(scopeId: string, traceId: string, content: string, expectedHash: string): Promise<EditResult> {
78
+ const r = await this.call('/manage/edit', { scope_id: scopeId, trace_id: traceId, content, expected_hash: expectedHash })
79
+ return { changed: r.changed, trace: r.trace,
80
+ ...(r.replaced_trace_id ? { replaced_trace_id: r.replaced_trace_id, pending: r.pending } : {}) }
81
+ }
82
+
83
+ @Remote
84
+ async forget(scopeId: string, traceId: string, expectedHash: string): Promise<ForgetResult> {
85
+ const r = await this.call('/manage/forget', { scope_id: scopeId, trace_id: traceId, expected_hash: expectedHash })
86
+ return { trace_id: r.trace_id, erased: r.erased, already_erased: r.already_erased, state_keys: r.state_keys }
87
+ }
88
+ }