@p-dsh-market/conversation-knowledge-map 0.1.1 → 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 +116 -21
- package/lib/index.js +16 -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 ''
|
|
@@ -258,6 +282,8 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
258
282
|
agentDefaultModel,
|
|
259
283
|
storage,
|
|
260
284
|
modelRunner,
|
|
285
|
+
sessionEventSource,
|
|
286
|
+
logger,
|
|
261
287
|
sourceReader = readSelectedSurfaces,
|
|
262
288
|
now = () => Date.now(),
|
|
263
289
|
idFactory = () => randomUUID()
|
|
@@ -268,6 +294,8 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
268
294
|
this.agentDefaultModel = agentDefaultModel
|
|
269
295
|
this.storage = storage
|
|
270
296
|
this.modelRunner = modelRunner
|
|
297
|
+
this.sessionEventSource = sessionEventSource
|
|
298
|
+
this.logger = logger
|
|
271
299
|
this.sourceReader = sourceReader
|
|
272
300
|
this.now = now
|
|
273
301
|
this.idFactory = idFactory
|
|
@@ -336,6 +364,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
336
364
|
}
|
|
337
365
|
this.tasks.set(task.id, task)
|
|
338
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))
|
|
339
368
|
this.update(task, 'created', { request: { ...request, prompt: shortText(request.prompt, 500) } })
|
|
340
369
|
task.promise = this.run(task, request).finally(() => {
|
|
341
370
|
if (this.busyByWorkspace.get(key) === task.id) this.busyByWorkspace.delete(key)
|
|
@@ -364,6 +393,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
364
393
|
includeSubagents: request.includeSubagents === true
|
|
365
394
|
})
|
|
366
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))
|
|
367
397
|
this.update(task, 'summarizing', { sourceCount: sources.length })
|
|
368
398
|
const summaries = []
|
|
369
399
|
for (const source of sources) {
|
|
@@ -428,11 +458,13 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
428
458
|
})
|
|
429
459
|
task.revision = saved.revision
|
|
430
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)
|
|
431
462
|
this.update(task, 'completed', { revision: saved.revision, result: task.result })
|
|
432
463
|
} catch (error) {
|
|
433
464
|
if (task.controller.signal.aborted || /取消|cancel/i.test(errorMessage(error))) {
|
|
434
465
|
this.update(task, 'cancelled', { error: '' })
|
|
435
466
|
} else {
|
|
467
|
+
logMessage(this.logger, 'error', 'generation failed id=%s status=%s error=%s', logId(task.id), task.status, errorMessage(error))
|
|
436
468
|
this.update(task, 'failed', { error: errorMessage(error) })
|
|
437
469
|
}
|
|
438
470
|
}
|
|
@@ -440,38 +472,101 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
440
472
|
}
|
|
441
473
|
|
|
442
474
|
async runModel(input) {
|
|
443
|
-
|
|
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
|
+
}
|
|
444
490
|
if (!this.agents?.create) throw new Error('当前 DSH Runtime 未提供 agents.create,无法生成知识视图。')
|
|
445
491
|
let selection = normalizeModelSelection(input.model)
|
|
446
492
|
if (!selection) {
|
|
447
|
-
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
|
+
}
|
|
448
499
|
}
|
|
449
500
|
const provider = String(selection?.provider || '').trim()
|
|
450
501
|
const model = String(selection?.model || '').trim()
|
|
451
|
-
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
|
+
}
|
|
452
506
|
const sessionId = `knowledge-map-${this.idFactory()}`
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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)
|
|
519
|
+
let unsubscribe
|
|
520
|
+
if (typeof this.sessionEventSource === 'function') {
|
|
521
|
+
try {
|
|
522
|
+
unsubscribe = this.sessionEventSource((session, event) => {
|
|
523
|
+
const eventSessionId = String(session?.id || session?.header?.id || '')
|
|
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))
|
|
463
531
|
})
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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))
|
|
467
535
|
}
|
|
468
|
-
}
|
|
536
|
+
}
|
|
537
|
+
let handle
|
|
469
538
|
try {
|
|
539
|
+
handle = await this.agents.create({
|
|
540
|
+
sessionId,
|
|
541
|
+
meta: { cwd: input.cwd, origin: 'subagent' },
|
|
542
|
+
agentOptions: { provider, model, maxTokens: input.kind === 'summary' ? 2500 : 6000 },
|
|
543
|
+
signal: input.signal,
|
|
544
|
+
setup: async (agentCtx) => {
|
|
545
|
+
agentCtx?.systemPrompt?.section?.({
|
|
546
|
+
name: 'knowledge-map:protocol',
|
|
547
|
+
order: 0,
|
|
548
|
+
text: '你是 DSH 知识视图生成器。只输出调用方要求的 JSON;不要调用外部网络、文件写入或其他 Agent 工具。'
|
|
549
|
+
})
|
|
550
|
+
try {
|
|
551
|
+
agentCtx?.tools?.restrict?.({ deny: ['multi_agent_discuss', 'shell', 'filesystem', 'web_search', 'browser'] })
|
|
552
|
+
} catch { /* older runtimes may not expose tool restriction */ }
|
|
553
|
+
}
|
|
554
|
+
})
|
|
470
555
|
handle.agent.followup(makeUserMessage(input.prompt, `${sessionId}-${input.kind}`))
|
|
471
556
|
await handle.agent.whenIdle()
|
|
472
|
-
|
|
557
|
+
const liveText = extractAgentText({ events: liveEvents })
|
|
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
|
|
473
566
|
} finally {
|
|
474
|
-
await
|
|
567
|
+
if (typeof unsubscribe === 'function') await unsubscribe()
|
|
568
|
+
await handle?.dispose?.()
|
|
569
|
+
logMessage(this.logger, 'info', 'agent call disposed kind=%s session=%s', input.kind, logId(sessionId))
|
|
475
570
|
}
|
|
476
571
|
}
|
|
477
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,17 +110,22 @@ 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)
|
|
115
|
+
const sessionEventSource = options.sessionEventSource || (typeof ctx?.on === 'function' ? (listener) => ctx.on('session/event', listener) : null)
|
|
114
116
|
const orchestrator = options.orchestrator || new KnowledgeGenerationOrchestrator({
|
|
115
117
|
sessionQuery,
|
|
116
118
|
sessions,
|
|
117
119
|
agents,
|
|
118
120
|
agentDefaultModel,
|
|
119
121
|
storage,
|
|
120
|
-
modelRunner: options.modelRunner
|
|
122
|
+
modelRunner: options.modelRunner,
|
|
123
|
+
sessionEventSource,
|
|
124
|
+
logger
|
|
121
125
|
})
|
|
122
126
|
const confirmations = new Map()
|
|
123
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))
|
|
124
129
|
|
|
125
130
|
async function contextFor(sessionId) {
|
|
126
131
|
const anchor = await resolveAnchorSession({ sessionQuery, sessions }, sessionId)
|
|
@@ -157,6 +162,7 @@ export function createHost(options = {}) {
|
|
|
157
162
|
if (!group) groups.unshift({ id: defaultModel.provider, name: defaultModel.provider, models: [{ id: defaultModel.model, name: defaultModel.model }] })
|
|
158
163
|
else if (!group.models.some((item) => item.id === defaultModel.model)) group.models.unshift({ id: defaultModel.model, name: defaultModel.model })
|
|
159
164
|
}
|
|
165
|
+
logMessage(logger, 'info', 'model catalog default=%s providers=%d groups=%d', defaultModel ? `${defaultModel.provider}/${defaultModel.model}` : 'none', providers.length, groups.length)
|
|
160
166
|
return { default: defaultModel, groups }
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -178,6 +184,7 @@ export function createHost(options = {}) {
|
|
|
178
184
|
const payload = { ...input, model, anchorSessionId: context.sessionId, cwd: context.cwd, expectedRevision: state.revision }
|
|
179
185
|
const token = randomUUID()
|
|
180
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)
|
|
181
188
|
return {
|
|
182
189
|
token,
|
|
183
190
|
expiresAt: Date.now() + CONFIRMATION_TTL,
|
|
@@ -207,6 +214,8 @@ export function createHost(options = {}) {
|
|
|
207
214
|
const path = parsePath(req)
|
|
208
215
|
const method = methodOf(req)
|
|
209
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)
|
|
210
219
|
try {
|
|
211
220
|
if (path.length === 0 && method === 'GET') {
|
|
212
221
|
return jsonResponse(res, 200, { ok: true, plugin: PLUGIN_ID })
|
|
@@ -240,6 +249,7 @@ export function createHost(options = {}) {
|
|
|
240
249
|
const body = await readJson(req)
|
|
241
250
|
const request = consumeConfirmation(body.token, body)
|
|
242
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}`)
|
|
243
253
|
return jsonResponse(res, 202, { ok: true, generation: task })
|
|
244
254
|
}
|
|
245
255
|
if (path[0] === 'generations' && path[1] && path[2] === 'events' && method === 'GET') {
|
|
@@ -282,7 +292,9 @@ export function createHost(options = {}) {
|
|
|
282
292
|
}
|
|
283
293
|
return jsonResponse(res, 404, { ok: false, error: 'Not found' })
|
|
284
294
|
} catch (error) {
|
|
285
|
-
|
|
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)
|
|
286
298
|
}
|
|
287
299
|
}
|
|
288
300
|
|
|
@@ -295,6 +307,7 @@ export function createHost(options = {}) {
|
|
|
295
307
|
content: readFileSync(SKILL_PATH, 'utf8').replace(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/, ''),
|
|
296
308
|
resourceBase: { kind: 'directory', path: SKILL_DIR_PATH }
|
|
297
309
|
})
|
|
310
|
+
logMessage(logger, 'info', 'host registrations complete plugin=%s', PLUGIN_ID)
|
|
298
311
|
if (typeof ctx?.effect === 'function') {
|
|
299
312
|
ctx.effect(() => () => {
|
|
300
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
|
}
|