@luziyang2026/dsh-question-nav 0.4.0 → 0.4.1
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 +17 -25
- package/README.md +16 -11
- package/README.zh.md +15 -9
- package/package.json +3 -5
- package/lib/types/core/history-index.d.ts +0 -99
- package/lib/types/core/load-all.d.ts +0 -53
package/LICENSE
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 Abel Keith Sun
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
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:
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
-
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
-
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
-
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
-
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
-
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
-
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
-
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
-
POSSIBILITY OF SUCH DAMAGE.
|
|
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
|
@@ -10,22 +10,26 @@ It is an external plugin bundle with zero runtime dependencies — the browser
|
|
|
10
10
|
half (`lib/client.js`) externalizes everything to the DSH shell, so it adds
|
|
11
11
|
only a small bundle to the GUI at load time.
|
|
12
12
|
|
|
13
|
-
Package: **`@luziyang2026/dsh-question-nav`** ([npm][npm]).
|
|
13
|
+
Package: **`@luziyang2026/dsh-question-nav`** ([npm][npm] · [GitHub][github]).
|
|
14
|
+
|
|
15
|
+
## Preview
|
|
16
|
+
|
|
17
|
+

|
|
14
18
|
|
|
15
19
|
## What it does
|
|
16
20
|
|
|
17
21
|
- **Left-edge dot minimap** (embedded, not reserving any width).
|
|
18
22
|
- **Vertically centered** in the conversation column.
|
|
19
|
-
- **One dot = one
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"load earlier" on click.
|
|
23
|
+
- **One dot = one turn that asked a question** (strictly aligned with the
|
|
24
|
+
Trajectory view's turn numbering), with a small count above the dot column.
|
|
25
|
+
- **Full history, persisted, no render-window expansion**: the plugin's host
|
|
26
|
+
half registers a `questionIndex` session projection — the projection
|
|
27
|
+
registry folds the whole event log (read-only, the chat's paged window is
|
|
28
|
+
never touched), the official projection cache persists it across restarts,
|
|
29
|
+
and push frames deliver new questions live.
|
|
27
30
|
- **Hover**: the dot enlarges and an instant tooltip (portal-rendered, no
|
|
28
|
-
native-title delay) shows the
|
|
31
|
+
native-title delay) shows the **turn label** (`Turn N`) plus the turn's
|
|
32
|
+
**full question text** (all of them, when one turn batched several).
|
|
29
33
|
- **Click**: jumps to that question. Only then does the jump loop page the
|
|
30
34
|
window (`loadOlder()`) to bring that specific page into view — never the
|
|
31
35
|
whole history up front.
|
|
@@ -79,8 +83,9 @@ module table.
|
|
|
79
83
|
|
|
80
84
|
## License
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
MIT.
|
|
83
87
|
|
|
84
88
|
[dsh]: https://github.com/deepseek-harness/deepseek-harness
|
|
85
89
|
[npm]: https://www.npmjs.com/package/@luziyang2026/dsh-question-nav
|
|
90
|
+
[github]: https://github.com/AbelKeithsun/dsh-question-nav
|
|
86
91
|
[bundle]: https://github.com/deepseek-harness/deepseek-harness
|
package/README.zh.md
CHANGED
|
@@ -7,19 +7,24 @@
|
|
|
7
7
|
它是外部插件 bundle,**零运行时依赖** —— 浏览器半区(`lib/client.js`)把一切
|
|
8
8
|
外部化给 DSH 外壳,加载时只往 GUI 里加一个很小的 bundle。
|
|
9
9
|
|
|
10
|
-
包名:**`@luziyang2026/dsh-question-nav`**([npm][npm])。
|
|
10
|
+
包名:**`@luziyang2026/dsh-question-nav`**([npm][npm] · [GitHub][github])。
|
|
11
|
+
|
|
12
|
+
## 效果展示
|
|
13
|
+
|
|
14
|
+

|
|
11
15
|
|
|
12
16
|
## 功能
|
|
13
17
|
|
|
14
18
|
- **左缘圆点迷你地图**:内嵌,**不占任何宽度**。
|
|
15
19
|
- **垂直居中**在对话栏中。
|
|
16
|
-
- **一个圆点 =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **悬停**:圆点放大 + 即时提示框(portal 渲染,无原生 `title`
|
|
20
|
+
- **一个圆点 = 一个含提问的 turn**(与轨迹视图的 Turn 编号严格对齐),
|
|
21
|
+
圆点列上方有圆点数量。
|
|
22
|
+
- **完整历史、持久化、不展开渲染窗口**:插件的 host 半面注册
|
|
23
|
+
`questionIndex` 会话投影——投影注册表折叠完整事件日志(只读,对话的
|
|
24
|
+
分页窗口完全不被动),官方 projection-cache 跨重启持久化,新提问通过
|
|
25
|
+
推送帧实时到达。
|
|
26
|
+
- **悬停**:圆点放大 + 即时提示框(portal 渲染,无原生 `title` 延迟)显示
|
|
27
|
+
**Turn 编号**(`Turn N`)和该轮提问**全文**(一轮多条提问时全部列出)。
|
|
23
28
|
- **点击**:跳转到该提问;只有这时跳转循环才 `loadOlder()` 扩窗,把**那一页**
|
|
24
29
|
带进视图——绝不会一次性展开整个历史。
|
|
25
30
|
- 圆点列空白区域**点穿**到对话内容,不会挡住聊天。
|
|
@@ -69,8 +74,9 @@ externals 经加载器模块表解析。
|
|
|
69
74
|
|
|
70
75
|
## License
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
MIT。
|
|
73
78
|
|
|
74
79
|
[dsh]: https://github.com/deepseek-harness/deepseek-harness
|
|
75
80
|
[npm]: https://www.npmjs.com/package/@luziyang2026/dsh-question-nav
|
|
81
|
+
[github]: https://github.com/AbelKeithsun/dsh-question-nav
|
|
76
82
|
[bundle]: https://github.com/deepseek-harness/deepseek-harness
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luziyang2026/dsh-question-nav",
|
|
3
3
|
"description": "In-session question navigator for the DSH web GUI: a vertical minimap of round dots overlaid on the left edge of the conversation column, one dot per user question — hover enlarges and shows the full question text, click jumps to that message.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.7.0",
|
|
7
7
|
"engines": {
|
|
@@ -69,7 +69,6 @@
|
|
|
69
69
|
"react-dom": "^18.2.0",
|
|
70
70
|
"tsdown": "^0.22.2",
|
|
71
71
|
"typescript": "^6.0.3",
|
|
72
|
-
"vite-tsconfig-paths": "^6.1.1",
|
|
73
72
|
"vitest": "^4.1.8"
|
|
74
73
|
},
|
|
75
74
|
"files": [
|
|
@@ -78,10 +77,9 @@
|
|
|
78
77
|
"cordis.patch.yml",
|
|
79
78
|
"LICENSE",
|
|
80
79
|
"README.md",
|
|
81
|
-
"README.zh.md"
|
|
82
|
-
"README.i18n.yaml"
|
|
80
|
+
"README.zh.md"
|
|
83
81
|
],
|
|
84
|
-
"license": "
|
|
82
|
+
"license": "MIT",
|
|
85
83
|
"repository": {
|
|
86
84
|
"type": "git",
|
|
87
85
|
"url": "https://github.com/AbelKeithsun/dsh-question-nav.git"
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Question-index builder over the raw session history RPC.
|
|
3
|
-
*
|
|
4
|
-
* DSH pages the rendered conversation window on purpose (memory economy):
|
|
5
|
-
* `chat.nodes` only ever holds the loaded window, and force-expanding it
|
|
6
|
-
* (repeated `loadOlder()`) materializes + renders the whole log — the exact
|
|
7
|
-
* cost DSH's paging exists to avoid. This module instead builds a lightweight
|
|
8
|
-
* index of every user question by paging the RAW history RPC (`session.history`
|
|
9
|
-
* with `beforeSeq`), which reads the host log without touching the render
|
|
10
|
-
* window at all. Only `{key, seq, time, text}` per question is retained.
|
|
11
|
-
*
|
|
12
|
-
* The chat anchor key is derived deterministically from the event — it equals
|
|
13
|
-
* `conversationContextKey('input-message', String(event.data.id))` — so the
|
|
14
|
-
* dots can target rows that are not loaded yet, and a click then pages the
|
|
15
|
-
* window on demand (see `jump.ts`).
|
|
16
|
-
*
|
|
17
|
-
* Pure-ish: takes injected ports (one raw history page read, clocks) so it is
|
|
18
|
-
* unit-testable without a browser or a live session.
|
|
19
|
-
*/
|
|
20
|
-
import type { QuestionNode } from './nodes.ts';
|
|
21
|
-
/** Minimal shape of a raw history event (structural, not SDK-bound). */
|
|
22
|
-
export interface RawEventLike {
|
|
23
|
-
type: string;
|
|
24
|
-
seq: number;
|
|
25
|
-
time: number;
|
|
26
|
-
surfaceOp?: unknown;
|
|
27
|
-
data?: {
|
|
28
|
-
id?: unknown;
|
|
29
|
-
source?: {
|
|
30
|
-
kind?: string;
|
|
31
|
-
plugin?: string;
|
|
32
|
-
};
|
|
33
|
-
content?: readonly {
|
|
34
|
-
type?: string;
|
|
35
|
-
text?: string;
|
|
36
|
-
}[];
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
/** The conversation Definition kind whose key a user question node uses. */
|
|
40
|
-
export declare const MESSAGE_DEFINITION_KIND = "input-message";
|
|
41
|
-
/**
|
|
42
|
-
* The engine-owned stable chat key for a user question event — mirrors
|
|
43
|
-
* `conversationContextKey('input-message', String(id))` from the DSH runtime
|
|
44
|
-
* (verified against it in the unit test).
|
|
45
|
-
*/
|
|
46
|
-
export declare function questionKey(id: unknown): string;
|
|
47
|
-
/**
|
|
48
|
-
* Whether a raw event is one user question the strip should index.
|
|
49
|
-
* Mirrors the DSH `messageDefinition` match + `start` classification:
|
|
50
|
-
* an append-origin `user/message` with a human (`user`) source. Replacement
|
|
51
|
-
* copies (compaction checkpoints, `source.kind === 'plugin'`) and injected
|
|
52
|
-
* context (`source.kind !== 'user'`) are excluded.
|
|
53
|
-
*/
|
|
54
|
-
export declare function isQuestionEvent(event: RawEventLike): boolean;
|
|
55
|
-
/** Map one raw question event to a strip question node, or null when not one. */
|
|
56
|
-
export declare function questionFromEvent(event: RawEventLike): QuestionNode | null;
|
|
57
|
-
export interface HistoryIndexPorts {
|
|
58
|
-
/**
|
|
59
|
-
* Read one raw history page. `beforeSeq` is exclusive (events with seq <
|
|
60
|
-
* beforeSeq); `undefined` reads the newest page. Resolves undefined when
|
|
61
|
-
* the page is unavailable (session gone / transport error).
|
|
62
|
-
*/
|
|
63
|
-
history: (beforeSeq: number | undefined, maxMessages: number) => Promise<{
|
|
64
|
-
events: readonly {
|
|
65
|
-
event: RawEventLike;
|
|
66
|
-
}[];
|
|
67
|
-
hasMore: boolean;
|
|
68
|
-
} | undefined>;
|
|
69
|
-
/** Monotonic ms clock. */
|
|
70
|
-
now: () => number;
|
|
71
|
-
}
|
|
72
|
-
export interface HistoryIndexOptions {
|
|
73
|
-
/** Raw messages per page (default 100). */
|
|
74
|
-
maxMessages?: number;
|
|
75
|
-
/** Max pages before giving up (default 200 => 20k messages). */
|
|
76
|
-
maxPages?: number;
|
|
77
|
-
/** Total wall-clock budget (default 30s). */
|
|
78
|
-
totalTimeoutMs?: number;
|
|
79
|
-
/** Abort the build; checked every iteration. */
|
|
80
|
-
signal?: AbortSignal;
|
|
81
|
-
/** Resume from a previous `nextBeforeSeq` instead of the newest page. */
|
|
82
|
-
startBeforeSeq?: number;
|
|
83
|
-
}
|
|
84
|
-
export type HistoryIndexCode = 'COMPLETE' | 'BUDGET' | 'TIMEOUT' | 'UNAVAILABLE' | 'CANCELLED';
|
|
85
|
-
export interface HistoryIndexResult {
|
|
86
|
-
ok: boolean;
|
|
87
|
-
code: HistoryIndexCode;
|
|
88
|
-
/** Questions collected so far, ascending by anchorSeq. */
|
|
89
|
-
questions: QuestionNode[];
|
|
90
|
-
/** Page count actually read. */
|
|
91
|
-
pages: number;
|
|
92
|
-
/** Where to continue (exclusive) when stopped early; undefined when COMPLETE. */
|
|
93
|
-
nextBeforeSeq: number | undefined;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Page the raw session history backward, collecting every user question into a
|
|
97
|
-
* lightweight index. Never touches the render window.
|
|
98
|
-
*/
|
|
99
|
-
export declare function buildQuestionIndex(ports: HistoryIndexPorts, options?: HistoryIndexOptions): Promise<HistoryIndexResult>;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Load-all orchestration for the question-nav strip.
|
|
3
|
-
*
|
|
4
|
-
* DSH sessions page history in fixed-size chunks: `chat.nodes` only ever holds
|
|
5
|
-
* the currently loaded window, and questions that still sit behind the "load
|
|
6
|
-
* older" button are invisible to the strip until the window is expanded
|
|
7
|
-
* backwards. This loop pages `loadOlder()` until `hasMore` is false (the whole
|
|
8
|
-
* history is materialized), so every user question becomes a dot.
|
|
9
|
-
*
|
|
10
|
-
* Pure-ish: takes injected ports (snapshot read, one paged loadOlder, view
|
|
11
|
-
* liveness, clocks) so it is unit-testable without a browser or session.
|
|
12
|
-
*/
|
|
13
|
-
export interface LoadAllSnapshot {
|
|
14
|
-
openState: string;
|
|
15
|
-
hasMore: boolean;
|
|
16
|
-
loadingOlder: boolean;
|
|
17
|
-
}
|
|
18
|
-
export interface LoadAllPorts {
|
|
19
|
-
/** Read the current session snapshot; undefined when unavailable. */
|
|
20
|
-
snapshot: () => LoadAllSnapshot | undefined;
|
|
21
|
-
/** Expand the window backwards by one page (may preserve scroll). */
|
|
22
|
-
loadOlder: () => Promise<void>;
|
|
23
|
-
/** True while the chat view is active (a `[data-chat-flow]` is mounted). */
|
|
24
|
-
isViewActive: () => boolean;
|
|
25
|
-
/** Monotonic ms clock. */
|
|
26
|
-
now: () => number;
|
|
27
|
-
/** Async sleep. */
|
|
28
|
-
sleep: (ms: number) => Promise<void>;
|
|
29
|
-
}
|
|
30
|
-
export interface LoadAllOptions {
|
|
31
|
-
/** Max older pages to fetch before giving up (default 400). */
|
|
32
|
-
maxPages?: number;
|
|
33
|
-
/** Total wall-clock budget for the whole expansion (default 60s). */
|
|
34
|
-
totalTimeoutMs?: number;
|
|
35
|
-
/** Poll interval for open/loading transitions (default 60ms). */
|
|
36
|
-
pollMs?: number;
|
|
37
|
-
/** Abort the expansion; checked every iteration. */
|
|
38
|
-
signal?: AbortSignal;
|
|
39
|
-
}
|
|
40
|
-
export type LoadAllCode = 'COMPLETE' | 'VIEW_INACTIVE' | 'NOT_OPEN' | 'BUDGET' | 'TIMEOUT' | 'CANCELLED';
|
|
41
|
-
export interface LoadAllResult {
|
|
42
|
-
ok: boolean;
|
|
43
|
-
code: LoadAllCode;
|
|
44
|
-
/** Number of `loadOlder` pages actually fetched. */
|
|
45
|
-
pages: number;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Expand the session window backwards until the earliest history is loaded.
|
|
49
|
-
* Waits while the session is still opening; aborts on cancellation, budget or
|
|
50
|
-
* timeout. Safe to re-enter: once `hasMore` is false the loop returns
|
|
51
|
-
* immediately with `COMPLETE`.
|
|
52
|
-
*/
|
|
53
|
-
export declare function loadAllOlder(ports: LoadAllPorts, options?: LoadAllOptions): Promise<LoadAllResult>;
|