@p-dsh-market/conversation-knowledge-map 0.1.2 → 0.1.3
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 +4 -0
- package/lib/generation-orchestrator.js +89 -10
- package/lib/index.js +14 -3
- package/lib/protocol.js +30 -0
- package/package.json +1 -1
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 surface read`、`agent session read` 和 `agent output parse failed`。
|
|
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,30 @@ function extractAgentText(surface) {
|
|
|
224
235
|
return chunks.join('')
|
|
225
236
|
}
|
|
226
237
|
|
|
227
|
-
async function readAgentText(sessionQuery, sessionId, signal) {
|
|
238
|
+
async function readAgentText(sessionQuery, sessionId, signal, { logger, diagnostics } = {}) {
|
|
239
|
+
const stats = diagnostics || { surfaceReads: 0, sessionReads: 0 }
|
|
228
240
|
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
229
241
|
if (signal?.aborted) throw signal.reason || new Error('生成已取消。')
|
|
242
|
+
stats.surfaceReads += 1
|
|
230
243
|
const surface = await sessionQuery?.readSurface?.(sessionId)
|
|
231
244
|
const text = extractAgentText(surface)
|
|
245
|
+
const eventCount = Array.isArray(surface?.events) ? surface.events.length : 0
|
|
246
|
+
stats.lastSurfaceEventCount = eventCount
|
|
247
|
+
stats.lastSurfaceShape = diagnosticSummary(surface)
|
|
248
|
+
stats.lastSurfaceTextLength = text.length
|
|
249
|
+
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)
|
|
232
250
|
if (text.trim()) return text
|
|
233
251
|
if (attempt < 5) await new Promise((resolve) => setTimeout(resolve, 40 * (attempt + 1)))
|
|
234
252
|
}
|
|
235
253
|
if (typeof sessionQuery?.readSession === 'function') {
|
|
254
|
+
stats.sessionReads += 1
|
|
236
255
|
const log = await sessionQuery.readSession(sessionId)
|
|
237
256
|
const text = extractAgentText(log)
|
|
257
|
+
const eventCount = Array.isArray(log?.events) ? log.events.length : 0
|
|
258
|
+
stats.lastSessionEventCount = eventCount
|
|
259
|
+
stats.lastSessionShape = diagnosticSummary(log)
|
|
260
|
+
stats.lastSessionTextLength = text.length
|
|
261
|
+
logMessage(logger, 'info', 'agent session read session=%s events=%d extractedTextLength=%d shape=%s', logId(sessionId), eventCount, text.length, stats.lastSessionShape)
|
|
238
262
|
if (text.trim()) return text
|
|
239
263
|
}
|
|
240
264
|
return ''
|
|
@@ -259,6 +283,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
259
283
|
storage,
|
|
260
284
|
modelRunner,
|
|
261
285
|
sessionEventSource,
|
|
286
|
+
logger,
|
|
262
287
|
sourceReader = readSelectedSurfaces,
|
|
263
288
|
now = () => Date.now(),
|
|
264
289
|
idFactory = () => randomUUID()
|
|
@@ -270,6 +295,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
270
295
|
this.storage = storage
|
|
271
296
|
this.modelRunner = modelRunner
|
|
272
297
|
this.sessionEventSource = sessionEventSource
|
|
298
|
+
this.logger = logger
|
|
273
299
|
this.sourceReader = sourceReader
|
|
274
300
|
this.now = now
|
|
275
301
|
this.idFactory = idFactory
|
|
@@ -338,6 +364,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
338
364
|
}
|
|
339
365
|
this.tasks.set(task.id, task)
|
|
340
366
|
this.busyByWorkspace.set(key, task.id)
|
|
367
|
+
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
368
|
this.update(task, 'created', { request: { ...request, prompt: shortText(request.prompt, 500) } })
|
|
342
369
|
task.promise = this.run(task, request).finally(() => {
|
|
343
370
|
if (this.busyByWorkspace.get(key) === task.id) this.busyByWorkspace.delete(key)
|
|
@@ -366,6 +393,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
366
393
|
includeSubagents: request.includeSubagents === true
|
|
367
394
|
})
|
|
368
395
|
this.assertNotCancelled(task)
|
|
396
|
+
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
397
|
this.update(task, 'summarizing', { sourceCount: sources.length })
|
|
370
398
|
const summaries = []
|
|
371
399
|
for (const source of sources) {
|
|
@@ -430,11 +458,13 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
430
458
|
})
|
|
431
459
|
task.revision = saved.revision
|
|
432
460
|
task.result = { revision: saved.revision, manifest: saved.manifest, mindMap, knowledgeGraph }
|
|
461
|
+
logMessage(this.logger, 'info', 'generation completed id=%s revision=%d elapsedMs=%d', logId(task.id), saved.revision, this.now() - task.createdAt)
|
|
433
462
|
this.update(task, 'completed', { revision: saved.revision, result: task.result })
|
|
434
463
|
} catch (error) {
|
|
435
464
|
if (task.controller.signal.aborted || /取消|cancel/i.test(errorMessage(error))) {
|
|
436
465
|
this.update(task, 'cancelled', { error: '' })
|
|
437
466
|
} else {
|
|
467
|
+
logMessage(this.logger, 'error', 'generation failed id=%s status=%s error=%s', logId(task.id), task.status, errorMessage(error))
|
|
438
468
|
this.update(task, 'failed', { error: errorMessage(error) })
|
|
439
469
|
}
|
|
440
470
|
}
|
|
@@ -442,25 +472,67 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
442
472
|
}
|
|
443
473
|
|
|
444
474
|
async runModel(input) {
|
|
445
|
-
|
|
475
|
+
const parseModelOutput = (value, source, diagnostics = {}) => {
|
|
476
|
+
try {
|
|
477
|
+
const result = parseStructuredOutput(value)
|
|
478
|
+
logMessage(this.logger, 'info', 'agent output parsed kind=%s source=%s value=%s result=%s', input.kind, source, diagnosticSummary(value), diagnosticSummary(result))
|
|
479
|
+
return result
|
|
480
|
+
} catch (error) {
|
|
481
|
+
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))
|
|
482
|
+
throw error
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (typeof this.modelRunner === 'function') {
|
|
486
|
+
logMessage(this.logger, 'info', 'model runner start kind=%s model=%s promptLength=%d', input.kind, modelLabel(input.model), String(input.prompt || '').length)
|
|
487
|
+
const value = await this.modelRunner(input)
|
|
488
|
+
return parseModelOutput(value, 'model-runner')
|
|
489
|
+
}
|
|
446
490
|
if (!this.agents?.create) throw new Error('当前 DSH Runtime 未提供 agents.create,无法生成知识视图。')
|
|
447
491
|
let selection = normalizeModelSelection(input.model)
|
|
448
492
|
if (!selection) {
|
|
449
|
-
try {
|
|
493
|
+
try {
|
|
494
|
+
selection = normalizeModelSelection(this.agentDefaultModel?.currentSelection?.())
|
|
495
|
+
} catch (error) {
|
|
496
|
+
logMessage(this.logger, 'warn', 'default model selection failed kind=%s error=%s', input.kind, shortText(errorMessage(error), 500))
|
|
497
|
+
selection = null
|
|
498
|
+
}
|
|
450
499
|
}
|
|
451
500
|
const provider = String(selection?.provider || '').trim()
|
|
452
501
|
const model = String(selection?.model || '').trim()
|
|
453
|
-
if (!provider || !model)
|
|
502
|
+
if (!provider || !model) {
|
|
503
|
+
logMessage(this.logger, 'error', 'agent call has no usable model kind=%s requestedModel=%s defaultModel=%s', input.kind, modelLabel(input.model), modelLabel(selection))
|
|
504
|
+
throw new Error('当前没有可用的默认 Provider/Model。')
|
|
505
|
+
}
|
|
454
506
|
const sessionId = `knowledge-map-${this.idFactory()}`
|
|
455
507
|
const liveEvents = []
|
|
508
|
+
const diagnostics = {
|
|
509
|
+
liveEvents: 0,
|
|
510
|
+
liveAssistantMessages: 0,
|
|
511
|
+
liveAssistantChunks: 0,
|
|
512
|
+
liveOtherEvents: 0,
|
|
513
|
+
surfaceReads: 0,
|
|
514
|
+
sessionReads: 0,
|
|
515
|
+
lastSurfaceEventCount: 0,
|
|
516
|
+
lastSessionEventCount: 0
|
|
517
|
+
}
|
|
518
|
+
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
519
|
let unsubscribe
|
|
457
520
|
if (typeof this.sessionEventSource === 'function') {
|
|
458
521
|
try {
|
|
459
522
|
unsubscribe = this.sessionEventSource((session, event) => {
|
|
460
523
|
const eventSessionId = String(session?.id || session?.header?.id || '')
|
|
461
|
-
if (eventSessionId
|
|
524
|
+
if (eventSessionId !== sessionId || !event) return
|
|
525
|
+
liveEvents.push(event)
|
|
526
|
+
diagnostics.liveEvents += 1
|
|
527
|
+
if (event.type === 'assistant/message') diagnostics.liveAssistantMessages += 1
|
|
528
|
+
else if (event.type === 'assistant/chunk') diagnostics.liveAssistantChunks += 1
|
|
529
|
+
else diagnostics.liveOtherEvents += 1
|
|
530
|
+
logMessage(this.logger, 'info', 'agent event session=%s type=%s data=%s', logId(sessionId), String(event.type || 'unknown'), diagnosticSummary(event.data))
|
|
462
531
|
})
|
|
463
|
-
|
|
532
|
+
logMessage(this.logger, 'info', 'agent event subscription ready session=%s', logId(sessionId))
|
|
533
|
+
} catch (error) {
|
|
534
|
+
logMessage(this.logger, 'warn', 'agent event subscription failed session=%s error=%s', logId(sessionId), errorMessage(error))
|
|
535
|
+
}
|
|
464
536
|
}
|
|
465
537
|
let handle
|
|
466
538
|
try {
|
|
@@ -483,11 +555,18 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
483
555
|
handle.agent.followup(makeUserMessage(input.prompt, `${sessionId}-${input.kind}`))
|
|
484
556
|
await handle.agent.whenIdle()
|
|
485
557
|
const liveText = extractAgentText({ events: liveEvents })
|
|
486
|
-
|
|
487
|
-
|
|
558
|
+
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)
|
|
559
|
+
if (liveText.trim()) return parseModelOutput(liveText, 'live-events', diagnostics)
|
|
560
|
+
const persistedText = await readAgentText(this.sessionQuery, sessionId, input.signal, { logger: this.logger, diagnostics })
|
|
561
|
+
logMessage(this.logger, 'info', 'agent persisted output session=%s textLength=%d surfaceReads=%d sessionReads=%d', logId(sessionId), persistedText.length, diagnostics.surfaceReads, diagnostics.sessionReads)
|
|
562
|
+
return parseModelOutput(persistedText, 'persisted-session', diagnostics)
|
|
563
|
+
} catch (error) {
|
|
564
|
+
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(error))
|
|
565
|
+
throw error
|
|
488
566
|
} finally {
|
|
489
|
-
await unsubscribe
|
|
567
|
+
if (typeof unsubscribe === 'function') await unsubscribe()
|
|
490
568
|
await handle?.dispose?.()
|
|
569
|
+
logMessage(this.logger, 'info', 'agent call disposed kind=%s session=%s', input.kind, logId(sessionId))
|
|
491
570
|
}
|
|
492
571
|
}
|
|
493
572
|
|
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
|
-
|
|
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
|
}
|