dsh-plugin-xujie-memory 0.5.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.
- package/ACCEPTANCE.md +37 -0
- package/BUILD-PROVENANCE.json +53 -0
- package/CHANGELOG.md +40 -1
- package/LICENSE +21 -21
- package/README.md +67 -17
- package/README_EN.md +54 -8
- package/cordis.patch.yml +49 -49
- package/lib/client.js +17219 -0
- package/lib/controller.js +307 -0
- package/lib/index.js +200 -14
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +15104 -0
- package/lib/typert.remote-client.d.ts +40 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +15093 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +1 -0
- package/package.json +49 -5
- package/python/specs/characters/aloof.json +14 -14
- package/python/specs/characters/gentle.json +14 -14
- package/python/specs/characters/tsundere.json +14 -14
- package/python/specs/debt.v1.json +31 -31
- package/python/specs/nickname.v1.json +16 -16
- package/python/specs/preference.v1.json +16 -16
- package/python/xujie_memory/__init__.py +123 -123
- package/python/xujie_memory/admission.py +211 -211
- package/python/xujie_memory/audit.py +377 -377
- package/python/xujie_memory/benchmark.py +281 -281
- package/python/xujie_memory/character.py +199 -199
- package/python/xujie_memory/console.py +41 -41
- package/python/xujie_memory/consolidate.py +356 -356
- package/python/xujie_memory/due.py +121 -121
- package/python/xujie_memory/emotion.py +573 -573
- package/python/xujie_memory/engine.py +1333 -1312
- package/python/xujie_memory/errors.py +98 -98
- package/python/xujie_memory/importance.py +104 -104
- package/python/xujie_memory/judge.py +350 -350
- package/python/xujie_memory/management.py +203 -0
- package/python/xujie_memory/memory.py +71 -10
- package/python/xujie_memory/orchestrator.py +690 -666
- package/python/xujie_memory/phrasing.py +292 -292
- package/python/xujie_memory/privacy.py +105 -0
- package/python/xujie_memory/schema.sql +492 -480
- package/python/xujie_memory/server.py +76 -0
- package/python/xujie_memory/specs.py +170 -170
- package/python/xujie_memory/summary.py +238 -238
- package/scripts/build.mjs +19 -0
- package/scripts/prepack.mjs +9 -0
- package/scripts/test-all.mjs +9 -0
- package/scripts/test-node.mjs +10 -0
- package/src/client/MemoryPanel.tsx +536 -0
- package/src/client/NeuralField.tsx +88 -0
- package/src/client/css.d.ts +4 -0
- package/src/client/icons.tsx +28 -0
- package/src/client/index.tsx +59 -0
- package/src/client/memory-adapter.ts +67 -0
- package/src/client/model.ts +79 -0
- package/src/client/neural/engine.ts +999 -0
- package/src/client/neural/morphology.ts +219 -0
- package/src/client/panel.css +337 -0
- package/src/client/types.ts +6 -0
- package/src/controller.ts +88 -0
- package/src/generated/typert.host.js +449 -0
- package/src/generated/typert.remote-client.js +438 -0
- package/src/index.ts +2 -0
- package/src/types.ts +13 -0
- package/test/cordis-fixture.mjs +5 -0
- package/test/management_regression.py +187 -0
- package/test/node-regression.entry.mjs +52 -0
- package/vendor/DSH-LICENSE.txt +21 -0
- package/vendor/THIRD-PARTY.md +6 -0
- package/vendor/dsh-protocol/index.ts +484 -0
- package/vendor/dsh-protocol/json-value.ts +53 -0
- package/vendor/dsh-protocol/owned-value.ts +39 -0
- package/vendor/dsh-protocol/principal.ts +17 -0
- package/vendor/dsh-protocol/remote-access.ts +38 -0
- package/vendor/dsh-protocol/remote-error.ts +49 -0
- package/vendor/dsh-protocol/remote-info.ts +161 -0
- package/vendor/dsh-protocol/types.ts +939 -0
- package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
- package/vendor/licenses/zod-MIT.txt +21 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
function Svg({ children, size = 16 }: { children: ReactNode; size?: number }) {
|
|
4
|
+
return <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">{children}</svg>
|
|
5
|
+
}
|
|
6
|
+
export const IconSearch = () => <Svg><circle cx="7" cy="7" r="4.5" /><path d="m10.5 10.5 3 3" /></Svg>
|
|
7
|
+
export const IconClose = () => <Svg><path d="m4 4 8 8M12 4l-8 8" /></Svg>
|
|
8
|
+
export const IconBack = () => <Svg><path d="M10 3.5 5.5 8l4.5 4.5" /></Svg>
|
|
9
|
+
export const IconPlus = () => <Svg><path d="M8 3.5v9M3.5 8h9" /></Svg>
|
|
10
|
+
export const IconMinus = () => <Svg><path d="M3.5 8h9" /></Svg>
|
|
11
|
+
export const IconReset = () => <Svg><path d="M3.2 6.2A5 5 0 1 1 3 8.8" /><path d="M3 3v3.4h3.4" /></Svg>
|
|
12
|
+
export const IconPause = () => <Svg><path d="M6 4v8M10 4v8" /></Svg>
|
|
13
|
+
export const IconPlay = () => <Svg><path d="M5.5 3.8v8.4L12 8z" /></Svg>
|
|
14
|
+
export const IconLock = () => <Svg size={20}><rect x="3.5" y="7" width="9" height="6.5" rx="1.5" /><path d="M5.5 7V5.2a2.5 2.5 0 0 1 5 0V7" /></Svg>
|
|
15
|
+
export const IconAlert = () => <Svg size={20}><path d="M8 2.5 14 13H2z" /><path d="M8 6.5v3" /><path d="M8 11.4v.1" /></Svg>
|
|
16
|
+
export const IconEdit = () => <Svg><path d="M10.5 3 13 5.5 6 12.5H3.5V10z" /></Svg>
|
|
17
|
+
export const IconForget = () => <Svg><path d="M3 4.5h10M6.5 4.5V3h3v1.5M4.5 4.5l.6 8.5h5.8l.6-8.5" /></Svg>
|
|
18
|
+
export const IconArrow = () => <Svg><path d="M6 3.5 10.5 8 6 12.5" /></Svg>
|
|
19
|
+
|
|
20
|
+
/** Sidebar glyph: a soma with apical, lateral and basal processes. */
|
|
21
|
+
export function NeuronGlyph({ size }: { size: number }) {
|
|
22
|
+
return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
|
|
23
|
+
<circle cx="12" cy="12.5" r="2.6" />
|
|
24
|
+
<path d="M12 9.9V3.5M12 6.2 8.8 3.8M12 6.2l3.2-2.4" />
|
|
25
|
+
<path d="M9.6 11.6 4 9.6M14.4 11.6l5.6-2" />
|
|
26
|
+
<path d="M10.4 14.6 6.5 20M13.6 14.6l3.9 5.4M12 15.1V21" />
|
|
27
|
+
</svg>
|
|
28
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
2
|
+
import type { MainPanelId } from '@deepseek-ai/dsh-client-ui-layout/client'
|
|
3
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
4
|
+
import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
|
5
|
+
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
|
6
|
+
// Generated Typert contribution (ctx.remote.xujieMemory face + DTO codecs).
|
|
7
|
+
import type {} from '../../lib/typert.remote-client.js'
|
|
8
|
+
import contribution from '../../lib/typert.remote-client.js'
|
|
9
|
+
import { MemoryPanel } from './MemoryPanel.tsx'
|
|
10
|
+
import { NeuronGlyph } from './icons.tsx'
|
|
11
|
+
import PANEL_CSS from './panel.css'
|
|
12
|
+
|
|
13
|
+
/** One id brands the main panel and its sidebar launcher. */
|
|
14
|
+
export const PANEL_ID = 'xujie-memory' as MainPanelId
|
|
15
|
+
|
|
16
|
+
/** Browser services this contribution needs before apply runs. */
|
|
17
|
+
export const inject = ['slots', 'remote']
|
|
18
|
+
|
|
19
|
+
export function apply(ctx: Context): void {
|
|
20
|
+
// 1. Mount the generated Remote contribution first. `remote.xujieMemory` only
|
|
21
|
+
// exists after this resolves, so injecting it before mounting would deadlock.
|
|
22
|
+
// $mount registers its own effect on this ctx; the returned disposer is also
|
|
23
|
+
// tied to this effect so unload unmounts the namespace.
|
|
24
|
+
ctx.effect(() => ctx.remote.$mount(contribution), 'xujie-memory: remote contribution')
|
|
25
|
+
|
|
26
|
+
// 2. Lifecycle-scoped stylesheet: every rule is .xjm-* prefixed and removed on unload.
|
|
27
|
+
ctx.effect(() => {
|
|
28
|
+
const style = document.createElement('style')
|
|
29
|
+
style.dataset.xujieMemory = 'panel'
|
|
30
|
+
style.textContent = PANEL_CSS
|
|
31
|
+
document.head.append(style)
|
|
32
|
+
return () => style.remove()
|
|
33
|
+
}, 'xujie-memory: panel styles')
|
|
34
|
+
|
|
35
|
+
// 3. Only after the namespace is live: register the main panel and its launcher.
|
|
36
|
+
// Registrations live on the injected child context, so they are withdrawn if
|
|
37
|
+
// the Remote namespace disappears (plugin unload or connection reset).
|
|
38
|
+
ctx.inject(['slots', 'remote.xujieMemory'], inner => {
|
|
39
|
+
const memoryRemote = inner.remote.xujieMemory
|
|
40
|
+
inner.slots.inject('main', () => inner.slots.register({
|
|
41
|
+
name: 'main',
|
|
42
|
+
key: PANEL_ID,
|
|
43
|
+
inject: () => ({ remote: memoryRemote }),
|
|
44
|
+
}, MemoryPanel))
|
|
45
|
+
inner.slots.inject('sidebar.panellist', () => inner.slots.register({
|
|
46
|
+
name: 'sidebar.panellist',
|
|
47
|
+
id: PANEL_ID,
|
|
48
|
+
label: '记忆',
|
|
49
|
+
order: 140,
|
|
50
|
+
}, MemoryPanelIcon))
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Sidebar launcher glyph; the sidebar owns the button, label and active state. */
|
|
55
|
+
export function MemoryPanelIcon({ size }: PropsRuntime<'sidebar.panellist'>) {
|
|
56
|
+
return <NeuronGlyph size={size} />
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { MemoryPanel }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Catalog, EditResult, ForgetResult, SearchHit, Snapshot, Trace } from './types.ts'
|
|
2
|
+
|
|
3
|
+
/** Structural RemoteResult: `{ ok: true, value }` or `{ ok: false, error }` (RemoteError instance). */
|
|
4
|
+
export type Result<T> =
|
|
5
|
+
| { readonly ok: true; readonly value: T }
|
|
6
|
+
| { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details?: unknown } }
|
|
7
|
+
|
|
8
|
+
/** The typed Remote face generated from MemoryController (ctx.remote.xujieMemory). */
|
|
9
|
+
export interface XujieMemoryRemote {
|
|
10
|
+
catalog(): Promise<Result<Catalog>>
|
|
11
|
+
snapshot(scopeId: string): Promise<Result<Snapshot>>
|
|
12
|
+
detail(scopeId: string, traceId: string): Promise<Result<Trace>>
|
|
13
|
+
search(scopeId: string, query: string): Promise<Result<SearchHit[]>>
|
|
14
|
+
edit(scopeId: string, traceId: string, content: string, expectedHash: string): Promise<Result<EditResult>>
|
|
15
|
+
forget(scopeId: string, traceId: string, expectedHash: string): Promise<Result<ForgetResult>>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type FailureKind = 'owner' | 'conflict' | 'not_found' | 'scope' | 'invalid' | 'unavailable' | 'unknown'
|
|
19
|
+
|
|
20
|
+
export class MemoryFailure extends Error {
|
|
21
|
+
readonly code: string
|
|
22
|
+
readonly reason: string
|
|
23
|
+
readonly kind: FailureKind
|
|
24
|
+
constructor(code: string, message: string, details?: unknown) {
|
|
25
|
+
super(message)
|
|
26
|
+
this.name = 'MemoryFailure'
|
|
27
|
+
this.code = code
|
|
28
|
+
const reason = typeof details === 'object' && details !== null && typeof (details as { reason?: unknown }).reason === 'string'
|
|
29
|
+
? (details as { reason: string }).reason : ''
|
|
30
|
+
this.reason = reason
|
|
31
|
+
this.kind = code === 'xujie-memory/owner-required' ? 'owner'
|
|
32
|
+
: /version_conflict/.test(reason) ? 'conflict'
|
|
33
|
+
: /trace_not_found/.test(reason) ? 'not_found'
|
|
34
|
+
: /scope_not_found/.test(reason) ? 'scope'
|
|
35
|
+
: /invalid_content_length/.test(reason) ? 'invalid'
|
|
36
|
+
: code === 'xujie-memory/rejected' ? 'unavailable' : 'unknown'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Unwrap one RemoteResult; failures become typed UI errors, never silent empties. */
|
|
41
|
+
export function unwrap<T>(result: Result<T>): T {
|
|
42
|
+
if (result && result.ok === true) return result.value
|
|
43
|
+
const error = (result as { error?: { code?: string; message?: string; details?: unknown } } | undefined)?.error
|
|
44
|
+
throw new MemoryFailure(error?.code ?? 'unknown', error?.message ?? '记忆库操作未完成', error?.details)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function asFailure(error: unknown): MemoryFailure {
|
|
48
|
+
if (error instanceof MemoryFailure) return error
|
|
49
|
+
const e = error as { code?: unknown; message?: unknown; details?: unknown } | undefined
|
|
50
|
+
if (e && typeof e.code === 'string') return new MemoryFailure(e.code, String(e.message ?? ''), e.details)
|
|
51
|
+
return new MemoryFailure('unknown', error instanceof Error ? error.message : '记忆库操作未完成')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** UI adapter: every scope ID comes from catalog; no user ID, kernel token or URL is accepted. */
|
|
55
|
+
export class MemoryAdapter {
|
|
56
|
+
constructor(private readonly remote: XujieMemoryRemote) {}
|
|
57
|
+
async catalog(): Promise<Catalog> { return unwrap(await this.remote.catalog()) }
|
|
58
|
+
async snapshot(scopeId: string): Promise<Snapshot> { return unwrap(await this.remote.snapshot(scopeId)) }
|
|
59
|
+
async detail(scopeId: string, traceId: string): Promise<Trace> { return unwrap(await this.remote.detail(scopeId, traceId)) }
|
|
60
|
+
async search(scopeId: string, query: string): Promise<SearchHit[]> { return unwrap(await this.remote.search(scopeId, query)) }
|
|
61
|
+
async edit(scopeId: string, traceId: string, content: string, expectedHash: string): Promise<EditResult> {
|
|
62
|
+
return unwrap(await this.remote.edit(scopeId, traceId, content, expectedHash))
|
|
63
|
+
}
|
|
64
|
+
async forget(scopeId: string, traceId: string, expectedHash: string): Promise<ForgetResult> {
|
|
65
|
+
return unwrap(await this.remote.forget(scopeId, traceId, expectedHash))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Edge, Node, Snapshot } from './types.ts'
|
|
2
|
+
import type { FieldEdge, FieldNode } from './neural/engine.ts'
|
|
3
|
+
import type { LayerIndex } from './neural/morphology.ts'
|
|
4
|
+
import type { FailureKind } from './memory-adapter.ts'
|
|
5
|
+
|
|
6
|
+
export const LAYER_META = [
|
|
7
|
+
{ key: 'semantic', name: '语义记忆', en: 'SEMANTIC', hint: '稳定事实、状态与锚点' },
|
|
8
|
+
{ key: 'episodic', name: '情节记忆', en: 'EPISODIC', hint: '原话与阶段摘要' },
|
|
9
|
+
{ key: 'working', name: '工作记忆', en: 'WORKING', hint: '近期上下文与待澄清' },
|
|
10
|
+
] as const
|
|
11
|
+
|
|
12
|
+
export const KIND_LABEL: Record<string, string> = {
|
|
13
|
+
trace: '原话', anchor: '锚点', recent: '近期', state: '状态', summary: '摘要', clarification: '待澄清',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const STATUS_LABEL: Record<string, string> = { known: '已确认', stale: '可能过期', disputed: '存在分歧', active: '活跃' }
|
|
17
|
+
|
|
18
|
+
export function layerIndex(layer: string): LayerIndex {
|
|
19
|
+
return layer === 'semantic' ? 0 : layer === 'working' ? 2 : 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Raw-text kinds point at an editable trace; the rest are derived and read-only. */
|
|
23
|
+
export function isTraceBacked(node: Node): boolean {
|
|
24
|
+
return node.editable === true && typeof node.trace_id === 'string' && node.trace_id !== ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function weightOf(node: Node): number {
|
|
28
|
+
if (node.kind === 'state') return 0.62
|
|
29
|
+
if (node.kind === 'summary') return 0.58
|
|
30
|
+
if (node.kind === 'clarification') return 0.42
|
|
31
|
+
const importance = Math.max(1, Math.min(5, Number(node.importance ?? 1)))
|
|
32
|
+
return Math.min(1, (importance - 1) / 4 * 0.7 + (node.anchor ? 0.3 : 0) + (node.kind === 'recent' ? 0.12 : 0))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function toField(snapshot: Snapshot): { nodes: FieldNode[]; edges: FieldEdge[] } {
|
|
36
|
+
const nodes = snapshot.nodes.map((n): FieldNode => ({
|
|
37
|
+
id: n.id, layer: layerIndex(n.layer), derived: !isTraceBacked(n), weight: weightOf(n),
|
|
38
|
+
label: n.content || '(空)', kindLabel: KIND_LABEL[n.kind] ?? n.kind,
|
|
39
|
+
}))
|
|
40
|
+
const edges = snapshot.edges.map((e: Edge): FieldEdge => ({ source: e.source, target: e.target }))
|
|
41
|
+
return { nodes, edges }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function layerCounts(snapshot: Snapshot | null): [number, number, number] {
|
|
45
|
+
const c: [number, number, number] = [0, 0, 0]
|
|
46
|
+
if (snapshot) for (const n of snapshot.nodes) c[layerIndex(n.layer)]++
|
|
47
|
+
return c
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function formatTime(value: string | undefined): string {
|
|
51
|
+
if (!value) return '—'
|
|
52
|
+
const d = new Date(value)
|
|
53
|
+
if (Number.isNaN(d.getTime())) return value
|
|
54
|
+
const p = (n: number) => String(n).padStart(2, '0')
|
|
55
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function shortId(id: string): string {
|
|
59
|
+
return id.length > 14 ? `${id.slice(0, 6)}…${id.slice(-4)}` : id
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const FAILURE_TEXT: Record<FailureKind, { title: string; body: string }> = {
|
|
63
|
+
owner: { title: '仅所有者可管理记忆', body: '记忆库只对当前 DSH 的所有者开放。共享会话与访客无法查看或修改。' },
|
|
64
|
+
conflict: { title: '这条记忆已在别处更新', body: '版本不一致,保存或忘记已被拒绝。载入最新内容后再操作。' },
|
|
65
|
+
not_found: { title: '记录不存在', body: '它可能已被编辑替换或已被忘记。' },
|
|
66
|
+
scope: { title: '记忆范围不存在', body: '该范围可能已被移除,请重新选择。' },
|
|
67
|
+
invalid: { title: '内容长度无效', body: '记忆内容需为 1–2000 个字符。' },
|
|
68
|
+
unavailable: { title: '记忆内核暂不可用', body: '内核可能正在启动或重启。稍后重试。' },
|
|
69
|
+
unknown: { title: '操作未完成', body: '记忆库没有返回结果。稍后重试。' },
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const SOURCE_LABEL: Record<string, string> = {
|
|
73
|
+
user_statement: '用户陈述', assistant_statement: '助手陈述', assistant_reply: '助手回复',
|
|
74
|
+
tool_result: '工具结果', system: '系统', import: '导入', manual: '手动录入', edit: '编辑',
|
|
75
|
+
}
|
|
76
|
+
export function sourceLabel(kind: string | null | undefined): string {
|
|
77
|
+
if (!kind) return '—'
|
|
78
|
+
return SOURCE_LABEL[kind] ?? kind
|
|
79
|
+
}
|