@p-dsh-market/conversation-knowledge-map 0.1.2 → 0.1.4

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 CHANGED
@@ -13,6 +13,10 @@ DSH 的“知识视图”插件,把同一工作路径下用户明确选择的
13
13
 
14
14
  节点“继续对话”只形成一个可编辑的后续问题。确认导航后,插件使用公开的 Session 导航入口;如果当前 Runtime 没有向该槽位暴露草稿镜像,则提供“打开并复制问题”的安全降级,不自动发送消息。
15
15
 
16
+ ## 生成失败诊断
17
+
18
+ Host 和生成编排器会输出带 `[conversation-knowledge-map]` 前缀的诊断日志。日志包含路由、Provider / Model、Agent Session、实时事件类型、surface/session 读取次数、提取文本长度、输出形状和错误原因,不记录 Prompt、对话正文或模型返回正文。优先查看 DSH Web Runtime 的终端日志;若 Runtime 提供 logger 服务,则同时写入该 logger。重点关注 `agent event`、`agent idle`、`agent turn failure`、`agent surface read`、`agent session read` 和 `agent output parse failed`。Runtime 在 `turn/end.reason.kind = error` 时会优先显示其 `code/message`;只有未发现 turn 错误且确实没有助手输出时,才会报告“模型没有返回 JSON 对象”。
19
+
16
20
  ## 本地验证
17
21
 
18
22
  ```powershell
@@ -2,11 +2,22 @@ import { randomUUID } from 'node:crypto'
2
2
 
3
3
  import { validateKnowledgeGraph } from './knowledge-graph-schema.js'
4
4
  import { validateMindMap } from './mind-map-schema.js'
5
- import { clone, errorMessage, makeUserMessage, shortText } from './protocol.js'
5
+ import { clone, diagnosticSummary, errorMessage, logMessage, makeUserMessage, shortText } from './protocol.js'
6
6
  import { chunkSourceText, readSelectedSurfaces } from './session-source.js'
7
7
 
8
8
  const MAX_SUMMARY_CHARS = 2400
9
9
 
10
+ function modelLabel(selection) {
11
+ const provider = String(selection?.provider || '').trim()
12
+ const model = String(selection?.model || '').trim()
13
+ return provider && model ? `${provider}/${model}` : 'default'
14
+ }
15
+
16
+ function logId(value) {
17
+ const text = String(value || '')
18
+ return text.length > 96 ? `${text.slice(0, 40)}…${text.slice(-40)}` : text
19
+ }
20
+
10
21
  function phaseMessage(status) {
11
22
  return {
12
23
  confirming: '等待用户确认生成范围…',
@@ -224,17 +235,76 @@ function extractAgentText(surface) {
224
235
  return chunks.join('')
225
236
  }
226
237
 
227
- async function readAgentText(sessionQuery, sessionId, signal) {
238
+ function agentTurnError(event) {
239
+ if (String(event?.type || '') !== 'turn/end') return null
240
+ const data = event?.data && typeof event.data === 'object' ? event.data : {}
241
+ const reason = data.reason && typeof data.reason === 'object' ? data.reason : null
242
+ if (reason?.kind !== 'error') return null
243
+ const nestedError = reason.error && typeof reason.error === 'object' ? reason.error : null
244
+ const code = String(nestedError?.code || reason.code || '').trim()
245
+ const message = String(nestedError?.message || reason.message || (typeof reason.error === 'string' ? reason.error : '')).trim()
246
+ const detail = shortText(message || 'Agent turn 执行失败。', 1200)
247
+ const error = new Error(`Agent Runtime 生成失败${code ? `(${code})` : ''}:${detail}`)
248
+ error.code = code
249
+ error.agentTurn = true
250
+ error.cause = nestedError || reason
251
+ return error
252
+ }
253
+
254
+ function findAgentTurnError(surface) {
255
+ const events = Array.isArray(surface?.events) ? surface.events : []
256
+ for (let index = events.length - 1; index >= 0; index -= 1) {
257
+ const error = agentTurnError(events[index])
258
+ if (error) return error
259
+ }
260
+ return null
261
+ }
262
+
263
+ function wrapAgentRuntimeError(error) {
264
+ if (error?.agentTurn || /^Agent Runtime 生成失败/.test(errorMessage(error))) return error
265
+ const wrapped = new Error(`Agent Runtime 生成失败:${errorMessage(error)}`)
266
+ wrapped.cause = error
267
+ return wrapped
268
+ }
269
+
270
+ async function readAgentText(sessionQuery, sessionId, signal, { logger, diagnostics } = {}) {
271
+ const stats = diagnostics || { surfaceReads: 0, sessionReads: 0 }
228
272
  for (let attempt = 0; attempt < 6; attempt += 1) {
229
273
  if (signal?.aborted) throw signal.reason || new Error('生成已取消。')
274
+ stats.surfaceReads += 1
230
275
  const surface = await sessionQuery?.readSurface?.(sessionId)
231
276
  const text = extractAgentText(surface)
277
+ const eventCount = Array.isArray(surface?.events) ? surface.events.length : 0
278
+ stats.lastSurfaceEventCount = eventCount
279
+ stats.lastSurfaceShape = diagnosticSummary(surface)
280
+ stats.lastSurfaceTextLength = text.length
281
+ logMessage(logger, 'info', 'agent surface read session=%s attempt=%d events=%d extractedTextLength=%d shape=%s', logId(sessionId), attempt + 1, eventCount, text.length, stats.lastSurfaceShape)
282
+ const surfaceError = findAgentTurnError(surface)
283
+ if (surfaceError) {
284
+ stats.agentFailureSource = 'surface'
285
+ stats.agentFailureCode = surfaceError.code || ''
286
+ logMessage(logger, 'error', 'agent turn failure session=%s source=surface code=%s error=%s', logId(sessionId), surfaceError.code || '', errorMessage(surfaceError))
287
+ throw surfaceError
288
+ }
232
289
  if (text.trim()) return text
233
290
  if (attempt < 5) await new Promise((resolve) => setTimeout(resolve, 40 * (attempt + 1)))
234
291
  }
235
292
  if (typeof sessionQuery?.readSession === 'function') {
293
+ stats.sessionReads += 1
236
294
  const log = await sessionQuery.readSession(sessionId)
237
295
  const text = extractAgentText(log)
296
+ const eventCount = Array.isArray(log?.events) ? log.events.length : 0
297
+ stats.lastSessionEventCount = eventCount
298
+ stats.lastSessionShape = diagnosticSummary(log)
299
+ stats.lastSessionTextLength = text.length
300
+ logMessage(logger, 'info', 'agent session read session=%s events=%d extractedTextLength=%d shape=%s', logId(sessionId), eventCount, text.length, stats.lastSessionShape)
301
+ const sessionError = findAgentTurnError(log)
302
+ if (sessionError) {
303
+ stats.agentFailureSource = 'session'
304
+ stats.agentFailureCode = sessionError.code || ''
305
+ logMessage(logger, 'error', 'agent turn failure session=%s source=session code=%s error=%s', logId(sessionId), sessionError.code || '', errorMessage(sessionError))
306
+ throw sessionError
307
+ }
238
308
  if (text.trim()) return text
239
309
  }
240
310
  return ''
@@ -259,6 +329,7 @@ export class KnowledgeGenerationOrchestrator {
259
329
  storage,
260
330
  modelRunner,
261
331
  sessionEventSource,
332
+ logger,
262
333
  sourceReader = readSelectedSurfaces,
263
334
  now = () => Date.now(),
264
335
  idFactory = () => randomUUID()
@@ -270,6 +341,7 @@ export class KnowledgeGenerationOrchestrator {
270
341
  this.storage = storage
271
342
  this.modelRunner = modelRunner
272
343
  this.sessionEventSource = sessionEventSource
344
+ this.logger = logger
273
345
  this.sourceReader = sourceReader
274
346
  this.now = now
275
347
  this.idFactory = idFactory
@@ -338,6 +410,7 @@ export class KnowledgeGenerationOrchestrator {
338
410
  }
339
411
  this.tasks.set(task.id, task)
340
412
  this.busyByWorkspace.set(key, task.id)
413
+ logMessage(this.logger, 'info', 'generation start id=%s cwd=%s outputMode=%s selectedSessions=%d strict=%s model=%s', logId(task.id), shortText(request.cwd, 180), request.outputMode, Array.isArray(request.selectedSessionIds) ? request.selectedSessionIds.length : 0, request.strict === true, modelLabel(request.model))
341
414
  this.update(task, 'created', { request: { ...request, prompt: shortText(request.prompt, 500) } })
342
415
  task.promise = this.run(task, request).finally(() => {
343
416
  if (this.busyByWorkspace.get(key) === task.id) this.busyByWorkspace.delete(key)
@@ -366,6 +439,7 @@ export class KnowledgeGenerationOrchestrator {
366
439
  includeSubagents: request.includeSubagents === true
367
440
  })
368
441
  this.assertNotCancelled(task)
442
+ 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))
369
443
  this.update(task, 'summarizing', { sourceCount: sources.length })
370
444
  const summaries = []
371
445
  for (const source of sources) {
@@ -430,11 +504,13 @@ export class KnowledgeGenerationOrchestrator {
430
504
  })
431
505
  task.revision = saved.revision
432
506
  task.result = { revision: saved.revision, manifest: saved.manifest, mindMap, knowledgeGraph }
507
+ logMessage(this.logger, 'info', 'generation completed id=%s revision=%d elapsedMs=%d', logId(task.id), saved.revision, this.now() - task.createdAt)
433
508
  this.update(task, 'completed', { revision: saved.revision, result: task.result })
434
509
  } catch (error) {
435
510
  if (task.controller.signal.aborted || /取消|cancel/i.test(errorMessage(error))) {
436
511
  this.update(task, 'cancelled', { error: '' })
437
512
  } else {
513
+ logMessage(this.logger, 'error', 'generation failed id=%s status=%s error=%s', logId(task.id), task.status, errorMessage(error))
438
514
  this.update(task, 'failed', { error: errorMessage(error) })
439
515
  }
440
516
  }
@@ -442,27 +518,80 @@ export class KnowledgeGenerationOrchestrator {
442
518
  }
443
519
 
444
520
  async runModel(input) {
445
- if (typeof this.modelRunner === 'function') return parseStructuredOutput(await this.modelRunner(input))
521
+ const parseModelOutput = (value, source, diagnostics = {}) => {
522
+ try {
523
+ const result = parseStructuredOutput(value)
524
+ logMessage(this.logger, 'info', 'agent output parsed kind=%s source=%s value=%s result=%s', input.kind, source, diagnosticSummary(value), diagnosticSummary(result))
525
+ return result
526
+ } 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
529
+ }
530
+ }
531
+ if (typeof this.modelRunner === 'function') {
532
+ logMessage(this.logger, 'info', 'model runner start kind=%s model=%s promptLength=%d', input.kind, modelLabel(input.model), String(input.prompt || '').length)
533
+ const value = await this.modelRunner(input)
534
+ return parseModelOutput(value, 'model-runner')
535
+ }
446
536
  if (!this.agents?.create) throw new Error('当前 DSH Runtime 未提供 agents.create,无法生成知识视图。')
447
537
  let selection = normalizeModelSelection(input.model)
448
538
  if (!selection) {
449
- try { selection = normalizeModelSelection(this.agentDefaultModel?.currentSelection?.()) } catch { selection = null }
539
+ try {
540
+ selection = normalizeModelSelection(this.agentDefaultModel?.currentSelection?.())
541
+ } catch (error) {
542
+ logMessage(this.logger, 'warn', 'default model selection failed kind=%s error=%s', input.kind, shortText(errorMessage(error), 500))
543
+ selection = null
544
+ }
450
545
  }
451
546
  const provider = String(selection?.provider || '').trim()
452
547
  const model = String(selection?.model || '').trim()
453
- if (!provider || !model) throw new Error('当前没有可用的默认 Provider/Model。')
548
+ if (!provider || !model) {
549
+ logMessage(this.logger, 'error', 'agent call has no usable model kind=%s requestedModel=%s defaultModel=%s', input.kind, modelLabel(input.model), modelLabel(selection))
550
+ throw new Error('当前没有可用的默认 Provider/Model。')
551
+ }
454
552
  const sessionId = `knowledge-map-${this.idFactory()}`
455
553
  const liveEvents = []
554
+ const diagnostics = {
555
+ liveEvents: 0,
556
+ liveAssistantMessages: 0,
557
+ liveAssistantChunks: 0,
558
+ liveOtherEvents: 0,
559
+ surfaceReads: 0,
560
+ sessionReads: 0,
561
+ lastSurfaceEventCount: 0,
562
+ lastSessionEventCount: 0,
563
+ agentFailureSource: '',
564
+ agentFailureCode: '',
565
+ agentFailure: null
566
+ }
567
+ 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)
456
568
  let unsubscribe
457
569
  if (typeof this.sessionEventSource === 'function') {
458
570
  try {
459
571
  unsubscribe = this.sessionEventSource((session, event) => {
460
572
  const eventSessionId = String(session?.id || session?.header?.id || '')
461
- if (eventSessionId === sessionId && event) liveEvents.push(event)
573
+ if (eventSessionId !== sessionId || !event) return
574
+ liveEvents.push(event)
575
+ diagnostics.liveEvents += 1
576
+ if (event.type === 'assistant/message') diagnostics.liveAssistantMessages += 1
577
+ else if (event.type === 'assistant/chunk') diagnostics.liveAssistantChunks += 1
578
+ else diagnostics.liveOtherEvents += 1
579
+ logMessage(this.logger, 'info', 'agent event session=%s type=%s data=%s', logId(sessionId), String(event.type || 'unknown'), diagnosticSummary(event.data))
580
+ const failure = agentTurnError(event)
581
+ if (failure && !diagnostics.agentFailure) {
582
+ diagnostics.agentFailure = failure
583
+ diagnostics.agentFailureSource = 'live-event'
584
+ diagnostics.agentFailureCode = failure.code || ''
585
+ logMessage(this.logger, 'error', 'agent turn failure session=%s source=live-event code=%s error=%s', logId(sessionId), failure.code || '', errorMessage(failure))
586
+ }
462
587
  })
463
- } catch { /* event subscription is an optimization; surface reads remain the fallback */ }
588
+ logMessage(this.logger, 'info', 'agent event subscription ready session=%s', logId(sessionId))
589
+ } catch (error) {
590
+ logMessage(this.logger, 'warn', 'agent event subscription failed session=%s error=%s', logId(sessionId), errorMessage(error))
591
+ }
464
592
  }
465
593
  let handle
594
+ let stage = 'create'
466
595
  try {
467
596
  handle = await this.agents.create({
468
597
  sessionId,
@@ -480,14 +609,26 @@ export class KnowledgeGenerationOrchestrator {
480
609
  } catch { /* older runtimes may not expose tool restriction */ }
481
610
  }
482
611
  })
612
+ stage = 'followup'
483
613
  handle.agent.followup(makeUserMessage(input.prompt, `${sessionId}-${input.kind}`))
614
+ stage = 'idle'
484
615
  await handle.agent.whenIdle()
616
+ if (diagnostics.agentFailure) throw diagnostics.agentFailure
617
+ stage = 'read-output'
485
618
  const liveText = extractAgentText({ events: liveEvents })
486
- if (liveText.trim()) return parseStructuredOutput(liveText)
487
- return parseStructuredOutput(await readAgentText(this.sessionQuery, sessionId, input.signal))
619
+ logMessage(this.logger, 'info', 'agent idle session=%s liveEvents=%d assistantMessages=%d assistantChunks=%d otherEvents=%d liveTextLength=%d', logId(sessionId), diagnostics.liveEvents, diagnostics.liveAssistantMessages, diagnostics.liveAssistantChunks, diagnostics.liveOtherEvents, liveText.length)
620
+ if (liveText.trim()) return parseModelOutput(liveText, 'live-events', diagnostics)
621
+ const persistedText = await readAgentText(this.sessionQuery, sessionId, input.signal, { logger: this.logger, diagnostics })
622
+ logMessage(this.logger, 'info', 'agent persisted output session=%s textLength=%d surfaceReads=%d sessionReads=%d', logId(sessionId), persistedText.length, diagnostics.surfaceReads, diagnostics.sessionReads)
623
+ return parseModelOutput(persistedText, 'persisted-session', diagnostics)
624
+ } catch (error) {
625
+ const surfacedError = diagnostics.agentFailure || (stage === 'read-output' ? error : wrapAgentRuntimeError(error))
626
+ logMessage(this.logger, 'error', 'agent call failed kind=%s session=%s liveEvents=%d surfaceReads=%d sessionReads=%d error=%s', input.kind, logId(sessionId), diagnostics.liveEvents, diagnostics.surfaceReads, diagnostics.sessionReads, errorMessage(surfacedError))
627
+ throw surfacedError
488
628
  } finally {
489
- await unsubscribe?.()
629
+ if (typeof unsubscribe === 'function') await unsubscribe()
490
630
  await handle?.dispose?.()
631
+ logMessage(this.logger, 'info', 'agent call disposed kind=%s session=%s', input.kind, logId(sessionId))
491
632
  }
492
633
  }
493
634
 
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url'
3
3
  import { randomUUID } from 'node:crypto'
4
4
 
5
5
  import { KnowledgeGenerationOrchestrator } from './generation-orchestrator.js'
6
- import { errorMessage, jsonResponse, methodOf, parseUrl, readJson, safeId, sseWrite, shortText } from './protocol.js'
6
+ import { errorMessage, jsonResponse, loggerFacade, logMessage, methodOf, parseUrl, readJson, safeId, sseWrite, shortText } from './protocol.js'
7
7
  import { listWorkspaceSessions, normalizeWorkspacePath, resolveAnchorSession } from './session-source.js'
8
8
  import { WorkspaceRevisionError, WorkspaceStorage } from './workspace-storage.js'
9
9
 
@@ -110,6 +110,7 @@ export function createHost(options = {}) {
110
110
  const llm = options.llm || getService(ctx, 'llm')
111
111
  const skills = options.skills || getService(ctx, 'skills')
112
112
  const webServer = options.webServer || getService(ctx, 'webServer')
113
+ const logger = loggerFacade(options.logger || getService(ctx, 'logger') || ctx?.logger || console)
113
114
  const storage = options.storage || new WorkspaceStorage(options.storageOptions)
114
115
  const sessionEventSource = options.sessionEventSource || (typeof ctx?.on === 'function' ? (listener) => ctx.on('session/event', listener) : null)
115
116
  const orchestrator = options.orchestrator || new KnowledgeGenerationOrchestrator({
@@ -119,10 +120,12 @@ export function createHost(options = {}) {
119
120
  agentDefaultModel,
120
121
  storage,
121
122
  modelRunner: options.modelRunner,
122
- sessionEventSource
123
+ sessionEventSource,
124
+ logger
123
125
  })
124
126
  const confirmations = new Map()
125
127
  const sseClients = new Set()
128
+ logMessage(logger, 'info', 'host apply plugin=%s services sessionQuery=%s sessions=%s agents=%s llm=%s webServer=%s sessionEvents=%s', PLUGIN_ID, Boolean(sessionQuery), Boolean(sessions), Boolean(agents?.create), Boolean(llm), Boolean(webServer?.register), Boolean(sessionEventSource))
126
129
 
127
130
  async function contextFor(sessionId) {
128
131
  const anchor = await resolveAnchorSession({ sessionQuery, sessions }, sessionId)
@@ -159,6 +162,7 @@ export function createHost(options = {}) {
159
162
  if (!group) groups.unshift({ id: defaultModel.provider, name: defaultModel.provider, models: [{ id: defaultModel.model, name: defaultModel.model }] })
160
163
  else if (!group.models.some((item) => item.id === defaultModel.model)) group.models.unshift({ id: defaultModel.model, name: defaultModel.model })
161
164
  }
165
+ logMessage(logger, 'info', 'model catalog default=%s providers=%d groups=%d', defaultModel ? `${defaultModel.provider}/${defaultModel.model}` : 'none', providers.length, groups.length)
162
166
  return { default: defaultModel, groups }
163
167
  }
164
168
 
@@ -180,6 +184,7 @@ export function createHost(options = {}) {
180
184
  const payload = { ...input, model, anchorSessionId: context.sessionId, cwd: context.cwd, expectedRevision: state.revision }
181
185
  const token = randomUUID()
182
186
  confirmations.set(token, { payload, expiresAt: Date.now() + CONFIRMATION_TTL, used: false })
187
+ logMessage(logger, 'info', 'generation confirmed cwd=%s anchorSession=%s selectedSessions=%d outputMode=%s strict=%s model=%s expectedRevision=%d', shortText(context.cwd, 180), shortText(context.sessionId, 96), input.selectedSessionIds.length, input.outputMode, input.strict, `${model.provider}/${model.model}`, state.revision)
183
188
  return {
184
189
  token,
185
190
  expiresAt: Date.now() + CONFIRMATION_TTL,
@@ -209,6 +214,8 @@ export function createHost(options = {}) {
209
214
  const path = parsePath(req)
210
215
  const method = methodOf(req)
211
216
  if (!path) return jsonResponse(res, 404, { ok: false, error: 'Not found' })
217
+ const routeLabel = path.join('/') || '(root)'
218
+ logMessage(logger, 'info', 'route start method=%s path=%s', method, routeLabel)
212
219
  try {
213
220
  if (path.length === 0 && method === 'GET') {
214
221
  return jsonResponse(res, 200, { ok: true, plugin: PLUGIN_ID })
@@ -242,6 +249,7 @@ export function createHost(options = {}) {
242
249
  const body = await readJson(req)
243
250
  const request = consumeConfirmation(body.token, body)
244
251
  const task = orchestrator.start(request)
252
+ logMessage(logger, 'info', 'generation accepted id=%s outputMode=%s model=%s', shortText(task.id, 96), request.outputMode, `${request.model.provider}/${request.model.model}`)
245
253
  return jsonResponse(res, 202, { ok: true, generation: task })
246
254
  }
247
255
  if (path[0] === 'generations' && path[1] && path[2] === 'events' && method === 'GET') {
@@ -284,7 +292,9 @@ export function createHost(options = {}) {
284
292
  }
285
293
  return jsonResponse(res, 404, { ok: false, error: 'Not found' })
286
294
  } catch (error) {
287
- return writeError(res, error, statusForError(error))
295
+ const status = statusForError(error)
296
+ logMessage(logger, 'error', 'route failed method=%s path=%s status=%d error=%s', method, routeLabel, status, errorMessage(error))
297
+ return writeError(res, error, status)
288
298
  }
289
299
  }
290
300
 
@@ -297,6 +307,7 @@ export function createHost(options = {}) {
297
307
  content: readFileSync(SKILL_PATH, 'utf8').replace(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/, ''),
298
308
  resourceBase: { kind: 'directory', path: SKILL_DIR_PATH }
299
309
  })
310
+ logMessage(logger, 'info', 'host registrations complete plugin=%s', PLUGIN_ID)
300
311
  if (typeof ctx?.effect === 'function') {
301
312
  ctx.effect(() => () => {
302
313
  for (const client of sseClients.values()) client.close?.()
package/lib/protocol.js CHANGED
@@ -23,6 +23,36 @@ export function shortText(value, max = 8000) {
23
23
  return text.length > max ? `${text.slice(0, Math.max(0, max - 1))}…` : text
24
24
  }
25
25
 
26
+ export function loggerFacade(service, name = 'conversation-knowledge-map') {
27
+ if (!service) return null
28
+ try {
29
+ const logger = typeof service === 'function' ? service(name) : service
30
+ return logger && typeof logger === 'object' ? logger : null
31
+ } catch {
32
+ return null
33
+ }
34
+ }
35
+
36
+ export function logMessage(logger, level, format, ...params) {
37
+ try { logger?.[level]?.(`[conversation-knowledge-map] ${format}`, ...params) } catch { /* logging must not affect the plugin */ }
38
+ }
39
+
40
+ function diagnosticShape(value, depth = 0) {
41
+ if (value === null) return 'null'
42
+ if (value === undefined) return 'undefined'
43
+ if (typeof value === 'string') return `string(${value.length})`
44
+ if (typeof value !== 'object') return typeof value
45
+ if (depth >= 2) return Array.isArray(value) ? `array(${value.length})` : 'object'
46
+ if (Array.isArray(value)) return `array(${value.length})[${value.slice(0, 6).map((item) => diagnosticShape(item, depth + 1)).join(',')}]`
47
+ const keys = Object.keys(value).sort()
48
+ const suffix = keys.length > 12 ? ',…' : ''
49
+ return `object{${keys.slice(0, 12).join(',')}${suffix}}`
50
+ }
51
+
52
+ export function diagnosticSummary(value) {
53
+ return diagnosticShape(value)
54
+ }
55
+
26
56
  export function clone(value) {
27
57
  return value === undefined ? undefined : JSON.parse(JSON.stringify(value))
28
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p-dsh-market/conversation-knowledge-map",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "DSH 多对话思维导图与静态知识图谱",
5
5
  "keywords": [
6
6
  "dsh",