@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.
@@ -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: request.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: request.selectedSessionIds,
510
+ selectedSessionIds: sourceSessionIds,
471
511
  model: request.model,
472
512
  signal: task.controller.signal
473
513
  })
474
- mindMap = validateMindMap(value, { selectedSessionIds: request.selectedSessionIds, strict: request.strict === true })
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: request.selectedSessionIds,
523
+ selectedSessionIds: sourceSessionIds,
484
524
  model: request.model,
485
525
  signal: task.controller.signal
486
526
  })
487
- knowledgeGraph = validateKnowledgeGraph(value, { selectedSessionIds: request.selectedSessionIds, strict: request.strict === true })
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: request.selectedSessionIds,
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
- 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(error))
528
- throw error
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' ? 2500 : 6000 },
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?.({
@@ -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: selected }]))
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) => selected.includes(String(headerOf(record)?.id || ''))))
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 sources = []
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') throw new Error('当前 DSH Runtime 未提供 sessionQuery.readSurface。')
149
- let surface
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
- surface = await sessionQuery.readSurface(id)
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
- throw new Error(`读取对话“${id}”失败:${errorMessage(error)}`)
163
+ surfaceError = error
154
164
  }
155
- const events = (surface?.events || []).map(surfaceEventView).filter(Boolean)
156
- sources.push({
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(surface?.session?.cwd || header.cwd),
160
- capturedThroughSeq: Number.isInteger(surface?.capturedThroughSeq) ? surface.capturedThroughSeq : null,
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))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p-dsh-market/conversation-knowledge-map",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "DSH 多对话思维导图与静态知识图谱",
5
5
  "keywords": [
6
6
  "dsh",