dsh-session-flow 0.1.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/LICENSE +201 -0
- package/README.en.md +126 -0
- package/README.md +126 -0
- package/cordis.patch.yml +7 -0
- package/lib/archive.js +272 -0
- package/lib/client.js +1913 -0
- package/lib/host.js +1132 -0
- package/lib/index-store.js +124 -0
- package/lib/timeline.js +257 -0
- package/package.json +51 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// lib/index-store.js — 会话索引缓存:增量扫描(按 mtime+size 跳过未变文件)+ 落盘缓存。
|
|
2
|
+
//
|
|
3
|
+
// 缓存位置:<dshHome>/session-flow/index-<workspace>.json
|
|
4
|
+
// 格式:{ version, scannedAt, sessions: { [sessionId]: <summarizeParsed 结果> } }
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { homedir } from 'node:os'
|
|
8
|
+
import { listSessionDirs, summarizeSessionFile } from './archive.js'
|
|
9
|
+
|
|
10
|
+
export function dshHome() {
|
|
11
|
+
return process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function indexRoot(home = dshHome()) {
|
|
15
|
+
// DSH_SESSION_FLOW_INDEX_DIR 可覆盖索引落盘位置(开发/测试用,生产默认 <home>/session-flow)。
|
|
16
|
+
return process.env.DSH_SESSION_FLOW_INDEX_DIR || join(home, 'session-flow')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 工作区名 → 安全缓存文件名。 */
|
|
20
|
+
export function workspaceIndexFile(home, wsName) {
|
|
21
|
+
const safe = String(wsName).replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 120)
|
|
22
|
+
return join(indexRoot(home), `index-${safe}.json`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 索引格式版本:升级时旧缓存会被自动全量重扫一次(避免字段缺失)。 */
|
|
26
|
+
const INDEX_VERSION = 3
|
|
27
|
+
|
|
28
|
+
export function readIndex(home, wsName) {
|
|
29
|
+
try {
|
|
30
|
+
const raw = JSON.parse(readFileSync(workspaceIndexFile(home, wsName), 'utf8'))
|
|
31
|
+
if (raw && typeof raw === 'object' && raw.sessions && typeof raw.sessions === 'object') {
|
|
32
|
+
return { version: raw.version || 1, scannedAt: raw.scannedAt || 0, sessions: raw.sessions }
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
return { version: 1, scannedAt: 0, sessions: {} }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function writeIndex(home, wsName, index) {
|
|
39
|
+
const dir = indexRoot(home)
|
|
40
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
41
|
+
const file = workspaceIndexFile(home, wsName)
|
|
42
|
+
const tmp = `${file}.tmp`
|
|
43
|
+
writeFileSync(tmp, JSON.stringify(index, null, 2))
|
|
44
|
+
renameSync(tmp, file)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 增量扫描一个工作区的会话索引。
|
|
49
|
+
* @param {string} home - dsh home 目录。
|
|
50
|
+
* @param {string} wsName - 工作区目录名(sessions/ 下的目录名)。
|
|
51
|
+
* @param {{ force?: boolean }} options - force 时忽略缓存全量重扫。
|
|
52
|
+
* @returns {{ index, scanned, skipped, removed }}
|
|
53
|
+
*/
|
|
54
|
+
export function scanWorkspaceIndex(home, wsName, options = {}) {
|
|
55
|
+
const index = readIndex(home, wsName)
|
|
56
|
+
// 索引格式升级:旧版本缓存缺新字段(如 toolNames),必须全量重扫一次。
|
|
57
|
+
const force = Boolean(options && options.force) || index.version < INDEX_VERSION
|
|
58
|
+
let scanned = 0
|
|
59
|
+
let skipped = 0
|
|
60
|
+
const removed = []
|
|
61
|
+
const seen = new Set()
|
|
62
|
+
|
|
63
|
+
const dirs = listSessionDirs(join(home, 'sessions', wsName))
|
|
64
|
+
for (const entry of dirs) {
|
|
65
|
+
seen.add(entry.id)
|
|
66
|
+
const cached = index.sessions[entry.id]
|
|
67
|
+
if (!force && cached && cached.fileMtimeMs === entry.mtimeMs && cached.sizeBytes === entry.sizeBytes) {
|
|
68
|
+
skipped++
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const { summary } = summarizeSessionFile(entry.file, { mtimeMs: entry.mtimeMs, sizeBytes: entry.sizeBytes })
|
|
73
|
+
// 保留旧索引中的 LLM 摘要及其生成基线(会话有新对话时由
|
|
74
|
+
// summaryLastEventTime 与当前 lastEventTime 对比判定过期):
|
|
75
|
+
// 解析结果整体覆盖会丢掉已落盘的摘要,导致「无新对话也需重新生成」。
|
|
76
|
+
index.sessions[entry.id] = {
|
|
77
|
+
...summary,
|
|
78
|
+
summary: (cached && cached.summary) || null,
|
|
79
|
+
summaryLastEventTime: (cached && cached.summaryLastEventTime) || null,
|
|
80
|
+
}
|
|
81
|
+
scanned++
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// 单个会话解析失败不阻塞整个工作区(torn 文件等):保留旧缓存并记录错误。
|
|
84
|
+
const reason = String(error && error.message || error)
|
|
85
|
+
index.sessions[entry.id] = {
|
|
86
|
+
id: entry.id,
|
|
87
|
+
fileMtimeMs: entry.mtimeMs,
|
|
88
|
+
sizeBytes: entry.sizeBytes,
|
|
89
|
+
parseError: reason,
|
|
90
|
+
recordCount: cached && cached.recordCount || 0,
|
|
91
|
+
...(cached || {}),
|
|
92
|
+
fileMtimeMs: entry.mtimeMs,
|
|
93
|
+
sizeBytes: entry.sizeBytes,
|
|
94
|
+
parseError: reason,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const id of Object.keys(index.sessions)) {
|
|
99
|
+
if (!seen.has(id)) {
|
|
100
|
+
delete index.sessions[id]
|
|
101
|
+
removed.push(id)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
index.scannedAt = Date.now()
|
|
105
|
+
index.version = INDEX_VERSION
|
|
106
|
+
writeIndex(home, wsName, index)
|
|
107
|
+
return { index, scanned, skipped, removed }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 按会话 id 在所有工作区中定位存档(返回 { workspace, dir, file } 或 null)。 */
|
|
111
|
+
export function findWorkspaceOfSession(home, sessionId) {
|
|
112
|
+
const root = join(home, 'sessions')
|
|
113
|
+
if (!existsSync(root)) return null
|
|
114
|
+
for (const wsName of readdirSync(root)) {
|
|
115
|
+
const wsDir = join(root, wsName)
|
|
116
|
+
try { if (!statSync(wsDir).isDirectory()) continue } catch { continue }
|
|
117
|
+
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
|
+
}
|
|
123
|
+
return null
|
|
124
|
+
}
|
package/lib/timeline.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// lib/timeline.js — 从解析后的会话事件派生「信息流」视图:
|
|
2
|
+
// 回合(Turn) → 步骤(Step) → 工具调用(ToolCall) → 产物(Artifact)
|
|
3
|
+
// 供 M3 折叠详情页与 M5 摘要引擎共同消费。
|
|
4
|
+
import { extractPaths, parseSession } from './archive.js'
|
|
5
|
+
|
|
6
|
+
/** 子代理工具结果中的会话 ID("started subagent <uuid>")。 */
|
|
7
|
+
const SUBAGENT_ID_RE = /started subagent\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 从 tool/result 的文本内容里截取预览。
|
|
11
|
+
* 文本可能直接是顶层 text 块,也可能嵌套在 tool-result 块里
|
|
12
|
+
* (content[0].content[0].text),递归提取所有 text 字段。
|
|
13
|
+
*/
|
|
14
|
+
export function resultTextOf(resultEvent) {
|
|
15
|
+
const blocks = resultEvent && resultEvent.data && resultEvent.data.message && resultEvent.data.message.content
|
|
16
|
+
if (!Array.isArray(blocks)) return ''
|
|
17
|
+
const texts = []
|
|
18
|
+
const walk = (b) => {
|
|
19
|
+
if (b === null || typeof b !== 'object') return
|
|
20
|
+
if (typeof b.text === 'string' && b.text.length > 0) texts.push(b.text)
|
|
21
|
+
if (Array.isArray(b.content)) {
|
|
22
|
+
for (const c of b.content) walk(c)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const b of blocks) walk(b)
|
|
26
|
+
return texts.join('\n').trim()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const preview = (text, max = 400) => {
|
|
30
|
+
const t = String(text || '')
|
|
31
|
+
return t.length > max ? t.slice(0, max) + '…' : t
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 事件文本内容(user/assistant 消息的 text 块拼接预览)。 */
|
|
35
|
+
export function contentPreviewOf(content) {
|
|
36
|
+
if (!Array.isArray(content)) return ''
|
|
37
|
+
return content
|
|
38
|
+
.map((b) => {
|
|
39
|
+
if (b && b.type === 'text' && typeof b.text === 'string') return b.text
|
|
40
|
+
if (b && b.type === 'reasoning' && typeof b.text === 'string') return '[思考] ' + b.text
|
|
41
|
+
if (b && b.type === 'tool-result') return '[工具结果]'
|
|
42
|
+
return ''
|
|
43
|
+
})
|
|
44
|
+
.join('\n')
|
|
45
|
+
.trim()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 提取 assistant 消息中的思考块(reasoning)全文。 */
|
|
49
|
+
export function thinkingTextOf(content) {
|
|
50
|
+
if (!Array.isArray(content)) return ''
|
|
51
|
+
return content
|
|
52
|
+
.filter((b) => b && b.type === 'reasoning' && typeof b.text === 'string')
|
|
53
|
+
.map((b) => b.text)
|
|
54
|
+
.join('\n')
|
|
55
|
+
.trim()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 提取 assistant 消息中的正文文本块(text,不含思考)——即「最终结论」内容。 */
|
|
59
|
+
export function textTextOf(content) {
|
|
60
|
+
if (!Array.isArray(content)) return ''
|
|
61
|
+
return content
|
|
62
|
+
.filter((b) => b && b.type === 'text' && typeof b.text === 'string')
|
|
63
|
+
.map((b) => b.text)
|
|
64
|
+
.join('\n')
|
|
65
|
+
.trim()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 从解析结果派生时间线。
|
|
70
|
+
* @param {{ header, title, events }} parsed - parseSession 的输出。
|
|
71
|
+
* @returns {{ turns, artifacts, summary }} turns 按顺序;artifacts 为全局产物路径集合。
|
|
72
|
+
*/
|
|
73
|
+
export function deriveTimeline(parsed) {
|
|
74
|
+
const { events } = parsed
|
|
75
|
+
const turns = []
|
|
76
|
+
const turnMap = new Map()
|
|
77
|
+
let currentStep = null // { step, startTime, endTime, toolCalls }
|
|
78
|
+
let currentTurn = null
|
|
79
|
+
|
|
80
|
+
const ensureTurn = (turnNo) => {
|
|
81
|
+
let t = turnMap.get(turnNo)
|
|
82
|
+
if (t === undefined) {
|
|
83
|
+
t = { turn: turnNo, startTime: null, endTime: null, userMessages: [], injectedMessages: [], assistantMessages: [], steps: [], items: [] }
|
|
84
|
+
turnMap.set(turnNo, t)
|
|
85
|
+
turns.push(t)
|
|
86
|
+
}
|
|
87
|
+
return t
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const ev of events) {
|
|
91
|
+
switch (ev.type) {
|
|
92
|
+
case 'turn/start': {
|
|
93
|
+
currentTurn = ensureTurn(ev.data && ev.data.turn)
|
|
94
|
+
currentTurn.startTime = currentTurn.startTime ?? ev.time
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
case 'turn/end': {
|
|
98
|
+
// 记录回合闭合时间:未收到 turn/end 的回合保持 endTime=null → 运行中信号。
|
|
99
|
+
if (currentTurn && currentTurn.turn === (ev.data && ev.data.turn)) currentTurn.endTime = ev.time
|
|
100
|
+
else {
|
|
101
|
+
const t = turnMap.get(ev.data && ev.data.turn)
|
|
102
|
+
if (t) t.endTime = ev.time
|
|
103
|
+
}
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
case 'step/start': {
|
|
107
|
+
currentTurn = currentTurn || ensureTurn(ev.data && ev.data.turn)
|
|
108
|
+
const stepNo = ev.data && ev.data.step
|
|
109
|
+
currentStep = { step: stepNo, startTime: ev.time, endTime: null, toolCalls: [] }
|
|
110
|
+
currentTurn.steps.push(currentStep)
|
|
111
|
+
// 时间序:步骤头(渲染时在所属工具调用前显示)。
|
|
112
|
+
currentTurn.items.push({ kind: 'step', seq: ev.seq, time: ev.time, step: stepNo })
|
|
113
|
+
break
|
|
114
|
+
}
|
|
115
|
+
case 'step/end': {
|
|
116
|
+
if (currentStep && currentStep.step === (ev.data && ev.data.step)) currentStep.endTime = ev.time
|
|
117
|
+
break
|
|
118
|
+
}
|
|
119
|
+
case 'user/message': {
|
|
120
|
+
currentTurn = currentTurn || ensureTurn(ev.data && ev.data.turn)
|
|
121
|
+
const text = contentPreviewOf(ev.data && ev.data.content)
|
|
122
|
+
// 来源分类:只有 source.kind === 'user' 才是真正的用户发言;
|
|
123
|
+
// plugin / goal / skill-catalog 等是智能体/插件注入的信息,单独归类。
|
|
124
|
+
const sourceKind = ev.data && ev.data.source ? String(ev.data.source.kind || '') : ''
|
|
125
|
+
const isInjected = sourceKind !== '' && sourceKind !== 'user'
|
|
126
|
+
const entry = { seq: ev.seq, time: ev.time, text, preview: preview(text), sourceKind: sourceKind || null }
|
|
127
|
+
if (isInjected) currentTurn.injectedMessages.push(entry)
|
|
128
|
+
else currentTurn.userMessages.push(entry)
|
|
129
|
+
// 时间序:用户发言(kind: user)或注入信息(kind: inject,保持真实位置)。
|
|
130
|
+
currentTurn.items.push({ kind: isInjected ? 'inject' : 'user', ...entry })
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
case 'assistant/message': {
|
|
134
|
+
currentTurn = currentTurn || ensureTurn(ev.data && ev.data.turn)
|
|
135
|
+
const content = ev.data && ev.data.message && ev.data.message.content
|
|
136
|
+
const thinking = thinkingTextOf(content)
|
|
137
|
+
const text = textTextOf(content)
|
|
138
|
+
currentTurn.assistantMessages.push({
|
|
139
|
+
seq: ev.seq, time: ev.time,
|
|
140
|
+
turn: ev.data && ev.data.turn, step: ev.data && ev.data.step,
|
|
141
|
+
thinking, thinkingPreview: preview(thinking, 800),
|
|
142
|
+
text, preview: preview(text, 1200),
|
|
143
|
+
hasThinking: thinking.length > 0,
|
|
144
|
+
hasText: text.length > 0,
|
|
145
|
+
})
|
|
146
|
+
// 时间序:助手消息(思考 + 正文结论,与工具调用真实交织)。
|
|
147
|
+
currentTurn.items.push({
|
|
148
|
+
kind: 'assistant', seq: ev.seq, time: ev.time,
|
|
149
|
+
thinking, thinkingPreview: preview(thinking, 800),
|
|
150
|
+
text, preview: preview(text, 1200),
|
|
151
|
+
hasThinking: thinking.length > 0,
|
|
152
|
+
hasText: text.length > 0,
|
|
153
|
+
})
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
case 'tool/call': {
|
|
157
|
+
const d = ev.data || {}
|
|
158
|
+
currentTurn = currentTurn || ensureTurn(d.turn)
|
|
159
|
+
let step = currentStep
|
|
160
|
+
if (!step || step.step !== d.step) {
|
|
161
|
+
step = currentTurn.steps.find((s) => s.step === d.step) || null
|
|
162
|
+
if (step === null && currentTurn.steps.length > 0) step = currentTurn.steps[currentTurn.steps.length - 1]
|
|
163
|
+
}
|
|
164
|
+
if (step === null) {
|
|
165
|
+
step = { step: d.step, startTime: ev.time, endTime: null, toolCalls: [] }
|
|
166
|
+
currentTurn.steps.push(step)
|
|
167
|
+
currentStep = step
|
|
168
|
+
}
|
|
169
|
+
step.toolCalls.push({
|
|
170
|
+
seq: ev.seq, time: ev.time,
|
|
171
|
+
callId: d.callId, name: d.name,
|
|
172
|
+
argumentsText: String(d.arguments || ''),
|
|
173
|
+
argumentsPreview: preview(String(d.arguments || ''), 300),
|
|
174
|
+
resultTime: null, isError: null, resultText: '', resultPreview: '',
|
|
175
|
+
durationMs: null,
|
|
176
|
+
artifacts: [...extractPaths(d.name, d.arguments)],
|
|
177
|
+
})
|
|
178
|
+
// 时间序:工具调用行(引用 step.toolCalls 里的完整对象,渲染时取用)。
|
|
179
|
+
currentTurn.items.push({
|
|
180
|
+
kind: 'tool', seq: ev.seq, time: ev.time, step: d.step,
|
|
181
|
+
call: step.toolCalls[step.toolCalls.length - 1],
|
|
182
|
+
})
|
|
183
|
+
break
|
|
184
|
+
}
|
|
185
|
+
case 'tool/result': {
|
|
186
|
+
const d = ev.data || {}
|
|
187
|
+
const callId = d.message && d.message.source && d.message.source.callId
|
|
188
|
+
if (callId) {
|
|
189
|
+
// 在当前回合内找最近的匹配工具调用(callId 或 seq 回链)。
|
|
190
|
+
let target = null
|
|
191
|
+
const targets = (currentTurn && currentTurn.steps) || []
|
|
192
|
+
for (const step of targets) {
|
|
193
|
+
for (const tc of step.toolCalls) {
|
|
194
|
+
if (tc.callId === callId) { target = tc; break }
|
|
195
|
+
}
|
|
196
|
+
if (target) break
|
|
197
|
+
}
|
|
198
|
+
if (!target) {
|
|
199
|
+
// 兜底:全量扫描已见调用。
|
|
200
|
+
outer:
|
|
201
|
+
for (const t of turns) {
|
|
202
|
+
for (const step of t.steps) {
|
|
203
|
+
for (const tc of step.toolCalls) {
|
|
204
|
+
if (tc.callId === callId) { target = tc; break outer }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (target) {
|
|
210
|
+
target.resultTime = ev.time
|
|
211
|
+
target.resultText = resultTextOf(ev)
|
|
212
|
+
target.resultPreview = preview(target.resultText, 400)
|
|
213
|
+
target.isError = Array.isArray(d.message && d.message.content)
|
|
214
|
+
? d.message.content.some((b) => b && b.isError === true)
|
|
215
|
+
: false
|
|
216
|
+
target.durationMs = target.resultTime !== null && target.time !== null ? target.resultTime - target.time : null
|
|
217
|
+
// 子代理工具:结果文本 "started subagent <uuid>" → 关联子代理会话 ID,
|
|
218
|
+
// 前端可一键跳转查看该子代理的实际执行内容(而非只显示这行摘要)。
|
|
219
|
+
// 仅对子代理类工具名提取,避免普通工具输出里的同文本误匹配。
|
|
220
|
+
if (/^subagent/i.test(target.name)) {
|
|
221
|
+
const m = SUBAGENT_ID_RE.exec(target.resultText)
|
|
222
|
+
if (m) target.childSessionId = m[1]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
break
|
|
227
|
+
}
|
|
228
|
+
default:
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const t of turns) {
|
|
234
|
+
// 回合闭合时间优先取 turn/end 事件;未收到 turn/end 时回退到「最后完成的步骤」
|
|
235
|
+
// (step/end 全部出现 ≈ 回合完成;若步骤也未闭合则保持 null → 运行中信号)。
|
|
236
|
+
if (t.endTime === null) {
|
|
237
|
+
t.endTime = t.steps.reduce((acc, s) => (s.endTime !== null && s.endTime > acc ? s.endTime : acc), t.startTime)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const artifacts = []
|
|
242
|
+
const seenArtifacts = new Set()
|
|
243
|
+
for (const t of turns) {
|
|
244
|
+
for (const step of t.steps) {
|
|
245
|
+
for (const tc of step.toolCalls) {
|
|
246
|
+
for (const p of tc.artifacts || []) {
|
|
247
|
+
if (!seenArtifacts.has(p)) {
|
|
248
|
+
seenArtifacts.add(p)
|
|
249
|
+
artifacts.push({ path: p, firstSeen: { turn: t.turn, step: step.step, callId: tc.callId } })
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { turns, artifacts }
|
|
257
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-session-flow",
|
|
3
|
+
"description": "会话信息流重设计插件:折叠汇总 + 会话总览归档 + 深链跳转 + 子代理血缘树 + 摘要引擎(规则/LLM 双模式),实时复用原生轨迹数据",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/host.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/host.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"lib/",
|
|
15
|
+
"cordis.patch.yml",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.en.md"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"dsh",
|
|
21
|
+
"agent-harness",
|
|
22
|
+
"plugin",
|
|
23
|
+
"web",
|
|
24
|
+
"ui",
|
|
25
|
+
"session",
|
|
26
|
+
"trajectory"
|
|
27
|
+
],
|
|
28
|
+
"license": "Apache-2.0",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/YeqingTang/dsh-session-flow.git"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dsh": {
|
|
37
|
+
"bundle": {
|
|
38
|
+
"patch": "./cordis.patch.yml"
|
|
39
|
+
},
|
|
40
|
+
"client": {
|
|
41
|
+
"platform": "web"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": "^18.2.0",
|
|
46
|
+
"react-dom": "^18.2.0",
|
|
47
|
+
"@deepseek-ai/cordis": "*",
|
|
48
|
+
"@deepseek-ai/dsh-client-runtime": "*",
|
|
49
|
+
"@deepseek-ai/dsh-client-connection": "*"
|
|
50
|
+
}
|
|
51
|
+
}
|