dsh-session-flow 1.3.0 → 1.4.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/lib/archive.js CHANGED
@@ -1,7 +1,10 @@
1
1
  // lib/archive.js — 会话存档核心:扫描 / 解码 / 解析 / 统计。
2
2
  //
3
- // 数据来源:~/.dsh/sessions/<workspace>/session-<uuid>/session.jsonl.zstd
3
+ // 数据来源:~/.dsh/sessions/<workspace>/session-<uuid>/session[.v2].jsonl.zstd
4
4
  // (zstd 压缩的多帧 JSONL;compression: none 时为明文 session.jsonl)。
5
+ // dsh v0.1.3 起存档升级 v2(session.v2.jsonl.zstd,行信封 {type,seq,time,data} 不变,
6
+ // header version:2 + 新增 session/end-seed 等事件):v1 文件冻结在迁移时刻、v2 持续
7
+ // 写入——读取必须优先 v2,否则读到冻结旧数据、纯 v2 新会话不可见(0.1.3 适配)。
5
8
  //
6
9
  // 解码使用 Node 内置 node:zlib 的 zstd 支持(Node >= 22.19),零第三方依赖;
7
10
  // 帧扫描算法借鉴自 @deepseek-ai/dsh-session-persistence-jsonl (MIT)。
@@ -246,8 +249,12 @@ export function summarizeParsed({ header, title, events }) {
246
249
  }
247
250
  }
248
251
 
249
- /** 定位一个会话目录里的存档文件(优先 zstd,回退明文)。 */
252
+ /** 定位一个会话目录里的存档文件(优先 v2,回退 v1 zstd,再回退明文)。
253
+ * dsh v0.1.3 起 v2 存档与 v1 并存:v1 冻结在迁移时刻、v2 持续写入——
254
+ * 必须优先 v2,否则老会话读到冻结数据、纯 v2 新会话直接漏掉(0.1.3 适配)。 */
250
255
  export function sessionFileOf(sessionDir) {
256
+ const v2 = join(sessionDir, 'session.v2.jsonl.zstd')
257
+ if (existsSync(v2)) return v2
251
258
  const zstd = join(sessionDir, 'session.jsonl.zstd')
252
259
  if (existsSync(zstd)) return zstd
253
260
  const plain = join(sessionDir, 'session.jsonl')
package/lib/client.js CHANGED
@@ -47,7 +47,7 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
47
47
  dockPinNotice: '📌 直播已钉住原会话(当前已切换会话)', dockPinRebind: '转播当前会话',
48
48
  rename: '重命名', renamePlaceholder: '输入新标题…', renameFail: '重命名失败', origTitle: '原名',
49
49
  search: '搜索标题/工具/文件… 支持 tool: pwsh · file: src · err:', allWorkspaces: '全部工作区',
50
- sortRecent: '最近运行', sortNewest: '最近创建', sortOldest: '最早创建', sortTools: '工具最多', sortLongest: '耗时最长',
50
+ sortRecent: '最近运行', sortNewest: '最近创建', sortOldest: '最早创建', sortTools: '工具最多', sortLongest: '耗时最长', sortSize: '占用最大',
51
51
  noMatch: '没有匹配的会话', noData: '暂无会话数据',
52
52
  running: '进行中', ended: '已结束', subagent: '子代理',
53
53
  turns: '回合', steps: '步骤', tools: '工具', errors: '错误', msgs: '消息',
@@ -68,7 +68,7 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
68
68
  emptyHint: '此会话已创建但尚未开始对话', userNav: '用户发言', noUserNav: '(无用户发言)',
69
69
  conclusion: '结论', injected: '注入', lineage: '血缘', noLineage: '(无子代理血缘信息)', task: '任务', liveTree: '运行时血缘', offlineTree: '档案血缘', modeOneShot: '一次性', modeContinuable: '可续',
70
70
  subagentDetail: '子代理详情', loadDetail: '加载子代理详情…', noDetail: '该子代理无可见事件',
71
- viewSubagent: '查看子代理', toolTop: '工具 Top', issues: '问题会话', issuesHint: '只显示有错误记录的会话',
71
+ viewSubagent: '查看子代理', toolTop: '工具 Top', issues: '问题会话', issuesHint: '只显示有错误记录的会话', storage: '存储', storageHint: '当前范围会话存档总占用(只读)',
72
72
  searchTab: '检索', searchInPlaceholder: '会话内检索…', noMatches: '无匹配位置', matches: '命中', navUsers: '用户',
73
73
  cache: '缓存管理', cacheTotal: '缓存总量', cacheIndex: '会话索引', cacheTimeline: '时间线缓存',
74
74
  cleanTimeline: '清理时间线缓存', cleanAll: '清理全部缓存', cacheDone: '已清理', cacheEmpty: '(无缓存文件)',
@@ -108,7 +108,7 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
108
108
  dockPinNotice: '📌 Live pinned to previous session (session switched)', dockPinRebind: 'Follow current session',
109
109
  rename: 'Rename', renamePlaceholder: 'New title…', renameFail: 'Rename failed', origTitle: 'Original',
110
110
  search: 'Search title / tools / files… use tool: · file: · err:', allWorkspaces: 'All workspaces',
111
- sortRecent: 'Recently run', sortNewest: 'Recently created', sortOldest: 'Oldest first', sortTools: 'Most tools', sortLongest: 'Longest',
111
+ sortRecent: 'Recently run', sortNewest: 'Recently created', sortOldest: 'Oldest first', sortTools: 'Most tools', sortLongest: 'Longest', sortSize: 'Largest',
112
112
  noMatch: 'No matching sessions', noData: 'No session data yet',
113
113
  running: 'Running', ended: 'Ended', subagent: 'subagent',
114
114
  turns: 'turns', steps: 'steps', tools: 'tools', errors: 'errors', msgs: 'msgs',
@@ -130,7 +130,7 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
130
130
  conclusion: 'Conclusion', injected: 'Injected', lineage: 'Lineage', noLineage: '(no subagent lineage)', task: 'Task',
131
131
  liveTree: 'Live lineage', offlineTree: 'Archive lineage', modeOneShot: 'one-shot', modeContinuable: 'continuable',
132
132
  subagentDetail: 'Subagent detail', loadDetail: 'Loading subagent detail…', noDetail: 'No visible events for this subagent',
133
- viewSubagent: 'View subagent', toolTop: 'Tool Top', issues: 'Problem sessions', issuesHint: 'Only sessions with errors',
133
+ viewSubagent: 'View subagent', toolTop: 'Tool Top', issues: 'Problem sessions', issuesHint: 'Only sessions with errors', storage: 'Storage', storageHint: 'Total archive size of sessions in scope (read-only)',
134
134
  searchTab: 'Search', searchInPlaceholder: 'Search in session…', noMatches: 'No matches', matches: 'matches', navUsers: 'User',
135
135
  cache: 'Cache Mgmt', cacheTotal: 'Cache total', cacheIndex: 'Index', cacheTimeline: 'Timeline cache',
136
136
  cleanTimeline: 'Clean timeline cache', cleanAll: 'Clean all cache', cacheDone: 'Cleaned', cacheEmpty: '(no cache files)',
@@ -1398,6 +1398,7 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
1398
1398
  if (sort === 'oldest') return r.createdAt || 0
1399
1399
  if (sort === 'tools') return r.toolCalls || 0
1400
1400
  if (sort === 'longest') return (r.lastEventTime || 0) - (r.createdAt || 0)
1401
+ if (sort === 'size') return r.sizeBytes || 0
1401
1402
  return 0
1402
1403
  }
1403
1404
  const ascending = sort === 'oldest'
@@ -1410,17 +1411,19 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
1410
1411
  const toolAgg = new Map()
1411
1412
  let issues = 0
1412
1413
  let roots = 0
1414
+ let totalSize = 0
1413
1415
  for (const g of (state.data && state.data.workspaces) || []) {
1414
1416
  if (wsFilter !== '' && wsFilter !== g.name) continue // 统计范围 = 当前选中的工作区
1415
1417
  for (const s of g.sessions) {
1416
1418
  if (s.parentSession) continue
1417
1419
  roots++
1418
1420
  if (s.toolErrors > 0) issues++
1421
+ totalSize += s.sizeBytes || 0
1419
1422
  for (const n of s.toolNames || []) toolAgg.set(n, (toolAgg.get(n) || 0) + 1)
1420
1423
  }
1421
1424
  }
1422
1425
  const topTools = [...toolAgg.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6)
1423
- return { topTools, issues, roots }
1426
+ return { topTools, issues, roots, totalSize }
1424
1427
  }, [state.data, wsFilter])
1425
1428
 
1426
1429
  const wsNames = ((state.data && state.data.workspaces) || []).map((g) => g.name)
@@ -1445,6 +1448,7 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
1445
1448
  h('option', { value: 'oldest' }, STR.sortOldest),
1446
1449
  h('option', { value: 'tools' }, STR.sortTools),
1447
1450
  h('option', { value: 'longest' }, STR.sortLongest),
1451
+ h('option', { value: 'size' }, STR.sortSize),
1448
1452
  ),
1449
1453
  h('label', { className: 'sf-muted', style: { display: 'inline-flex', alignItems: 'center', gap: 4, cursor: 'pointer', whiteSpace: 'nowrap' } },
1450
1454
  h('input', { type: 'checkbox', checked: showEmpty, onChange: (e) => setShowEmpty(e.target.checked) }),
@@ -1531,6 +1535,10 @@ window.__ModuleLoader__.load({ id: 'dsh-session-flow', factory: (require) => {
1531
1535
  h('span', {}, '⚠ ' + STR.issues),
1532
1536
  h('span', { className: 'sf-statCount err' }, stats.issues),
1533
1537
  ),
1538
+ h('span', { className: 'sf-statChip', title: STR.storageHint },
1539
+ h('span', {}, STR.storage),
1540
+ h('span', { className: 'sf-statCount' }, fmtSize(stats.totalSize)),
1541
+ ),
1534
1542
  ),
1535
1543
  state.phase === 'loading' && h('div', { className: 'sf-hint' }, STR.scanning),
1536
1544
  state.phase === 'error' && h('div', { className: 'sf-hint', style: { color: '#d43b3b' } }, STR.loadFailed + ': ' + String(state.error)),
@@ -5,7 +5,7 @@
5
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs'
6
6
  import { join } from 'node:path'
7
7
  import { homedir } from 'node:os'
8
- import { listSessionDirs, summarizeSessionFile } from './archive.js'
8
+ import { listSessionDirs, sessionFileOf, summarizeSessionFile } from './archive.js'
9
9
 
10
10
  export function dshHome() {
11
11
  return process.env.DSH_HOME || join(homedir(), '.dsh')
@@ -22,8 +22,10 @@ export function workspaceIndexFile(home, wsName) {
22
22
  return join(indexRoot(home), `index-${safe}.json`)
23
23
  }
24
24
 
25
- /** 索引格式版本:升级时旧缓存会被自动全量重扫一次(避免字段缺失)。 */
26
- const INDEX_VERSION = 4
25
+ /** 索引格式版本:升级时旧缓存会被自动全量重扫一次(避免字段缺失)。
26
+ * 5(2026-09-08):dsh v0.1.3 存档 v2 布局适配——存档文件改为 session.v2.jsonl.zstd
27
+ * (v1 冻结),强制全量重扫让摘要/统计全部来自 v2。 */
28
+ const INDEX_VERSION = 5
27
29
 
28
30
  export function readIndex(home, wsName) {
29
31
  try {
@@ -107,7 +109,9 @@ export function scanWorkspaceIndex(home, wsName, options = {}) {
107
109
  return { index, scanned, skipped, removed }
108
110
  }
109
111
 
110
- /** 按会话 id 在所有工作区中定位存档(返回 { workspace, dir, file } 或 null)。 */
112
+ /** 按会话 id 在所有工作区中定位存档(返回 { workspace, dir, file } 或 null)。
113
+ * 文件定位统一走 sessionFileOf(v2 优先,0.1.3 适配——此前硬编码 v1 文件名,
114
+ * 纯 v2 新会话全部 404「not found in archives」,钢琴条/详情/检索静默失效)。 */
111
115
  export function findWorkspaceOfSession(home, sessionId) {
112
116
  const root = join(home, 'sessions')
113
117
  if (!existsSync(root)) return null
@@ -115,10 +119,8 @@ export function findWorkspaceOfSession(home, sessionId) {
115
119
  const wsDir = join(root, wsName)
116
120
  try { if (!statSync(wsDir).isDirectory()) continue } catch { continue }
117
121
  const dir = join(wsDir, sessionId)
118
- const zstd = join(dir, 'session.jsonl.zstd')
119
- if (existsSync(zstd)) return { workspace: wsName, dir, file: zstd }
120
- const plain = join(dir, 'session.jsonl')
121
- if (existsSync(plain)) return { workspace: wsName, dir, file: plain }
122
+ const file = sessionFileOf(dir)
123
+ if (file !== null) return { workspace: wsName, dir, file }
122
124
  }
123
125
  return null
124
126
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-session-flow",
3
3
  "description": "DSH 会话回顾与归档插件:可折叠信息流 + 会话级汇总——总览工作台、折叠时间线、血缘树、双模式摘要、全文检索、ZIP 导出、实时跟踪、会话重命名(官方数据源对齐)、健康监控",
4
- "version": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "type": "module",
6
6
  "main": "lib/host.js",
7
7
  "exports": {