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 +9 -2
- package/lib/client.js +13 -5
- package/lib/index-store.js +10 -8
- package/package.json +1 -1
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)),
|
package/lib/index-store.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
119
|
-
if (
|
|
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