dsh-plugin-sessiongraph 0.0.0-stage → 0.1.12

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CC ZHAO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,137 @@
1
- # Temporary Holding Version
1
+ # dsh-plugin-sessiongraph
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ English | [中文](README.zh.md)
4
+
5
+ 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.
6
+
7
+ ## Architecture
8
+
9
+ Six packages behind one strict seam: **adding a store or an indexer never touches the model tools.**
10
+
11
+ | Package | Role |
12
+ |---|---|
13
+ | `service` | Defines the `ctx.sessiongraph` seam: query vocabulary, store/indexer registries, schema v1, the Chinese bigram tokenizer |
14
+ | `sqlite` | Read provider: answers the five queries from `index.db` |
15
+ | `sessions` | Write provider: scan session logs, decode frame by frame, extract entries, write the database (TypeScript and Rust engines) |
16
+ | `tool` | Consumer: the two model tools, the `/sessiongraph` command, and the system-prompt section |
17
+ | `native` | Loader for the Rust kernel (returns `undefined` on any failure, never throws) |
18
+ | `bundle` | Installable meta-package: `cordis.patch.yml` |
19
+
20
+ **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.
21
+
22
+ ### Rust kernel
23
+
24
+ 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.
25
+
26
+ | | Full rebuild | Incremental | Query | Index size |
27
+ |---|---|---|---|---|
28
+ | **Rust kernel** | **6.76 s** | **59–107 ms** | 0.1–6.9 ms | ~170 MB |
29
+ | TypeScript (fallback) | 19.22 s | 230 ms | same | ~170 MB |
30
+
31
+ 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.
32
+
33
+ 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**.
34
+
35
+ ## How it relates to memory and to the built-in session search
36
+
37
+ The same question has three homes, and picking the wrong one means finding nothing:
38
+
39
+ | | Memory (OpenViking / MEMORY.md) | **Built-in DSH `session_search`** | **sessiongraph** |
40
+ |---|---|---|---|
41
+ | Stores | Extracted conclusions: preferences, decisions, entities | **Semantic text** projected from session events, per event type | **Raw entries**: message bodies, tool-call names and arguments |
42
+ | Form | Markdown entries under `viking://` | SQLite FTS5 in an **in-memory** database (`openAt: first-search`, rebuilt every start) | SQLite **on disk**, maintained incrementally |
43
+ | Scope | Machine-wide | **The caller's workspace** | Machine-wide, across workspaces |
44
+ | 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** |
45
+ | How you search | By meaning | By token or phrase (English keywords, whole words) | By characters, full text (Chinese and English) |
46
+ | Can it find the exact words | No — only what survived extraction | Partly: returns an excerpt | Yes — the whole thing, including commands and arguments |
47
+ | 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" |
48
+
49
+ In one line: **memory answers "what is the conclusion", the built-in search answers "which session mentioned it", sessiongraph answers "what was actually said".**
50
+
51
+ Three differences that matter in practice:
52
+
53
+ - **Chinese**: the built-in search is effectively unusable for Chinese (its tokenizer has no CJK handling); sessiongraph is built for two-character Chinese terms
54
+ - **Scope**: the built-in search only sees the caller's workspace; sessiongraph spans every workspace
55
+ - **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
56
+
57
+ 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.
58
+
59
+ ## Usage
60
+
61
+ ### Three entry points
62
+
63
+ | Entry | How |
64
+ |---|---|
65
+ | **The model calls it** | Just ask "which session discussed the splash screen"; it reaches for `sessiongraph` |
66
+ | **Slash command** | `/sessiongraph status` / `index` / `search <term>` / `workspaces` |
67
+ | **Command results flow back** | After the command runs, its 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 |
68
+
69
+ ### Tools
70
+
71
+ `sessiongraph`:
72
+
73
+ | Operation | What it does |
74
+ |---|---|
75
+ | `search` | Cross-session full-text search; filters on `workspace` / `after` / `before` / `kinds`. Returns title, workspace, time, turn and a highlighted snippet |
76
+ | `session` | Open one session by full id or unique prefix: metadata, ordered entries, child sessions |
77
+ | `timeline` | List sessions newest-first, optionally scoped to a workspace |
78
+ | `workspaces` | Workspace inventory with session/entry counts |
79
+ | `status` | The index's own state — **the only operation that answers before an index exists** |
80
+
81
+ `sessiongraph_index`: build or refresh on its own 300-second budget; sessions whose source mtime and size are unchanged are skipped, and past a thousand sessions the run becomes a host background job returning a job id.
82
+
83
+ ### Commands
84
+
85
+ | Command | What it does |
86
+ |---|---|
87
+ | `/sessiongraph` | Same as `status` |
88
+ | `/sessiongraph status` | Index size, disk usage, last build time, live session count, current workspace |
89
+ | `/sessiongraph index [--force]` | Build or refresh; large corpora delegate to a background job and print the job id |
90
+ | `/sessiongraph search <term>` | Search directly, printing the top ten hits |
91
+ | `/sessiongraph workspaces` | Workspace list, `*` marking the current one |
92
+
93
+ ### Automatic indexing
94
+
95
+ **A fresh install needs nothing from you.** Four mechanisms, all automatic:
96
+
97
+ | When | Mechanism | Cost |
98
+ |---|---|---|
99
+ | **The first time** (no index yet) | Session creation builds it in the background (`autoBuild`, on by default) | seconds to minutes, off the critical path |
100
+ | **On session creation** (index present) | `agent/created` → incremental run through `ctx.jobs`, never blocking | 59–107 ms |
101
+ | **Before a query** | If `status.json`'s `indexedAt` is older than 5 minutes, refresh incrementally first | ~400 ms |
102
+ | **The current session** | Reads the host's `ctx.sessionQuery` and indexes live sessions that have not hit disk yet | negligible |
103
+
104
+ Prefer to drive it yourself? `autoBuild: false` disables the first automatic build and `autoIndex: false` disables the incremental refreshes; everything then goes through `sessiongraph_index` / `/sessiongraph index`.
105
+
106
+ ### What gets indexed
107
+
108
+ `user/message` (only `source.kind === 'user'`, i.e. text a human typed), the `text` blocks of `assistant/message` (`reasoning` is 61% of the bytes and is not indexed), `tool/call` names and arguments, and `session/title`. Skipped: `tool/result` bodies, `request/header`, `step/*`, `job/*`.
109
+
110
+ ### Why two-character Chinese terms match
111
+
112
+ SQLite's `unicode61` treats `我今天在改广告SDK` as a single token, so searching `广告` finds nothing; `trigram` needs at least three characters, so two-character words stay unreachable. Write and read paths therefore share one purpose-built tokenizer (`packages/service/src/tokenize.ts`): CJK runs split into overlapping bigrams (`广告点击` → `广告` `告点` `点击`), Latin/digit runs become lowercased whole words split on `_`, and query tokens are quoted and joined with `AND`. The trade-off is recall over phrase precision — searching your own history is a "find it" problem.
113
+
114
+ ## Install
115
+
116
+ ```sh
117
+ dsh plugin add dsh-plugin-sessiongraph
118
+ ```
119
+
120
+ Or link a local checkout in a profile:
121
+
122
+ ```json
123
+ "dependencies": { "dsh-plugin-sessiongraph": "link:/path/to/dsh-plugin-sessiongraph" },
124
+ "dsh": { "profile": { "bundles": [ "...", "dsh-plugin-sessiongraph" ] } }
125
+ ```
126
+
127
+ Restart DSH afterwards; the first `sessiongraph_index` call builds the database, and everything after that is incremental.
128
+
129
+ ## Development
130
+
131
+ ```sh
132
+ pnpm install
133
+ pnpm build # tsc -b, five TypeScript project references
134
+ pnpm test # vitest run, 200+ tests
135
+ ```
136
+
137
+ Internal contracts — data format, schema, tokenizer rules, per-package responsibilities, and which host capabilities this plugin reuses — live in [`DESIGN.md`](./DESIGN.md).
@@ -0,0 +1,43 @@
1
+ # The dsh-plugin-sessiongraph bundle patch: mounts the whole local-conversation-index
2
+ # capability as ONE layer over an existing dsh profile.
3
+ #
4
+ # Row order carries no load semantics — activation is service-availability
5
+ # driven — so the seam may appear before or after the providers that fill it.
6
+ # The user's own profile patch layer can address any row below by `id` to
7
+ # retune it (last write per row wins), e.g. pointing `sessionsRoot` at another
8
+ # harness data directory or moving `indexRoot` off the default.
9
+
10
+ - insert:
11
+ # TEMPORARILY DISABLED: this row mounts the host's own `@deepseek-ai/dsh-session-query-sqlite`
12
+ # to read live sessions. It publishes a machine-wide `sessionQuery`, and when DSH's session
13
+ # controller resolves that service instead of its own isolated instance, the session list stops
14
+ # working (history unopenable, new session unresponsive). Kept here, commented out, until the
15
+ # interaction is understood.
16
+ #
17
+ # - id: sessiongraph-session-query
18
+ # name: '@deepseek-ai/dsh-session-query-sqlite'
19
+ # config:
20
+ # path: ':memory:'
21
+ # openAt: never
22
+
23
+ # Service Definition: publishes ctx.sessiongraph, the store/indexer provider
24
+ # registries, and the normalized query vocabulary. Carries no index of its own.
25
+ - id: sessiongraph
26
+ name: 'dsh-plugin-sessiongraph-service'
27
+
28
+ # Service Provider (read): serves the five operations from <indexRoot>/index.db.
29
+ # Claims a root only when that file already exists; `status` still answers for
30
+ # a root with no index so "never indexed" is distinguishable from "no match".
31
+ - id: sessiongraph-sqlite
32
+ name: 'dsh-plugin-sessiongraph-sqlite'
33
+
34
+ # Service Provider (write): scans ~/.dsh/sessions, decompresses each
35
+ # session.v*.jsonl.zstd, and writes that same index. Registers ONLY an
36
+ # indexer, never a store, so the seam's one-claimant-per-root rule stays intact.
37
+ - id: sessiongraph-sessions
38
+ name: 'dsh-plugin-sessiongraph-sessions'
39
+
40
+ # Consumer: the model-facing `sessiongraph` and `sessiongraph_index` tools plus
41
+ # their system-prompt section.
42
+ - id: sessiongraph-tool
43
+ name: 'dsh-plugin-sessiongraph-tool'
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "dsh-plugin-sessiongraph",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.12",
4
+ "description": "Bundle for the sessiongraph capability — install this package to add local conversation-history search to a DeepSeek Harness profile",
5
+ "keywords": [
6
+ "dsh",
7
+ "dsh-plugin",
8
+ "deepseek-harness",
9
+ "cordis",
10
+ "bundle",
11
+ "sessiongraph",
12
+ "session-history"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "CC ZHAO",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/CC19990113/dsh-plugin-sessiongraph.git",
19
+ "directory": "packages/bundle"
20
+ },
21
+ "homepage": "https://github.com/CC19990113/dsh-plugin-sessiongraph#readme",
22
+ "bugs": "https://github.com/CC19990113/dsh-plugin-sessiongraph/issues",
23
+ "type": "module",
24
+ "files": [
25
+ "cordis.patch.yml",
26
+ "README.md"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "provenance": false
31
+ },
32
+ "dependencies": {
33
+ "dsh-plugin-sessiongraph-service": "^0.1.12",
34
+ "dsh-plugin-sessiongraph-sessions": "^0.1.12",
35
+ "dsh-plugin-sessiongraph-sqlite": "^0.1.12",
36
+ "dsh-plugin-sessiongraph-tool": "^0.1.12"
37
+ },
38
+ "scripts": {}
6
39
  }