@p-dsh-market/conversation-knowledge-map 0.1.5 → 0.1.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/lib/generation-orchestrator.js +59 -10
- package/lib/session-source.js +51 -15
- package/package.json +1 -1
|
@@ -6,6 +6,8 @@ import { clone, diagnosticSummary, errorMessage, logMessage, makeUserMessage, sh
|
|
|
6
6
|
import { chunkSourceText, readSelectedSurfaces } from './session-source.js'
|
|
7
7
|
|
|
8
8
|
const MAX_SUMMARY_CHARS = 2400
|
|
9
|
+
const SUMMARY_MAX_TOKENS = 8000
|
|
10
|
+
const VIEW_MAX_TOKENS = 12000
|
|
9
11
|
|
|
10
12
|
function modelLabel(selection) {
|
|
11
13
|
const provider = String(selection?.provider || '').trim()
|
|
@@ -251,6 +253,19 @@ function agentTurnError(event) {
|
|
|
251
253
|
return error
|
|
252
254
|
}
|
|
253
255
|
|
|
256
|
+
function agentTurnLimit(event) {
|
|
257
|
+
if (String(event?.type || '') !== 'turn/end') return null
|
|
258
|
+
const data = event?.data && typeof event.data === 'object' ? event.data : {}
|
|
259
|
+
const reason = data.reason && typeof data.reason === 'object' ? data.reason : null
|
|
260
|
+
if (reason?.kind !== 'max-tokens') return null
|
|
261
|
+
const error = new Error('模型输出达到最大 Token 限制,JSON 尚未完成。请减少所选对话数量或内容长度后重试。')
|
|
262
|
+
error.code = 'MAX_TOKENS'
|
|
263
|
+
error.agentTurn = true
|
|
264
|
+
error.tokenLimit = true
|
|
265
|
+
error.cause = reason
|
|
266
|
+
return error
|
|
267
|
+
}
|
|
268
|
+
|
|
254
269
|
function findAgentTurnError(surface) {
|
|
255
270
|
const events = Array.isArray(surface?.events) ? surface.events : []
|
|
256
271
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
@@ -260,6 +275,15 @@ function findAgentTurnError(surface) {
|
|
|
260
275
|
return null
|
|
261
276
|
}
|
|
262
277
|
|
|
278
|
+
function findAgentTurnLimit(surface) {
|
|
279
|
+
const events = Array.isArray(surface?.events) ? surface.events : []
|
|
280
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
281
|
+
const error = agentTurnLimit(events[index])
|
|
282
|
+
if (error) return error
|
|
283
|
+
}
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
263
287
|
function wrapAgentRuntimeError(error) {
|
|
264
288
|
if (error?.agentTurn || /^Agent Runtime 生成失败/.test(errorMessage(error))) return error
|
|
265
289
|
const wrapped = new Error(`Agent Runtime 生成失败:${errorMessage(error)}`)
|
|
@@ -286,6 +310,13 @@ async function readAgentText(sessionQuery, sessionId, signal, { logger, diagnost
|
|
|
286
310
|
logMessage(logger, 'error', 'agent turn failure session=%s source=surface code=%s error=%s', logId(sessionId), surfaceError.code || '', errorMessage(surfaceError))
|
|
287
311
|
throw surfaceError
|
|
288
312
|
}
|
|
313
|
+
const surfaceLimit = findAgentTurnLimit(surface)
|
|
314
|
+
if (surfaceLimit && !stats.agentLimit) {
|
|
315
|
+
stats.agentLimit = surfaceLimit
|
|
316
|
+
stats.agentFailureSource = 'surface'
|
|
317
|
+
stats.agentFailureCode = surfaceLimit.code
|
|
318
|
+
logMessage(logger, 'warn', 'agent token limit session=%s source=surface error=%s', logId(sessionId), errorMessage(surfaceLimit))
|
|
319
|
+
}
|
|
289
320
|
if (text.trim()) return text
|
|
290
321
|
if (attempt < 5) await new Promise((resolve) => setTimeout(resolve, 40 * (attempt + 1)))
|
|
291
322
|
}
|
|
@@ -305,6 +336,13 @@ async function readAgentText(sessionQuery, sessionId, signal, { logger, diagnost
|
|
|
305
336
|
logMessage(logger, 'error', 'agent turn failure session=%s source=session code=%s error=%s', logId(sessionId), sessionError.code || '', errorMessage(sessionError))
|
|
306
337
|
throw sessionError
|
|
307
338
|
}
|
|
339
|
+
const sessionLimit = findAgentTurnLimit(log)
|
|
340
|
+
if (sessionLimit && !stats.agentLimit) {
|
|
341
|
+
stats.agentLimit = sessionLimit
|
|
342
|
+
stats.agentFailureSource = 'session'
|
|
343
|
+
stats.agentFailureCode = sessionLimit.code
|
|
344
|
+
logMessage(logger, 'warn', 'agent token limit session=%s source=session error=%s', logId(sessionId), errorMessage(sessionLimit))
|
|
345
|
+
}
|
|
308
346
|
if (text.trim()) return text
|
|
309
347
|
}
|
|
310
348
|
return ''
|
|
@@ -436,8 +474,10 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
436
474
|
const sources = await this.sourceReader({ sessionQuery: this.sessionQuery, sessions: this.sessions }, {
|
|
437
475
|
cwd: request.cwd,
|
|
438
476
|
sessionIds: request.selectedSessionIds,
|
|
477
|
+
fallbackSessionId: request.anchorSessionId,
|
|
439
478
|
includeSubagents: request.includeSubagents === true
|
|
440
479
|
})
|
|
480
|
+
const sourceSessionIds = [...new Set(sources.map((source) => source.sessionId))]
|
|
441
481
|
this.assertNotCancelled(task)
|
|
442
482
|
logMessage(this.logger, 'info', 'generation sources loaded id=%s sources=%d events=%d', logId(task.id), sources.length, sources.reduce((total, source) => total + (Array.isArray(source.events) ? source.events.length : 0), 0))
|
|
443
483
|
this.update(task, 'summarizing', { sourceCount: sources.length })
|
|
@@ -450,7 +490,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
450
490
|
prompt: summaryPrompt(source, chunk, request.strict === true),
|
|
451
491
|
cwd: request.cwd,
|
|
452
492
|
strict: request.strict === true,
|
|
453
|
-
selectedSessionIds:
|
|
493
|
+
selectedSessionIds: sourceSessionIds,
|
|
454
494
|
model: request.model,
|
|
455
495
|
signal: task.controller.signal
|
|
456
496
|
})
|
|
@@ -467,11 +507,11 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
467
507
|
prompt: outputPrompt('mind-map', summaries, request.prompt, request.strict === true),
|
|
468
508
|
cwd: request.cwd,
|
|
469
509
|
strict: request.strict === true,
|
|
470
|
-
selectedSessionIds:
|
|
510
|
+
selectedSessionIds: sourceSessionIds,
|
|
471
511
|
model: request.model,
|
|
472
512
|
signal: task.controller.signal
|
|
473
513
|
})
|
|
474
|
-
mindMap = validateMindMap(value, { selectedSessionIds:
|
|
514
|
+
mindMap = validateMindMap(value, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
475
515
|
}
|
|
476
516
|
if (request.outputMode === 'knowledge-graph' || request.outputMode === 'both') {
|
|
477
517
|
this.update(task, 'building-knowledge-graph')
|
|
@@ -480,11 +520,11 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
480
520
|
prompt: outputPrompt('knowledge-graph', summaries, request.prompt, request.strict === true),
|
|
481
521
|
cwd: request.cwd,
|
|
482
522
|
strict: request.strict === true,
|
|
483
|
-
selectedSessionIds:
|
|
523
|
+
selectedSessionIds: sourceSessionIds,
|
|
484
524
|
model: request.model,
|
|
485
525
|
signal: task.controller.signal
|
|
486
526
|
})
|
|
487
|
-
knowledgeGraph = validateKnowledgeGraph(value, { selectedSessionIds:
|
|
527
|
+
knowledgeGraph = validateKnowledgeGraph(value, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
488
528
|
}
|
|
489
529
|
assertOutputSourceRefs({ mindMap, knowledgeGraph }, sources)
|
|
490
530
|
this.assertNotCancelled(task)
|
|
@@ -494,7 +534,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
494
534
|
cwd: request.cwd,
|
|
495
535
|
expectedRevision: request.expectedRevision,
|
|
496
536
|
generationId: task.id,
|
|
497
|
-
sourceSessionIds
|
|
537
|
+
sourceSessionIds,
|
|
498
538
|
prompt: request.prompt,
|
|
499
539
|
strict: request.strict,
|
|
500
540
|
outputMode: request.outputMode,
|
|
@@ -524,8 +564,9 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
524
564
|
logMessage(this.logger, 'info', 'agent output parsed kind=%s source=%s value=%s result=%s', input.kind, source, diagnosticSummary(value), diagnosticSummary(result))
|
|
525
565
|
return result
|
|
526
566
|
} catch (error) {
|
|
527
|
-
|
|
528
|
-
|
|
567
|
+
const surfacedError = diagnostics.agentLimit || error
|
|
568
|
+
logMessage(this.logger, 'error', 'agent output parse failed kind=%s source=%s value=%s diagnostics=%s error=%s', input.kind, source, diagnosticSummary(value), JSON.stringify(diagnostics), errorMessage(surfacedError))
|
|
569
|
+
throw surfacedError
|
|
529
570
|
}
|
|
530
571
|
}
|
|
531
572
|
if (typeof this.modelRunner === 'function') {
|
|
@@ -562,7 +603,8 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
562
603
|
lastSessionEventCount: 0,
|
|
563
604
|
agentFailureSource: '',
|
|
564
605
|
agentFailureCode: '',
|
|
565
|
-
agentFailure: null
|
|
606
|
+
agentFailure: null,
|
|
607
|
+
agentLimit: null
|
|
566
608
|
}
|
|
567
609
|
logMessage(this.logger, 'info', 'agent call start kind=%s session=%s provider=%s model=%s promptLength=%d', input.kind, logId(sessionId), provider, model, String(input.prompt || '').length)
|
|
568
610
|
let unsubscribe
|
|
@@ -584,6 +626,13 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
584
626
|
diagnostics.agentFailureCode = failure.code || ''
|
|
585
627
|
logMessage(this.logger, 'error', 'agent turn failure session=%s source=live-event code=%s error=%s', logId(sessionId), failure.code || '', errorMessage(failure))
|
|
586
628
|
}
|
|
629
|
+
const limit = agentTurnLimit(event)
|
|
630
|
+
if (limit && !diagnostics.agentLimit) {
|
|
631
|
+
diagnostics.agentLimit = limit
|
|
632
|
+
diagnostics.agentFailureSource = 'live-event'
|
|
633
|
+
diagnostics.agentFailureCode = limit.code
|
|
634
|
+
logMessage(this.logger, 'warn', 'agent token limit session=%s source=live-event error=%s', logId(sessionId), errorMessage(limit))
|
|
635
|
+
}
|
|
587
636
|
})
|
|
588
637
|
logMessage(this.logger, 'info', 'agent event subscription ready session=%s', logId(sessionId))
|
|
589
638
|
} catch (error) {
|
|
@@ -596,7 +645,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
596
645
|
handle = await this.agents.create({
|
|
597
646
|
sessionId,
|
|
598
647
|
meta: { cwd: input.cwd, origin: 'subagent' },
|
|
599
|
-
agentOptions: { provider, model, maxTokens: input.kind === 'summary' ?
|
|
648
|
+
agentOptions: { provider, model, maxTokens: input.kind === 'summary' ? SUMMARY_MAX_TOKENS : VIEW_MAX_TOKENS },
|
|
600
649
|
signal: input.signal,
|
|
601
650
|
setup: async (agentCtx) => {
|
|
602
651
|
agentCtx?.systemPrompt?.section?.({
|
package/lib/session-source.js
CHANGED
|
@@ -112,8 +112,10 @@ function sourceSeqsOf(event) {
|
|
|
112
112
|
return Array.isArray(value) ? value.filter((item) => Number.isInteger(item) && item >= 0) : []
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
export function surfaceEventView(event) {
|
|
115
|
+
export function surfaceEventView(event, { fullSession = false } = {}) {
|
|
116
116
|
const role = eventRole(String(event?.type || ''))
|
|
117
|
+
const data = event?.data?.message || event?.data || {}
|
|
118
|
+
if (fullSession && role === 'user' && data?.source?.kind !== 'user') return null
|
|
117
119
|
const text = shortText(eventMessageText(event), 12000)
|
|
118
120
|
if (!role || !text) return null
|
|
119
121
|
return {
|
|
@@ -125,42 +127,76 @@ export function surfaceEventView(event) {
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, sessionIds, includeSubagents = false }) {
|
|
130
|
+
export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, sessionIds, fallbackSessionId = '', includeSubagents = false }) {
|
|
129
131
|
const selected = [...new Set((sessionIds || []).map((id) => String(id || '').trim()).filter(Boolean))]
|
|
130
132
|
if (selected.length === 0) throw new Error('至少选择一个对话。')
|
|
131
133
|
const normalizedCwd = normalizeWorkspacePath(cwd)
|
|
132
134
|
if (!normalizedCwd) throw new Error('当前工作路径无效。')
|
|
135
|
+
const fallbackId = String(fallbackSessionId || '').trim()
|
|
136
|
+
const resolvedIds = [...new Set([...selected, ...(fallbackId ? [fallbackId] : [])])]
|
|
133
137
|
const records = []
|
|
134
138
|
if (typeof sessionQuery?.filterSessions === 'function') {
|
|
135
|
-
records.push(...await sessionQuery.filterSessions([{ kind: 'id', values:
|
|
139
|
+
records.push(...await sessionQuery.filterSessions([{ kind: 'id', values: resolvedIds }]))
|
|
136
140
|
} else if (typeof sessionQuery?.listSessions === 'function') {
|
|
137
141
|
const all = await sessionQuery.listSessions()
|
|
138
|
-
records.push(...all.filter((record) =>
|
|
142
|
+
records.push(...all.filter((record) => resolvedIds.includes(String(headerOf(record)?.id || ''))))
|
|
139
143
|
}
|
|
140
144
|
const recordMap = new Map(records.map((record) => [String(headerOf(record)?.id || record?.id || ''), record]))
|
|
141
|
-
const
|
|
142
|
-
for (const id of selected) {
|
|
145
|
+
const readSource = async (id) => {
|
|
143
146
|
const record = recordMap.get(id)
|
|
144
147
|
const header = headerOf(record) || sessions?.get?.(id)?.header
|
|
145
148
|
if (!header?.id) throw new Error(`所选对话不存在或已不可读:${id}`)
|
|
146
149
|
if (!sameWorkspacePath(header.cwd, normalizedCwd)) throw new Error(`所选对话不属于当前工作路径:${id}`)
|
|
147
150
|
if (!includeSubagents && header.origin === 'subagent') throw new Error(`不能选择子 Agent 对话:${id}`)
|
|
148
|
-
if (typeof sessionQuery?.readSurface !== 'function'
|
|
149
|
-
|
|
151
|
+
if (typeof sessionQuery?.readSurface !== 'function' && typeof sessionQuery?.readSession !== 'function') {
|
|
152
|
+
throw new Error('当前 DSH Runtime 未提供 sessionQuery.readSurface/readSession。')
|
|
153
|
+
}
|
|
154
|
+
let snapshot
|
|
155
|
+
let events = []
|
|
156
|
+
let surfaceError = null
|
|
150
157
|
try {
|
|
151
|
-
|
|
158
|
+
if (typeof sessionQuery?.readSurface === 'function') {
|
|
159
|
+
snapshot = await sessionQuery.readSurface(id)
|
|
160
|
+
events = (snapshot?.events || []).map((event) => surfaceEventView(event)).filter(Boolean)
|
|
161
|
+
}
|
|
152
162
|
} catch (error) {
|
|
153
|
-
|
|
163
|
+
surfaceError = error
|
|
154
164
|
}
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
if (events.length === 0 && typeof sessionQuery?.readSession === 'function') {
|
|
166
|
+
try {
|
|
167
|
+
const full = await sessionQuery.readSession(id)
|
|
168
|
+
snapshot = full || snapshot
|
|
169
|
+
events = (full?.events || []).map((event) => surfaceEventView(event, { fullSession: true })).filter(Boolean)
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (surfaceError) throw new Error(`读取对话“${id}”失败:surface=${errorMessage(surfaceError)};session=${errorMessage(error)}`)
|
|
172
|
+
throw new Error(`读取对话“${id}”完整记录失败:${errorMessage(error)}`)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (surfaceError && events.length === 0) throw new Error(`读取对话“${id}”失败:${errorMessage(surfaceError)}`)
|
|
176
|
+
return {
|
|
157
177
|
sessionId: id,
|
|
158
178
|
title: `对话 ${id.slice(0, 8)}`,
|
|
159
|
-
cwd: normalizeWorkspacePath(
|
|
160
|
-
capturedThroughSeq: Number.isInteger(
|
|
179
|
+
cwd: normalizeWorkspacePath(snapshot?.session?.cwd || header.cwd),
|
|
180
|
+
capturedThroughSeq: Number.isInteger(snapshot?.capturedThroughSeq) ? snapshot.capturedThroughSeq : null,
|
|
161
181
|
events,
|
|
162
182
|
text: events.map((event) => `${event.role === 'user' ? '用户' : '助手'}:${event.text}`).join('\n\n')
|
|
163
|
-
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const sources = []
|
|
186
|
+
let needsFallback = false
|
|
187
|
+
for (const id of selected) {
|
|
188
|
+
const source = await readSource(id)
|
|
189
|
+
if (source.events.length > 0) sources.push(source)
|
|
190
|
+
else needsFallback = true
|
|
191
|
+
}
|
|
192
|
+
if (needsFallback && fallbackId && !sources.some((source) => source.sessionId === fallbackId)) {
|
|
193
|
+
const fallback = await readSource(fallbackId)
|
|
194
|
+
if (fallback.events.length > 0) sources.push(fallback)
|
|
195
|
+
}
|
|
196
|
+
if (sources.length === 0) {
|
|
197
|
+
throw new Error(fallbackId
|
|
198
|
+
? `所选对话及当前对话都没有可读取的用户/助手消息:${selected.join(', ')}`
|
|
199
|
+
: `所选对话没有可读取的用户/助手消息:${selected.join(', ')}`)
|
|
164
200
|
}
|
|
165
201
|
if (typeof sessionQuery?.readTitleSnapshots === 'function' && sources.length) {
|
|
166
202
|
const results = await sessionQuery.readTitleSnapshots(sources.map((source) => source.sessionId))
|