dsh-turn-outline 0.2.5 → 0.2.7

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/README.en.md CHANGED
@@ -9,6 +9,14 @@
9
9
 
10
10
  [中文](README.md)
11
11
 
12
+ ## Preview
13
+
14
+ <p align="center">
15
+ <img src="docs/assets/turn-outline-panel.png" alt="Turn Outline tab: the session folded into per-user turns of input, tool steps and output" width="420" />
16
+ </p>
17
+
18
+ > Real-session screenshot: the whole session folds into turns 1-5, each = user input + tool steps + final output; the tool filter chips at the top (All / bash / read / edit / More) aggregate by semantic dot, and clicking any row jumps back to its place in the conversation.
19
+
12
20
  ## Overview
13
21
 
14
22
  **Who it is for**: users of DeepSeek Harness (Web / Desktop profile) with long sessions who want to quickly revisit "what happened at step N" — at zero cost, without adding an LLM or touching the DSH core.
@@ -29,24 +37,24 @@
29
37
 
30
38
  ### Option 1: one-liner from npm (recommended)
31
39
 
32
- Install better-sidebar first (stable line pinned to 0.17.x), then this plugin:
40
+ Install better-sidebar first, then this plugin:
33
41
 
34
42
  ```sh
35
- npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar@0.17.1
43
+ npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar
36
44
  npx @deepseek-ai/dsh plugin --profile desktop add dsh-turn-outline
37
45
  ```
38
46
 
39
47
  > Using the Web profile? Swap `--profile desktop` for `--profile web`.
40
48
  >
41
- > ⚠️ **Version-line note**: this plugin is currently validated on **better-sidebar 0.17.x + DSH 0.1.1-rc.x (stable)**. The npm `latest` better-sidebar 0.18.0 supports only DSH 0.1.2-rc.1+ and has not been co-tested with this plugin — until a 0.18-adapted release, pin `dsh-better-sidebar@0.17.1` before upgrading your host.
49
+ > ℹ️ **Version-line note**: this plugin declares the peer range `dsh-better-sidebar@^0.17.0 || ^0.18.0`, and both lines are co-tested — **better-sidebar 0.18.0 + DSH 0.1.2-rc.1** (validated since 0.2.7) and **better-sidebar 0.17.1 + DSH 0.1.1-rc.x (stable)**. Note that better-sidebar 0.18.0 supports only DSH 0.1.2-rc.1+: on an older stable host (0.1.0-rc.8 ~ 0.1.1-rc.2), pin `dsh-better-sidebar@0.17.1`.
42
50
 
43
51
  ### Option 2: install from source (development / offline)
44
52
 
45
53
  ```sh
46
54
  git clone https://github.com/Andor-Z/dsh-turn-outline
47
55
  cd dsh-turn-outline
48
- pnpm install && pnpm build && pnpm pack # produces dsh-turn-outline-0.2.5.tgz
49
- npx @deepseek-ai/dsh plugin --profile desktop add "file:$(pwd)/dsh-turn-outline-0.2.5.tgz"
56
+ pnpm install && pnpm build && pnpm pack # produces dsh-turn-outline-0.2.6.tgz
57
+ npx @deepseek-ai/dsh plugin --profile desktop add "file:$(pwd)/dsh-turn-outline-0.2.6.tgz"
50
58
  ```
51
59
 
52
60
  ### Restart & enable
@@ -70,7 +78,7 @@ npx @deepseek-ai/dsh plugin --profile desktop remove dsh-turn-outline
70
78
 
71
79
  ```
72
80
  host (Node) inject: ['webServer','sessionQuery','sessions']
73
- POST /turntrail/api/events { sessionId, afterSeq }
81
+ POST /turn-outline/api/events { sessionId, afterSeq }
74
82
  → live sessions: borrow the frozen snapshot of the live store (O(1) probe + O(delta) projection, zero deep clones)
75
83
  → cold sessions: sessionQuery persisted log + lastSeq cache (LRU + byte cap)
76
84
  → paged responses (PAGE_SIZE events per page, hasMore semantics) + field trimming (no full tool-result payloads)
@@ -91,7 +99,7 @@ client (browser) inject: ['sessions','betterSidebar'] (betterSidebar is an optio
91
99
 
92
100
  ## Permissions & data
93
101
 
94
- - **Session logs**: one read-only host route `POST /turntrail/api/events`, browser same-origin + Host-header trust fence; read-only, never writes or mutates the session.
102
+ - **Session logs**: one read-only host route `POST /turn-outline/api/events`, browser same-origin + Host-header trust fence; read-only, never writes or mutates the session.
95
103
  - **Zero LLM calls**: nothing is sent to any model; no token consumption.
96
104
  - **No credential access**: no other network/file access besides session events; no user config, no persistent storage (tab state follows better-sidebar's layout persistence).
97
105
 
@@ -102,14 +110,14 @@ client (browser) inject: ['sessions','betterSidebar'] (betterSidebar is an optio
102
110
  | Platform | DSH 0.1.1-rc.x (stable line; web/desktop profile) |
103
111
  | Dependencies | dsh-better-sidebar (optional peer; inert when absent), react (injected by the DSH client seed) |
104
112
  | Build | TypeScript + esbuild, no runtime npm dependencies |
105
- | Tests | vitest, 77 cases (fold / folder / jump / tab / host) |
113
+ | Tests | vitest, 80 cases (fold / folder / jump / tab / host) |
106
114
 
107
115
  ## Development
108
116
 
109
117
  ```sh
110
118
  pnpm install # if pnpm 10+ blocks build scripts, allow esbuild as prompted (see pnpm-workspace.yaml)
111
119
  pnpm typecheck # tsc --noEmit
112
- pnpm test # vitest run (77 cases)
120
+ pnpm test # vitest run (80 cases)
113
121
  pnpm build # esbuild → lib/index.js + lib/client.js (includes bundle-contract self check)
114
122
  pnpm pack # produces dsh-turn-outline-<version>.tgz (prepack rebuilds automatically)
115
123
  ```
package/README.md CHANGED
@@ -9,6 +9,14 @@
9
9
 
10
10
  [English](README.en.md)
11
11
 
12
+ ## 样式预览
13
+
14
+ <p align="center">
15
+ <img src="docs/assets/turn-outline-panel.png" alt="轮次轨迹 tab:会话按用户轮次折叠成「输入 → 工具步骤 → 输出」的结构化轨迹" width="420" />
16
+ </p>
17
+
18
+ > 真实会话截图:整条会话折叠为「轮次 1-5」,每轮 = 用户输入 + 工具步骤 + 最终输出;顶部工具筛选 chips(全部 / bash / read / edit / 更多)按语义色点聚合,点击任意行可定位回对话原位。
19
+
12
20
  ## 概览
13
21
 
14
22
  **适合谁**:使用 DeepSeek Harness(Web / Desktop profile)、会话很长、想快速回看某一步「当时做了什么」的用户——希望零成本、不引入 LLM 也不改动核心。
@@ -29,24 +37,24 @@
29
37
 
30
38
  ### 方式一:npm 一行命令(推荐)
31
39
 
32
- 先装 better-sidebar(stable 线钉 0.17.x),再装本插件:
40
+ 先装 better-sidebar,再装本插件:
33
41
 
34
42
  ```sh
35
- npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar@0.17.1
43
+ npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar
36
44
  npx @deepseek-ai/dsh plugin --profile desktop add dsh-turn-outline
37
45
  ```
38
46
 
39
47
  > 使用 Web profile 时把 `--profile desktop` 换成 `--profile web`。
40
48
  >
41
- > ⚠️ **版本线说明**:本插件当前验证于 **better-sidebar 0.17.x + DSH 0.1.1-rc.x(stable)**。npm `latest` 的 better-sidebar 0.18.0 仅支持 DSH 0.1.2-rc.1+,尚未与本插件联调——在 0.18 适配版发布前,升级宿主请先固定 `dsh-better-sidebar@0.17.1`。
49
+ > ℹ️ **版本线说明**:本插件 peer 声明 `dsh-better-sidebar@^0.17.0 || ^0.18.0`,双线均已联调——**better-sidebar 0.18.0 + DSH 0.1.2-rc.1**(0.2.7 起验证)与 **better-sidebar 0.17.1 + DSH 0.1.1-rc.x(stable)**。注意 better-sidebar 0.18.0 仅支持 DSH 0.1.2-rc.1+:旧 stable 宿主(0.1.0-rc.8 ~ 0.1.1-rc.2)请固定安装 `dsh-better-sidebar@0.17.1`。
42
50
 
43
51
  ### 方式二:源码本地安装(开发 / 离线)
44
52
 
45
53
  ```sh
46
54
  git clone https://github.com/Andor-Z/dsh-turn-outline
47
55
  cd dsh-turn-outline
48
- pnpm install && pnpm build && pnpm pack # 产出 dsh-turn-outline-0.2.5.tgz
49
- npx @deepseek-ai/dsh plugin --profile desktop add "file:$(pwd)/dsh-turn-outline-0.2.5.tgz"
56
+ pnpm install && pnpm build && pnpm pack # 产出 dsh-turn-outline-0.2.6.tgz
57
+ npx @deepseek-ai/dsh plugin --profile desktop add "file:$(pwd)/dsh-turn-outline-0.2.6.tgz"
50
58
  ```
51
59
 
52
60
  ### 重启与启用
@@ -70,7 +78,7 @@ npx @deepseek-ai/dsh plugin --profile desktop remove dsh-turn-outline
70
78
 
71
79
  ```
72
80
  host(Node) inject: ['webServer','sessionQuery','sessions']
73
- POST /turntrail/api/events { sessionId, afterSeq }
81
+ POST /turn-outline/api/events { sessionId, afterSeq }
74
82
  → 活跃会话:直接借用 live store 冻结快照(O(1) 探测 + O(delta) 投影,零深拷贝)
75
83
  → 冷会话:sessionQuery 持久化日志 + lastSeq 缓存(LRU + 字节上限)
76
84
  → 响应逐页(每页 PAGE_SIZE 事件,hasMore 语义)+ 字段裁剪(不发 tool 结果全文)
@@ -91,7 +99,7 @@ client(浏览器) inject: ['sessions','betterSidebar'](betterSidebar 可
91
99
 
92
100
  ## 权限与数据
93
101
 
94
- - **会话日志**:host 只读路由 `POST /turntrail/api/events`,浏览器同源 + Host 头信任围栏;只读不回写、不改动会话。
102
+ - **会话日志**:host 只读路由 `POST /turn-outline/api/events`,浏览器同源 + Host 头信任围栏;只读不回写、不改动会话。
95
103
  - **零 LLM 调用**:不向任何模型发送内容,无 token 消耗。
96
104
  - **不读取凭据**:除会话事件外无其他网络 / 文件访问;无用户配置、无持久化存储(tab 状态随 better-sidebar 布局持久化)。
97
105
 
@@ -102,14 +110,14 @@ client(浏览器) inject: ['sessions','betterSidebar'](betterSidebar 可
102
110
  | 平台 | DSH 0.1.1-rc.x(stable 线;web/desktop profile) |
103
111
  | 依赖 | dsh-better-sidebar(可选 peer;未装则 inert)、react(DSH client 种子注入) |
104
112
  | 构建 | TypeScript + esbuild,无运行时 npm 依赖 |
105
- | 测试 | vitest 77 用例(fold / folder / jump / tab / host) |
113
+ | 测试 | vitest 80 用例(fold / folder / jump / tab / host) |
106
114
 
107
115
  ## 开发
108
116
 
109
117
  ```sh
110
118
  pnpm install # pnpm 10+ 拦截构建脚本时按提示允许 esbuild(见 pnpm-workspace.yaml)
111
119
  pnpm typecheck # tsc --noEmit
112
- pnpm test # vitest run(77 用例)
120
+ pnpm test # vitest run(80 用例)
113
121
  pnpm build # esbuild → lib/index.js + lib/client.js(含 bundle 契约自检)
114
122
  pnpm pack # 产出 dsh-turn-outline-<version>.tgz(prepack 自动重新构建)
115
123
  ```
package/lib/client.js CHANGED
@@ -1107,7 +1107,7 @@ function t(lang, key) {
1107
1107
  var React = __toESM(require("react"), 1);
1108
1108
  var MAX_PULL_PAGES = 64;
1109
1109
  function fetchTrailEvents(sessionId, afterSeq, base) {
1110
- const url = new URL("/turntrail/api/events", base ?? window.location.origin);
1110
+ const url = new URL("/turn-outline/api/events", base ?? window.location.origin);
1111
1111
  return fetch(url.toString(), {
1112
1112
  method: "POST",
1113
1113
  headers: { "content-type": "application/json", accept: "application/json" },
@@ -1134,15 +1134,23 @@ function useTrail(sessionId, visible, fetchEvents, sessionsService) {
1134
1134
  const requestGen = React.useRef(0);
1135
1135
  const lastPullAt = React.useRef(0);
1136
1136
  const throttleTimer = React.useRef(null);
1137
+ const visibleRef = React.useRef(visible);
1138
+ const schedulePullRef = React.useRef(null);
1137
1139
  React.useEffect(() => {
1138
1140
  fetchRef.current = fetchEvents;
1139
1141
  }, [fetchEvents]);
1142
+ React.useEffect(() => {
1143
+ visibleRef.current = visible;
1144
+ }, [visible]);
1140
1145
  React.useEffect(() => () => {
1141
1146
  if (throttleTimer.current !== null) window.clearTimeout(throttleTimer.current);
1147
+ schedulePullRef.current = null;
1142
1148
  }, []);
1143
1149
  const pull = React.useCallback(async () => {
1144
1150
  const generation = ++requestGen.current;
1145
1151
  const sid = sessionIdRef.current;
1152
+ const seqBefore = seqRef.current;
1153
+ let drainCapped = false;
1146
1154
  try {
1147
1155
  for (let page = 0; page < MAX_PULL_PAGES; page += 1) {
1148
1156
  const { events, hasMore } = await fetchRef.current(sid, seqRef.current);
@@ -1163,10 +1171,17 @@ function useTrail(sessionId, visible, fetchEvents, sessionsService) {
1163
1171
  setStats(folder.stats());
1164
1172
  }
1165
1173
  }
1166
- if (hasMore !== true || events.length === 0) break;
1174
+ if (hasMore !== true || events.length === 0) {
1175
+ drainCapped = false;
1176
+ break;
1177
+ }
1178
+ drainCapped = page === MAX_PULL_PAGES - 1;
1167
1179
  }
1168
1180
  setError(null);
1169
1181
  setStatus("ready");
1182
+ if (drainCapped && seqRef.current !== seqBefore && generation === requestGen.current && sid === sessionIdRef.current && visibleRef.current && schedulePullRef.current !== null) {
1183
+ schedulePullRef.current();
1184
+ }
1170
1185
  } catch (reason) {
1171
1186
  if (generation !== requestGen.current) return;
1172
1187
  setError(reason instanceof Error ? reason.message : String(reason));
@@ -1192,6 +1207,9 @@ function useTrail(sessionId, visible, fetchEvents, sessionsService) {
1192
1207
  void pull();
1193
1208
  }, MIN_GAP - since);
1194
1209
  }, [pull]);
1210
+ React.useEffect(() => {
1211
+ schedulePullRef.current = schedulePull;
1212
+ }, [schedulePull]);
1195
1213
  React.useEffect(() => {
1196
1214
  sessionIdRef.current = sessionId;
1197
1215
  folderRef.current = null;
@@ -1809,28 +1827,27 @@ function targetIcon() {
1809
1827
  }
1810
1828
 
1811
1829
  // src/client/index.tsx
1812
- var primitives = (() => {
1813
- try {
1814
- return require("@deepseek-ai/dsh-client-ui-primitives");
1815
- } catch {
1816
- return null;
1817
- }
1818
- })();
1819
- function listIcon() {
1820
- const path = React3.createElement("path", {
1821
- d: "M3 3.5h10M3 8h10M3 12.5h6",
1822
- fill: "none",
1823
- stroke: "currentColor",
1824
- strokeWidth: "1.5",
1825
- strokeLinecap: "round",
1826
- strokeLinejoin: "round"
1827
- });
1828
- return React3.createElement("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": true }, path);
1829
- }
1830
- function tabIcon(size) {
1831
- const IconList = primitives !== null && typeof primitives.IconBranchOutline16 === "function" ? primitives.IconBranchOutline16 : null;
1832
- if (IconList !== null) return React3.createElement(IconList, { size });
1833
- return listIcon();
1830
+ function routeIcon(size) {
1831
+ const children = [
1832
+ React3.createElement("circle", { key: "a", cx: 6, cy: 19, r: 3 }),
1833
+ React3.createElement("path", { key: "p", d: "M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15" }),
1834
+ React3.createElement("circle", { key: "b", cx: 18, cy: 5, r: 3 })
1835
+ ];
1836
+ return React3.createElement(
1837
+ "svg",
1838
+ {
1839
+ viewBox: "0 0 24 24",
1840
+ width: size,
1841
+ height: size,
1842
+ fill: "none",
1843
+ stroke: "currentColor",
1844
+ strokeWidth: 2,
1845
+ strokeLinecap: "round",
1846
+ strokeLinejoin: "round",
1847
+ "aria-hidden": true
1848
+ },
1849
+ children
1850
+ );
1834
1851
  }
1835
1852
  var inject = ["sessions", "betterSidebar"];
1836
1853
  function apply(ctx) {
@@ -1846,7 +1863,7 @@ function apply(ctx) {
1846
1863
  ctx.effect(() => betterSidebar.registerTab({
1847
1864
  id: "turn-outline",
1848
1865
  title: () => typeof navigator !== "undefined" && navigator.language.toLowerCase().startsWith("zh") ? "\u8F6E\u6B21\u8F68\u8FF9" : "Turn Outline",
1849
- icon: (size) => tabIcon(size),
1866
+ icon: (size) => routeIcon(size),
1850
1867
  order: 46,
1851
1868
  single: true,
1852
1869
  component: (props) => React3.createElement(TrailTab, {