@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 +4 -0
- package/lib/generation-orchestrator.js +151 -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 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
|
-
|
|
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
|
-
|
|
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 {
|
|
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)
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
}
|