@p-dsh-market/conversation-knowledge-map 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/README.md +22 -0
- package/cordis.patch.yml +4 -0
- package/lib/client.js +575 -0
- package/lib/generation-orchestrator.js +401 -0
- package/lib/index.js +286 -0
- package/lib/knowledge-graph-schema.js +89 -0
- package/lib/mind-map-schema.js +94 -0
- package/lib/protocol.js +104 -0
- package/lib/session-source.js +188 -0
- package/lib/workspace-storage.js +224 -0
- package/package.json +74 -0
- package/skills/conversation-knowledge-map/SKILL.md +13 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { errorMessage, eventMessageText, shortText } from './protocol.js'
|
|
4
|
+
|
|
5
|
+
const WINDOWS_PATH = process.platform === 'win32'
|
|
6
|
+
|
|
7
|
+
export function normalizeWorkspacePath(value) {
|
|
8
|
+
const raw = String(value || '').trim()
|
|
9
|
+
if (!raw) return ''
|
|
10
|
+
const absolute = path.resolve(raw)
|
|
11
|
+
const normalized = path.normalize(absolute)
|
|
12
|
+
const root = path.parse(normalized).root
|
|
13
|
+
return normalized.length > root.length ? normalized.replace(/[\\/]$/, '') : normalized
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sameWorkspacePath(left, right) {
|
|
17
|
+
const a = normalizeWorkspacePath(left)
|
|
18
|
+
const b = normalizeWorkspacePath(right)
|
|
19
|
+
if (!a || !b) return false
|
|
20
|
+
return WINDOWS_PATH ? a.toLowerCase() === b.toLowerCase() : a === b
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function headerOf(record) {
|
|
24
|
+
return record?.header || record?.session || record || null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function titleOf(value) {
|
|
28
|
+
const title = value?.title ?? value?.value?.title ?? value
|
|
29
|
+
if (typeof title === 'string') return shortText(title, 160)
|
|
30
|
+
if (typeof title?.title === 'string') return shortText(title.title, 160)
|
|
31
|
+
if (typeof title?.text === 'string') return shortText(title.text, 160)
|
|
32
|
+
return ''
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function titleResultsById(results) {
|
|
36
|
+
const map = new Map()
|
|
37
|
+
for (const item of results || []) {
|
|
38
|
+
if (item?.status === 'fulfilled') map.set(String(item.sessionId), titleOf(item.value))
|
|
39
|
+
}
|
|
40
|
+
return map
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function sessionRecordView(record, title = '', currentSessionId = '') {
|
|
44
|
+
const header = headerOf(record)
|
|
45
|
+
const id = String(header?.id || record?.id || '')
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
title: title || `未命名对话 ${id.slice(0, 8)}`,
|
|
49
|
+
createdAt: Number(header?.createdAt || 0),
|
|
50
|
+
cwd: String(header?.cwd || ''),
|
|
51
|
+
parentSession: String(header?.parentSession || ''),
|
|
52
|
+
origin: String(header?.origin || ''),
|
|
53
|
+
live: record?.live !== false,
|
|
54
|
+
persisted: record?.persisted !== false,
|
|
55
|
+
current: id === currentSessionId
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function resolveAnchorSession({ sessionQuery, sessions }, sessionId) {
|
|
60
|
+
const id = String(sessionId || '').trim()
|
|
61
|
+
if (!id || id === 'active') return null
|
|
62
|
+
const live = sessions?.get?.(id)
|
|
63
|
+
if (live?.header?.id || live?.id === id) {
|
|
64
|
+
return { header: live.header || live, live: true, persisted: true }
|
|
65
|
+
}
|
|
66
|
+
if (typeof sessionQuery?.filterSessions === 'function') {
|
|
67
|
+
const records = await sessionQuery.filterSessions([{ kind: 'id', values: [id] }])
|
|
68
|
+
return records?.find((record) => String(headerOf(record)?.id || '') === id) || null
|
|
69
|
+
}
|
|
70
|
+
if (typeof sessionQuery?.listSessions === 'function') {
|
|
71
|
+
const records = await sessionQuery.listSessions()
|
|
72
|
+
return records?.find((record) => String(headerOf(record)?.id || '') === id) || null
|
|
73
|
+
}
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function listWorkspaceSessions({ sessionQuery, sessions }, cwd, currentSessionId = '', includeSubagents = false) {
|
|
78
|
+
const normalizedCwd = normalizeWorkspacePath(cwd)
|
|
79
|
+
if (!normalizedCwd) return []
|
|
80
|
+
let records = []
|
|
81
|
+
if (typeof sessionQuery?.filterSessions === 'function') {
|
|
82
|
+
records = await sessionQuery.filterSessions([{ kind: 'cwd', values: [cwd] }])
|
|
83
|
+
} else if (typeof sessionQuery?.listSessions === 'function') {
|
|
84
|
+
records = await sessionQuery.listSessions()
|
|
85
|
+
} else if (typeof sessions?.list === 'function') {
|
|
86
|
+
records = await sessions.list()
|
|
87
|
+
}
|
|
88
|
+
records = (records || []).filter((record) => {
|
|
89
|
+
const header = headerOf(record)
|
|
90
|
+
return sameWorkspacePath(header?.cwd, normalizedCwd) && (includeSubagents || header?.origin !== 'subagent')
|
|
91
|
+
})
|
|
92
|
+
const ids = records.map((record) => String(headerOf(record)?.id || record?.id || '')).filter(Boolean)
|
|
93
|
+
let titleResults = []
|
|
94
|
+
if (typeof sessionQuery?.readTitleSnapshots === 'function' && ids.length) {
|
|
95
|
+
titleResults = await sessionQuery.readTitleSnapshots(ids)
|
|
96
|
+
}
|
|
97
|
+
const titles = titleResultsById(titleResults)
|
|
98
|
+
return records
|
|
99
|
+
.map((record) => sessionRecordView(record, titles.get(String(headerOf(record)?.id || '')), currentSessionId))
|
|
100
|
+
.filter((record) => record.id)
|
|
101
|
+
.sort((left, right) => right.createdAt - left.createdAt || left.id.localeCompare(right.id))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function eventRole(type) {
|
|
105
|
+
if (type === 'user/message') return 'user'
|
|
106
|
+
if (type === 'assistant/message') return 'assistant'
|
|
107
|
+
return ''
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function sourceSeqsOf(event) {
|
|
111
|
+
const value = event?.sourceEventSeqs || event?.data?.sourceEventSeqs || []
|
|
112
|
+
return Array.isArray(value) ? value.filter((item) => Number.isInteger(item) && item >= 0) : []
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function surfaceEventView(event) {
|
|
116
|
+
const role = eventRole(String(event?.type || ''))
|
|
117
|
+
const text = shortText(eventMessageText(event), 12000)
|
|
118
|
+
if (!role || !text) return null
|
|
119
|
+
return {
|
|
120
|
+
seq: Number.isInteger(event?.seq) ? event.seq : 0,
|
|
121
|
+
type: String(event.type),
|
|
122
|
+
role,
|
|
123
|
+
text,
|
|
124
|
+
sourceEventSeqs: sourceSeqsOf(event)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, sessionIds, includeSubagents = false }) {
|
|
129
|
+
const selected = [...new Set((sessionIds || []).map((id) => String(id || '').trim()).filter(Boolean))]
|
|
130
|
+
if (selected.length === 0) throw new Error('至少选择一个对话。')
|
|
131
|
+
const normalizedCwd = normalizeWorkspacePath(cwd)
|
|
132
|
+
if (!normalizedCwd) throw new Error('当前工作路径无效。')
|
|
133
|
+
const records = []
|
|
134
|
+
if (typeof sessionQuery?.filterSessions === 'function') {
|
|
135
|
+
records.push(...await sessionQuery.filterSessions([{ kind: 'id', values: selected }]))
|
|
136
|
+
} else if (typeof sessionQuery?.listSessions === 'function') {
|
|
137
|
+
const all = await sessionQuery.listSessions()
|
|
138
|
+
records.push(...all.filter((record) => selected.includes(String(headerOf(record)?.id || ''))))
|
|
139
|
+
}
|
|
140
|
+
const recordMap = new Map(records.map((record) => [String(headerOf(record)?.id || record?.id || ''), record]))
|
|
141
|
+
const sources = []
|
|
142
|
+
for (const id of selected) {
|
|
143
|
+
const record = recordMap.get(id)
|
|
144
|
+
const header = headerOf(record) || sessions?.get?.(id)?.header
|
|
145
|
+
if (!header?.id) throw new Error(`所选对话不存在或已不可读:${id}`)
|
|
146
|
+
if (!sameWorkspacePath(header.cwd, normalizedCwd)) throw new Error(`所选对话不属于当前工作路径:${id}`)
|
|
147
|
+
if (!includeSubagents && header.origin === 'subagent') throw new Error(`不能选择子 Agent 对话:${id}`)
|
|
148
|
+
if (typeof sessionQuery?.readSurface !== 'function') throw new Error('当前 DSH Runtime 未提供 sessionQuery.readSurface。')
|
|
149
|
+
let surface
|
|
150
|
+
try {
|
|
151
|
+
surface = await sessionQuery.readSurface(id)
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw new Error(`读取对话“${id}”失败:${errorMessage(error)}`)
|
|
154
|
+
}
|
|
155
|
+
const events = (surface?.events || []).map(surfaceEventView).filter(Boolean)
|
|
156
|
+
sources.push({
|
|
157
|
+
sessionId: id,
|
|
158
|
+
title: `对话 ${id.slice(0, 8)}`,
|
|
159
|
+
cwd: normalizeWorkspacePath(surface?.session?.cwd || header.cwd),
|
|
160
|
+
capturedThroughSeq: Number.isInteger(surface?.capturedThroughSeq) ? surface.capturedThroughSeq : null,
|
|
161
|
+
events,
|
|
162
|
+
text: events.map((event) => `${event.role === 'user' ? '用户' : '助手'}:${event.text}`).join('\n\n')
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
if (typeof sessionQuery?.readTitleSnapshots === 'function' && sources.length) {
|
|
166
|
+
const results = await sessionQuery.readTitleSnapshots(sources.map((source) => source.sessionId))
|
|
167
|
+
const titles = titleResultsById(results)
|
|
168
|
+
for (const source of sources) source.title = titles.get(source.sessionId) || source.title
|
|
169
|
+
}
|
|
170
|
+
return sources
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function chunkSourceText(source, maxChars = 9000) {
|
|
174
|
+
const text = String(source?.text || '')
|
|
175
|
+
if (!text) return [{ text: '', sourceRefs: [] }]
|
|
176
|
+
const chunks = []
|
|
177
|
+
let start = 0
|
|
178
|
+
while (start < text.length) {
|
|
179
|
+
const end = Math.min(text.length, start + maxChars)
|
|
180
|
+
const chunkText = text.slice(start, end)
|
|
181
|
+
const refs = (source.events || [])
|
|
182
|
+
.filter((event) => event.text && text.indexOf(event.text, start) >= start && text.indexOf(event.text, start) < end)
|
|
183
|
+
.map((event) => ({ sessionId: source.sessionId, eventSeqs: [event.seq] }))
|
|
184
|
+
chunks.push({ text: chunkText, sourceRefs: refs })
|
|
185
|
+
start = end
|
|
186
|
+
}
|
|
187
|
+
return chunks
|
|
188
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { randomUUID } from 'node:crypto'
|
|
4
|
+
|
|
5
|
+
import { DATA_DIR, shortText } from './protocol.js'
|
|
6
|
+
import { normalizeWorkspacePath } from './session-source.js'
|
|
7
|
+
|
|
8
|
+
const FILES = {
|
|
9
|
+
manifest: 'manifest.json',
|
|
10
|
+
mindMap: 'mind-map.json',
|
|
11
|
+
knowledgeGraph: 'knowledge-graph.json',
|
|
12
|
+
navigationHistory: 'navigation-history.json'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const WORKSPACE_SCHEMA_VERSION = 1
|
|
16
|
+
|
|
17
|
+
function compatibilityOf(manifest) {
|
|
18
|
+
if (Number(manifest?.schemaVersion) === WORKSPACE_SCHEMA_VERSION) {
|
|
19
|
+
return { supported: true, state: 'current', schemaVersion: WORKSPACE_SCHEMA_VERSION, message: '' }
|
|
20
|
+
}
|
|
21
|
+
const version = Number.isInteger(manifest?.schemaVersion) ? manifest.schemaVersion : null
|
|
22
|
+
return {
|
|
23
|
+
supported: false,
|
|
24
|
+
state: 'legacy',
|
|
25
|
+
schemaVersion: version,
|
|
26
|
+
message: '当前工作区知识视图来自旧版本 Schema,暂不直接加载;请确认后重新生成。'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isNotFound(error) {
|
|
31
|
+
return error?.code === 'ENOENT'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function readJsonIfExists(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(await readFile(filePath, 'utf8'))
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (isNotFound(error)) return null
|
|
39
|
+
throw new Error(`读取知识视图数据失败:${filePath}:${error.message}`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function writeJson(filePath, value) {
|
|
44
|
+
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function replaceBundle(dataDir, values) {
|
|
48
|
+
const id = randomUUID()
|
|
49
|
+
const stagingDir = path.join(dataDir, `.staging-${id}`)
|
|
50
|
+
const finals = Object.entries(values).map(([key, value]) => ({
|
|
51
|
+
key,
|
|
52
|
+
temp: path.join(stagingDir, FILES[key]),
|
|
53
|
+
final: path.join(dataDir, FILES[key]),
|
|
54
|
+
value
|
|
55
|
+
}))
|
|
56
|
+
const backups = []
|
|
57
|
+
const installed = []
|
|
58
|
+
await mkdir(stagingDir, { recursive: true })
|
|
59
|
+
try {
|
|
60
|
+
for (const item of finals) await writeJson(item.temp, item.value)
|
|
61
|
+
for (const item of finals) {
|
|
62
|
+
const backup = `${item.final}.backup-${id}`
|
|
63
|
+
let hadOld = false
|
|
64
|
+
try {
|
|
65
|
+
await rename(item.final, backup)
|
|
66
|
+
hadOld = true
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (!isNotFound(error)) throw error
|
|
69
|
+
}
|
|
70
|
+
backups.push({ final: item.final, backup, hadOld })
|
|
71
|
+
await rename(item.temp, item.final)
|
|
72
|
+
installed.push(item.final)
|
|
73
|
+
}
|
|
74
|
+
for (const backup of backups) if (backup.hadOld) await rm(backup.backup, { force: true })
|
|
75
|
+
await rm(stagingDir, { recursive: true, force: true })
|
|
76
|
+
} catch (error) {
|
|
77
|
+
for (const filePath of installed) await rm(filePath, { force: true }).catch(() => {})
|
|
78
|
+
for (const backup of backups) {
|
|
79
|
+
if (backup.hadOld) await rename(backup.backup, backup.final).catch(() => {})
|
|
80
|
+
}
|
|
81
|
+
await rm(stagingDir, { recursive: true, force: true }).catch(() => {})
|
|
82
|
+
throw error
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function workspaceKey(cwd) {
|
|
87
|
+
return process.platform === 'win32' ? cwd.toLowerCase() : cwd
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class WorkspaceRevisionError extends Error {
|
|
91
|
+
constructor(expected, actual) {
|
|
92
|
+
super(`工作区结果已被其他窗口更新(期望 revision ${expected},当前 revision ${actual}),请重新确认后再生成。`)
|
|
93
|
+
this.name = 'WorkspaceRevisionError'
|
|
94
|
+
this.expected = expected
|
|
95
|
+
this.actual = actual
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class WorkspaceStorage {
|
|
100
|
+
constructor({ now = () => Date.now() } = {}) {
|
|
101
|
+
this.now = now
|
|
102
|
+
this.locks = new Map()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
resolveDataDir(cwd) {
|
|
106
|
+
const normalized = normalizeWorkspacePath(cwd)
|
|
107
|
+
if (!normalized || !path.isAbsolute(normalized)) throw new Error('工作路径必须是绝对路径。')
|
|
108
|
+
const dataDir = path.resolve(normalized, DATA_DIR)
|
|
109
|
+
if (path.dirname(dataDir) !== normalized) throw new Error('工作区数据目录解析失败。')
|
|
110
|
+
return dataDir
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async readState(cwd) {
|
|
114
|
+
const dataDir = this.resolveDataDir(cwd)
|
|
115
|
+
const manifest = await readJsonIfExists(path.join(dataDir, FILES.manifest))
|
|
116
|
+
if (!manifest) return {
|
|
117
|
+
exists: false,
|
|
118
|
+
dataDir,
|
|
119
|
+
revision: 0,
|
|
120
|
+
manifest: null,
|
|
121
|
+
mindMap: null,
|
|
122
|
+
knowledgeGraph: null,
|
|
123
|
+
navigationHistory: [],
|
|
124
|
+
compatibility: { supported: true, state: 'empty', schemaVersion: WORKSPACE_SCHEMA_VERSION, message: '' }
|
|
125
|
+
}
|
|
126
|
+
const compatibility = compatibilityOf(manifest)
|
|
127
|
+
if (!compatibility.supported) return {
|
|
128
|
+
exists: true,
|
|
129
|
+
dataDir,
|
|
130
|
+
revision: Number.isInteger(manifest.revision) ? manifest.revision : 0,
|
|
131
|
+
manifest,
|
|
132
|
+
mindMap: null,
|
|
133
|
+
knowledgeGraph: null,
|
|
134
|
+
navigationHistory: [],
|
|
135
|
+
compatibility
|
|
136
|
+
}
|
|
137
|
+
const [mindMap, knowledgeGraph, navigationHistory] = await Promise.all([
|
|
138
|
+
readJsonIfExists(path.join(dataDir, FILES.mindMap)),
|
|
139
|
+
readJsonIfExists(path.join(dataDir, FILES.knowledgeGraph)),
|
|
140
|
+
readJsonIfExists(path.join(dataDir, FILES.navigationHistory))
|
|
141
|
+
])
|
|
142
|
+
return {
|
|
143
|
+
exists: true,
|
|
144
|
+
dataDir,
|
|
145
|
+
revision: Number.isInteger(manifest.revision) ? manifest.revision : 0,
|
|
146
|
+
manifest,
|
|
147
|
+
mindMap,
|
|
148
|
+
knowledgeGraph,
|
|
149
|
+
navigationHistory: Array.isArray(navigationHistory) ? navigationHistory : [],
|
|
150
|
+
compatibility
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async saveBundle({ cwd, expectedRevision = 0, generationId, sourceSessionIds, prompt, strict, outputMode, model, mindMap, knowledgeGraph }) {
|
|
155
|
+
const normalizedCwd = normalizeWorkspacePath(cwd)
|
|
156
|
+
const key = workspaceKey(normalizedCwd)
|
|
157
|
+
const previous = this.locks.get(key) || Promise.resolve()
|
|
158
|
+
const operation = previous.catch(() => {}).then(async () => {
|
|
159
|
+
const current = await this.readState(normalizedCwd)
|
|
160
|
+
if (Number(expectedRevision) !== current.revision) throw new WorkspaceRevisionError(expectedRevision, current.revision)
|
|
161
|
+
const revision = current.revision + 1
|
|
162
|
+
const manifest = {
|
|
163
|
+
schemaVersion: WORKSPACE_SCHEMA_VERSION,
|
|
164
|
+
plugin: '@p-dsh-market/conversation-knowledge-map',
|
|
165
|
+
generationId: String(generationId || ''),
|
|
166
|
+
revision,
|
|
167
|
+
cwd: normalizedCwd,
|
|
168
|
+
sourceSessionIds: [...new Set((sourceSessionIds || []).map(String))],
|
|
169
|
+
promptSummary: shortText(prompt, 500),
|
|
170
|
+
strict: strict === true,
|
|
171
|
+
outputMode: String(outputMode || 'both'),
|
|
172
|
+
model: model ? { provider: String(model.provider || ''), model: String(model.model || '') } : null,
|
|
173
|
+
generatedAt: this.now()
|
|
174
|
+
}
|
|
175
|
+
const navigationHistory = current.navigationHistory || []
|
|
176
|
+
await replaceBundle(this.resolveDataDir(normalizedCwd), {
|
|
177
|
+
manifest,
|
|
178
|
+
mindMap: mindMap || null,
|
|
179
|
+
knowledgeGraph: knowledgeGraph || null,
|
|
180
|
+
navigationHistory
|
|
181
|
+
})
|
|
182
|
+
return { revision, manifest, dataDir: this.resolveDataDir(normalizedCwd) }
|
|
183
|
+
})
|
|
184
|
+
this.locks.set(key, operation)
|
|
185
|
+
try {
|
|
186
|
+
return await operation
|
|
187
|
+
} finally {
|
|
188
|
+
if (this.locks.get(key) === operation) this.locks.delete(key)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async appendNavigation({ cwd, expectedRevision, navigation }) {
|
|
193
|
+
const normalizedCwd = normalizeWorkspacePath(cwd)
|
|
194
|
+
const key = workspaceKey(normalizedCwd)
|
|
195
|
+
const previous = this.locks.get(key) || Promise.resolve()
|
|
196
|
+
const operation = previous.catch(() => {}).then(async () => {
|
|
197
|
+
const current = await this.readState(normalizedCwd)
|
|
198
|
+
if (expectedRevision !== undefined && Number(expectedRevision) !== current.revision) {
|
|
199
|
+
throw new WorkspaceRevisionError(expectedRevision, current.revision)
|
|
200
|
+
}
|
|
201
|
+
const history = [...(current.navigationHistory || []), {
|
|
202
|
+
id: String(navigation.id || randomUUID()),
|
|
203
|
+
nodeId: String(navigation.nodeId || ''),
|
|
204
|
+
targetSessionId: String(navigation.targetSessionId || ''),
|
|
205
|
+
questionSummary: shortText(navigation.question, 500),
|
|
206
|
+
confirmedAt: this.now()
|
|
207
|
+
}].slice(-100)
|
|
208
|
+
if (!current.manifest) throw new Error('当前工作路径还没有已保存的知识视图。')
|
|
209
|
+
await replaceBundle(current.dataDir, {
|
|
210
|
+
manifest: current.manifest,
|
|
211
|
+
mindMap: current.mindMap,
|
|
212
|
+
knowledgeGraph: current.knowledgeGraph,
|
|
213
|
+
navigationHistory: history
|
|
214
|
+
})
|
|
215
|
+
return { revision: current.revision, navigation: history.at(-1) }
|
|
216
|
+
})
|
|
217
|
+
this.locks.set(key, operation)
|
|
218
|
+
try {
|
|
219
|
+
return await operation
|
|
220
|
+
} finally {
|
|
221
|
+
if (this.locks.get(key) === operation) this.locks.delete(key)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@p-dsh-market/conversation-knowledge-map",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DSH 多对话思维导图与静态知识图谱",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"p-dsh-market",
|
|
9
|
+
"conversation",
|
|
10
|
+
"mind-map",
|
|
11
|
+
"knowledge-graph"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./lib/index.js",
|
|
17
|
+
"./client": "./lib/client.js",
|
|
18
|
+
"./protocol": "./lib/protocol.js",
|
|
19
|
+
"./session-source": "./lib/session-source.js",
|
|
20
|
+
"./generation-orchestrator": "./lib/generation-orchestrator.js",
|
|
21
|
+
"./mind-map-schema": "./lib/mind-map-schema.js",
|
|
22
|
+
"./knowledge-graph-schema": "./lib/knowledge-graph-schema.js",
|
|
23
|
+
"./workspace-storage": "./lib/workspace-storage.js",
|
|
24
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib/index.js",
|
|
29
|
+
"lib/client.js",
|
|
30
|
+
"lib/protocol.js",
|
|
31
|
+
"lib/session-source.js",
|
|
32
|
+
"lib/generation-orchestrator.js",
|
|
33
|
+
"lib/mind-map-schema.js",
|
|
34
|
+
"lib/knowledge-graph-schema.js",
|
|
35
|
+
"lib/workspace-storage.js",
|
|
36
|
+
"skills/conversation-knowledge-map/SKILL.md",
|
|
37
|
+
"cordis.patch.yml",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"dsh": {
|
|
44
|
+
"protocolVersion": 1,
|
|
45
|
+
"client": {
|
|
46
|
+
"platform": "web"
|
|
47
|
+
},
|
|
48
|
+
"bundle": {
|
|
49
|
+
"patch": "./cordis.patch.yml"
|
|
50
|
+
},
|
|
51
|
+
"market": {
|
|
52
|
+
"displayName": "知识视图",
|
|
53
|
+
"capabilities": ["skills", "host", "client", "desktop-shell"]
|
|
54
|
+
},
|
|
55
|
+
"desktop": {
|
|
56
|
+
"permissions": ["shell:titlebar", "shell:page", "workspace:read", "workspace:write-plugin-data"],
|
|
57
|
+
"contributes": {
|
|
58
|
+
"titlebarActions": [
|
|
59
|
+
{
|
|
60
|
+
"id": "open-conversation-knowledge-map",
|
|
61
|
+
"slot": "desktop.titlebar.workspaceActions",
|
|
62
|
+
"label": "知识视图",
|
|
63
|
+
"order": 140,
|
|
64
|
+
"when": ["dshRunning", "pluginActive", "restartNotRequired"],
|
|
65
|
+
"action": {
|
|
66
|
+
"type": "pluginRpc",
|
|
67
|
+
"method": "conversationKnowledgeMap.open"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conversation-knowledge-map
|
|
3
|
+
description: 在用户明确要求整理多个同工作路径对话、生成思维导图或静态知识图谱时,使用知识视图插件的结构化生成、来源引用、严格约束和工作区持久化流程。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 知识视图生成约束
|
|
7
|
+
|
|
8
|
+
- 只有用户明确点击确认后,才能读取所选对话正文、调用模型或写入 `.g-dsh-market-knowledge`。
|
|
9
|
+
- 只使用锚点 Session 的 `header.cwd` 和用户从同一 `cwd` 选择的 Session;默认排除 `origin: subagent`。
|
|
10
|
+
- 默认读取 `sessionQuery.readSurface()`,不要读取或复制完整原始 JSONL。
|
|
11
|
+
- 思维导图节点必须包含标题、阶段性说明和来源引用;知识图谱关系必须包含证据和置信度。
|
|
12
|
+
- 知识图谱是静态结果,不自动更新、不在图上编辑、不从节点发散。
|
|
13
|
+
- 思维导图节点只生成后续问题;导航确认后不自动发送。
|