dsh-plugin-sessiongraph
本机全部会话的全文检索:sessiongraph 跨工作区查找对话,中文两字词也能命中,sessiongraph_index 负责建索引。
安装
dsh plugin --profile web add dsh-plugin-sessiongraph
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-plugin-sessiongraph
English | [中文](README.zh.md)
Every DSH session on this machine, indexed as one searchable corpus — the model looks up "where did I say or do this" in milliseconds, instead of you digging through the sidebar or grepping compressed logs.
Architecture
Six packages behind one strict seam: adding a store or an indexer never touches the model tools.
Package · Role
service · Defines the ctx.sessiongraph seam: query vocabulary, store/indexer registries, schema v1, the Chinese bigram tokenizer
sqlite · Read provider: answers the seven queries from index.db, routing between the native and TypeScript engines
sessions · Write provider: scan session logs, decode frame by frame, extract entries, write the database (TypeScript and Rust engines); raw.ts reads unabridged events back
tool · Consumer: one sessiongraph tool (eight operations), the /sessiongraph command, the system-prompt section, the settings HTTP route, and tool visibility
native · Rust kernel: index writes (indexSessions) and queries (query); returns undefined on any failure, never throws
panel · Browser half only: the settings "会话检索" tab (dsh.client; the host entry is an empty apply)
bundle · Installable meta-package: cordis.patch.yml
Routing is "exactly one claimant", never first match. Every registered store is asked concurrently on each query; zero claimants and several claimants both throw (SESSIONGRAPH_UNAVAILABLE / SESSIONGRAPH_CONFLICT). Registration order and hot reload therefore never change the result. status is the one operation that answers with no store at all — someone who has never built an index needs to learn that fact, not receive the same failure every other operation raises.
Rust kernel
The indexing path is a napi-rs native module (Rayon for parallelism, rusqlite for the database), not WASM: the kernel must read the filesystem directly, write SQLite directly, and move gigabyte-scale data. WASM's boundary copies and absent fs/sqlite are disqualifying.
· Full rebuild · Incremental · Query · Index size
Rust kernel · 6.76 s · 59–107 ms · 0.1–6.9 ms · ~170 MB
TypeScript (fallback) · 19.22 s · 230 ms · same · ~170 MB
Both databases hold identical content, so this is not "a different index": sqlite_master DDL is byte-for-byte equal, every unchanged session compares field by field with zero differences, entry rows / session rows / FTS token multisets hash to the same SHA-256, and the TypeScript store reads a Rust-built database directly.
Engine selection is engine: 'auto' | 'native' | 'typescript', defaulting to auto: use the native kernel when present, fall back silently otherwise — a missing binary is never an error.
How it relates to memory and to the built-in session search
The same question has three homes, and picking the wrong one means finding nothing:
· Memory (OpenViking / MEMORY.md) · Built-in DSH session_search · sessiongraph
Stores · Extracted conclusions: preferences, decisions, entities · Semantic text projected from session events, per event type · Raw entries: message bodies, tool-call names and arguments
Form · Markdown entries under viking:// · SQLite FTS5 in an in-memory database (openAt: first-search, rebuilt every start) · SQLite on disk, maintained incrementally
Scope · Machine-wide · The caller's workspace · Machine-wide, across workspaces
Chinese · Semantic (embeddings), recalls by meaning · No — the unicode61 tokenizer does no CJK handling at all: 我今天在改广告SDK is one token, so 广告 matches nothing · Yes — purpose-built bigram tokenizer, two-character terms match
How you search · By meaning · By token or phrase (English keywords, whole words) · By characters, full text (Chinese and English)
Can it find the exact words · No — only what survived extraction · Partly: returns an excerpt · Yes — the whole thing, including commands and arguments
Good for · "What do I prefer", "what did we decide" · English keywords, exact phrases, recent sessions · "When did I say this", "what was that command"
In one line: memory answers "what is the conclusion", the built-in search answers "which session mentioned it", sessiongraph answers "what was actually said".
Since 0.2.0 the model no longer has to choose: the settings page hides the host's five session_* tools by default and leaves only sessiongraph (see "Settings: which session tools the model sees"). Switch back any time.
Three differences that matter in practice:
- Chinese: the built-in search is effectively unusable for Chinese (its tokenizer has no CJK handling); sessiongraph is built for two-character Chinese terms
- Scope: the built-in search only sees the caller's workspace; sessiongraph spans every workspace
- Persistence: the built-in index lives in memory, opens on first search, and is gone after a restart; sessiongraph's index is on disk, usable immediately, kept fresh incrementally
Memory does not overlap with either: a conversation may never enter memory (short, casual, below any threshold) yet it is always in sessiongraph's raw index. Conversely, a memory entry has usually lost its context, and checking it means going back to the original.
Usage
Three entry points
Entry · How
The model calls it · Just ask "which session discussed the splash screen"; it reaches for sessiongraph
Slash command · /sessiongraph status / index / search <term> / workspaces
Command results flow back (opt-in, off by default) · With injectCommandResults: true the command's result is injected into the conversation as a plugin/notice message, so the model's next turn knows what you looked up — the same mechanism /plan uses. Off by default because commands run on the host's main thread, and calling
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:12,最后更新 2026-11-06