dsh-better-tips 0.12.1 → 0.13.0

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/index.js CHANGED
@@ -27,7 +27,8 @@
27
27
 
28
28
  import { randomUUID } from 'node:crypto'
29
29
  import { readFile } from 'node:fs/promises'
30
- import { join, resolve, dirname } from 'node:path'
30
+ import { join, resolve, dirname, isAbsolute } from 'node:path'
31
+ import { fileURLToPath } from 'node:url'
31
32
  import { createRequire } from 'node:module'
32
33
  import { readLogMetadata } from './lib/log-metadata.js'
33
34
  import { describeError, installFetchDiagnostics, redactSecrets } from './lib/fetch-diagnostics.js'
@@ -157,6 +158,24 @@ function shapeDetail (record, protectCredentials = false) {
157
158
  }
158
159
  }
159
160
 
161
+ /** Authentication can be wrapped by an adapter error; withhold the whole diagnostic text. */
162
+ function hasAuthCause (value) {
163
+ const seen = new Set()
164
+ const pending = [{ value, depth: 0 }]
165
+ for (let index = 0; index < pending.length && index < 64; index++) {
166
+ const { value, depth } = pending[index]
167
+ if (!value || typeof value !== 'object' || depth > 16 || seen.has(value)) continue
168
+ if (value.code === 'AUTH') return true
169
+ seen.add(value)
170
+ const append = (child) => { if (pending.length < 64) pending.push({ value: child, depth: depth + 1 }) }
171
+ if (value.cause !== undefined) append(value.cause)
172
+ if (Array.isArray(value.errors)) {
173
+ for (let at = 0; at < value.errors.length && pending.length < 64; at++) append(value.errors[at])
174
+ }
175
+ }
176
+ return false
177
+ }
178
+
160
179
  /** One "captured but unmatched" request: address, time and the failure shape. */
161
180
  function shapeRecent (record) {
162
181
  return {
@@ -232,10 +251,11 @@ export function apply (ctx, config = {}) {
232
251
  const records = diagnostics?.recordsFor(context.attemptId) ?? []
233
252
  return records.filter((record) => record.context?.sessionId === context.sessionId && record.context?.provider === context.provider).at(-1)
234
253
  }
235
- function scopedStream (options, next, onFailure) {
254
+ function scopedStream (options, next, onContext) {
236
255
  const context = { attemptId: randomUUID(), sessionId: options.sessionId, provider: options.provider,
237
256
  model: typeof options.model === 'string' ? options.model : undefined,
238
257
  purpose: options.purpose ?? 'agent', startedAt: Date.now() }
258
+ onContext?.(context)
239
259
  return (async function * () {
240
260
  const source = await scoped(context, next)
241
261
  const iterator = scoped(context, () => source[Symbol.asyncIterator]())
@@ -255,7 +275,6 @@ export function apply (ctx, config = {}) {
255
275
  pendingFailures.set(context.sessionId, pending.slice(-12))
256
276
  pruneDetails(pendingFailures)
257
277
  }
258
- onFailure?.(context, failure)
259
278
  }
260
279
  }
261
280
  yield chunk
@@ -330,7 +349,11 @@ export function apply (ctx, config = {}) {
330
349
  let context
331
350
  const attemptOptions = { ...options, signal }
332
351
  try {
333
- const stream = scopedStream(attemptOptions, () => dispatch(attemptOptions), (value) => { context = value })
352
+ const stream = scopedStream(attemptOptions, () => dispatch(attemptOptions), (value) => {
353
+ context = value
354
+ attempt.attemptId = value.attemptId
355
+ attempt.provenance = 'request-context'
356
+ })
334
357
  for await (const chunk of stream) {
335
358
  signal.throwIfAborted()
336
359
  chunks.push(chunk)
@@ -339,7 +362,9 @@ export function apply (ctx, config = {}) {
339
362
  } catch (error) {
340
363
  attempt.endedAt = Date.now()
341
364
  attempt.elapsedMs = attempt.endedAt - attempt.startedAt
365
+ attempt.protectedAuth = hasAuthCause(error)
342
366
  attempt.thrown = describeError(error)
367
+ if (context) attempt.detail = captureFor(context)
343
368
  record.attempts.push(attempt)
344
369
  record.status = signal.aborted ? 'aborted' : 'thrown'
345
370
  record.endedAt = attempt.endedAt
@@ -348,14 +373,11 @@ export function apply (ctx, config = {}) {
348
373
  attempt.endedAt = Date.now()
349
374
  attempt.elapsedMs = attempt.endedAt - attempt.startedAt
350
375
  const reason = finish?.reason
376
+ attempt.finishReason = reason?.kind
351
377
  if (reason?.kind === 'error') {
352
378
  attempt.failure = { message: String(reason.failure?.message ?? ''), code: String(reason.failure?.code ?? ''),
353
379
  status: reason.failure?.status ?? statusFromMessage(reason.failure?.message) }
354
- if (context) {
355
- attempt.attemptId = context.attemptId
356
- attempt.provenance = 'request-context'
357
- attempt.detail = captureFor(context)
358
- }
380
+ if (context) attempt.detail = captureFor(context)
359
381
  record.attempts.push(attempt)
360
382
  if (!shouldRetry({ failure: reason.failure, settings }).retry || index > settings.compactionRetries || signal.aborted) {
361
383
  record.status = signal.aborted ? 'aborted' : 'error'
@@ -373,7 +395,9 @@ export function apply (ctx, config = {}) {
373
395
  }
374
396
  continue
375
397
  }
376
- record.status = reason?.kind === 'aborted' ? 'aborted' : finish ? 'ok' : 'incomplete'
398
+ // This is the summary request's terminal state, not proof of compaction commit.
399
+ record.status = reason?.kind === 'stop' || reason?.kind === 'tool-calls' ? 'ok'
400
+ : reason?.kind === 'max-tokens' ? 'max-tokens' : reason?.kind === 'aborted' ? 'aborted' : 'incomplete'
377
401
  const usage = chunks.find((chunk) => chunk?.type === 'usage')
378
402
  if (usage?.usage !== undefined) record.usage = usage.usage
379
403
  record.attempts.push(attempt)
@@ -455,6 +479,170 @@ export function apply (ctx, config = {}) {
455
479
  Promise.resolve().then(() => packageDirectory('@deepseek-ai/dsh')).then((directory) => packageVersion(directory, '@deepseek-ai/dsh')).catch(() => null),
456
480
  packageVersion(new URL('.', import.meta.url), name),
457
481
  ]).then(([dshVersion, pluginVersion]) => ({ dshVersion, pluginVersion }))
482
+ const enabledPluginInventory = async (signal) => {
483
+ signal.throwIfAborted()
484
+ let partial = false
485
+ let entriesAvailable = false
486
+ let bundlesAvailable = false
487
+ const unique = new Map()
488
+ const manifests = new Map()
489
+ const resolutions = new Map()
490
+ const nativePackages = new Set()
491
+ const get = (key) => { try { return scope.get(key) } catch { partial = true; return undefined } }
492
+ const packages = get('pluginPackages')
493
+ const manager = get('pluginManager')
494
+ const loader = get('loader')
495
+ const hostBase = scope.baseUrl ?? ctx.baseUrl ?? import.meta.url
496
+ const packageName = (value) => {
497
+ if (typeof value !== 'string' || value.startsWith('.') || value.includes(':') || isAbsolute(value)) return undefined
498
+ const parts = value.split('/')
499
+ const name = value.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
500
+ return /^(?:@[A-Za-z0-9._~-]+\/)?[A-Za-z0-9._~-]+$/.test(name) && redactDiagnosticString(name) === name ? name : undefined
501
+ }
502
+ const add = (name, version) => {
503
+ if (nativePackages.has(name)) return
504
+ if (packageName(name) !== name) { partial = true; return }
505
+ const safeVersion = typeof version === 'string' && /^[0-9A-Za-z][0-9A-Za-z.+_-]*$/.test(version) && redactDiagnosticString(version) === version ? version : null
506
+ if (safeVersion === null) partial = true
507
+ unique.set(JSON.stringify([name, safeVersion]), { name, version: safeVersion })
508
+ }
509
+ let bundles = []
510
+ try {
511
+ if (typeof manager?.listBundles === 'function') {
512
+ const listed = await manager.listBundles()
513
+ signal.throwIfAborted()
514
+ if (!Array.isArray(listed)) throw new Error('invalid inventory')
515
+ bundles = listed
516
+ bundlesAvailable = true
517
+ for (const bundle of bundles) if (bundle.official === true || bundle.availability === 'installation') {
518
+ nativePackages.add(bundle.name)
519
+ for (const row of bundle.rows ?? []) { const name = packageName(row.moduleName); if (name) nativePackages.add(name) }
520
+ }
521
+ }
522
+ } catch { signal.throwIfAborted(); partial = true }
523
+ const readManifest = (path) => {
524
+ if (!manifests.has(String(path))) manifests.set(String(path), (async () => {
525
+ signal.throwIfAborted()
526
+ try { const value = JSON.parse(await readFile(path, { encoding: 'utf8', signal })); signal.throwIfAborted(); return value } catch (error) { signal.throwIfAborted(); return undefined }
527
+ })())
528
+ return manifests.get(String(path))
529
+ }
530
+ const resolveEntry = async (moduleName, anchor = hostBase) => {
531
+ signal.throwIfAborted()
532
+ if (typeof moduleName === 'string' && moduleName.startsWith('cordis:')) return
533
+ const key = JSON.stringify([moduleName, anchor])
534
+ if (!resolutions.has(key)) resolutions.set(key, (async () => {
535
+ const name = packageName(moduleName)
536
+ if (nativePackages.has(name)) return
537
+ let manifestPath
538
+ try {
539
+ if (name) {
540
+ if (typeof packages?.packageOf === 'function') manifestPath = packages.packageOf(name, anchor)?.manifestPath
541
+ else {
542
+ for (const root of createRequire(anchor).resolve.paths(name) ?? []) {
543
+ const path = join(root, name, 'package.json')
544
+ if (await readManifest(path)) { manifestPath = path; break }
545
+ }
546
+ }
547
+ } else if (typeof moduleName === 'string' && !moduleName.startsWith('cordis:')) {
548
+ const url = isAbsolute(moduleName) ? undefined : new URL(moduleName, anchor)
549
+ let directory = dirname(isAbsolute(moduleName) ? moduleName : url?.protocol === 'file:' ? fileURLToPath(url) : '')
550
+ if (directory !== '.') for (;;) {
551
+ const path = join(directory, 'package.json')
552
+ if (await readManifest(path)) { manifestPath = path; break }
553
+ const parent = dirname(directory)
554
+ if (parent === directory) break
555
+ directory = parent
556
+ }
557
+ }
558
+ } catch { signal.throwIfAborted() }
559
+ const manifest = manifestPath ? await readManifest(manifestPath) : undefined
560
+ if (manifest && (!name || manifest.name === name)) {
561
+ const repository = typeof manifest.repository === 'string' ? manifest.repository : manifest.repository?.url
562
+ const nativeRepository = typeof repository === 'string' && /^(?:git\+)?https:\/\/github\.com\/deepseek-ai\/deepseek-harness(?:\.git)?\/?$/.test(repository)
563
+ if (nativeRepository) {
564
+ nativePackages.add(manifest.name)
565
+ for (const [key, plugin] of unique) if (plugin.name === manifest.name) unique.delete(key)
566
+ } else add(manifest.name, manifest.version)
567
+ }
568
+ else { partial = true; if (name) add(name, null) }
569
+ })())
570
+ await resolutions.get(key)
571
+ signal.throwIfAborted()
572
+ }
573
+ try {
574
+ if (typeof loader?.entries === 'function') {
575
+ const entries = [...loader.entries()]
576
+ entriesAvailable = true
577
+ for (const entry of entries) {
578
+ signal.throwIfAborted()
579
+ if (entry.options?.group || entry.disabled) continue
580
+ await resolveEntry(entry.options?.name, entry.parent?.tree?.ctx?.baseUrl ?? hostBase)
581
+ }
582
+ } else if (typeof manager?.listPlugins === 'function') {
583
+ const entries = await manager.listPlugins()
584
+ signal.throwIfAborted()
585
+ if (!Array.isArray(entries)) throw new Error('invalid inventory')
586
+ entriesAvailable = true
587
+ for (const entry of entries) if (entry.enabled === true) await resolveEntry(entry.moduleName)
588
+ }
589
+ } catch { signal.throwIfAborted(); partial = true }
590
+ for (const bundle of bundles) {
591
+ signal.throwIfAborted()
592
+ if (bundle.enabled === true) add(bundle.name, bundle.version)
593
+ }
594
+ try {
595
+ const presets = get('agentPresets')
596
+ if (presets) {
597
+ const inspected = typeof presets.inspectCompositions === 'function' ? presets.inspectCompositions() : []
598
+ signal.throwIfAborted()
599
+ if (!Array.isArray(inspected)) throw new Error('invalid preset inventory')
600
+ for (const composition of inspected) for (const entry of composition.modules ?? []) {
601
+ await resolveEntry(entry.moduleName, entry.useHostBase ? hostBase : entry.baseUrl ?? hostBase)
602
+ }
603
+ if (typeof presets.compositionInventory !== 'function') partial = true
604
+ else {
605
+ const compositions = await presets.compositionInventory()
606
+ signal.throwIfAborted()
607
+ if (!Array.isArray(compositions)) throw new Error('invalid preset inventory')
608
+ for (const composition of compositions) {
609
+ if (composition.broken) partial = true
610
+ for (const row of composition.rows ?? []) {
611
+ signal.throwIfAborted()
612
+ const name = packageName(row.moduleName)
613
+ if (nativePackages.has(name) || typeof row.moduleName === 'string' && row.moduleName.startsWith('cordis:')) continue
614
+ const exact = inspected.some((item) => item.id === composition.id && item.modules?.some((entry) => entry.moduleName === row.moduleName))
615
+ if (row.enabled === 'conditional') continue
616
+ if (row.enabled !== true) continue
617
+ if (!exact) {
618
+ partial = true
619
+ if (name) add(name, null)
620
+ }
621
+ }
622
+ }
623
+ }
624
+ }
625
+ } catch { signal.throwIfAborted(); partial = true }
626
+ signal.throwIfAborted()
627
+ return { enabledPlugins: [...unique.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : (a.version ?? '') < (b.version ?? '') ? -1 : (a.version ?? '') > (b.version ?? '') ? 1 : 0),
628
+ pluginsStatus: !entriesAvailable && !bundlesAvailable ? 'unavailable' : partial || !entriesAvailable || !bundlesAvailable ? 'partial' : 'complete' }
629
+ }
630
+ const environmentHandler = async (request) => {
631
+ const admission = connection.admit(request)
632
+ if (admission.rejection) return jsonResponse({ error: 'unauthorized' }, admission.rejection)
633
+ if (admission.peer?.principal?.kind !== 'owner') return jsonResponse({ error: 'owner required' }, 403)
634
+ try {
635
+ request.signal.throwIfAborted()
636
+ const inventory = await enabledPluginInventory(request.signal)
637
+ const [dshVersion, pluginVersion] = await Promise.all([
638
+ packageDirectory('@deepseek-ai/dsh').then((directory) => packageVersion(directory, '@deepseek-ai/dsh')),
639
+ packageVersion(new URL('.', import.meta.url), name),
640
+ ])
641
+ request.signal.throwIfAborted()
642
+ return jsonResponse({ runtime: { dshVersion, pluginVersion, ...inventory } })
643
+ } catch { return jsonResponse({ error: request.signal.aborted ? 'cancelled' : 'environment unavailable' }, request.signal.aborted ? 499 : 503) }
644
+ }
645
+ scope.effect(() => connection.fetch.register({ path: '/api/better-tips/environment', methods: ['GET'], requestBody: 'buffered', fetch: environmentHandler }), 'better-tips: report environment api')
458
646
  const metadataHandler = async (request) => {
459
647
  const admission = connection.admit(request)
460
648
  if (admission.rejection) return jsonResponse({ error: 'unauthorized' }, admission.rejection)
@@ -570,16 +758,18 @@ export function apply (ctx, config = {}) {
570
758
  usage: record.usage,
571
759
  attempts: record.attempts.map((attempt) => {
572
760
  const capture = attempt.detail
761
+ const protectedAuth = attempt.failure?.code === 'AUTH' || attempt.protectedAuth || hasAuthCause(attempt.thrown)
573
762
  return {
574
763
  index: attempt.index,
575
764
  attemptId: attempt.attemptId,
576
765
  provenance: attempt.provenance,
577
766
  at: attempt.startedAt,
578
767
  elapsedMs: attempt.elapsedMs,
768
+ finishReason: attempt.finishReason,
579
769
  failure: attempt.failure,
580
- thrown: attempt.thrown,
770
+ thrown: protectedAuth && attempt.thrown ? { code: 'AUTH', message: '', protected: true } : attempt.thrown,
581
771
  lagMs: capture === undefined ? undefined : Math.max(0, (attempt.endedAt ?? record.endedAt) - capture.at),
582
- detail: capture === undefined ? undefined : shapeDetail(capture, attempt.failure?.code === 'AUTH'),
772
+ detail: capture === undefined ? undefined : shapeDetail(capture, protectedAuth),
583
773
  }
584
774
  }),
585
775
  }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-better-tips",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "Clear DSH error summaries, original messages, on-demand network evidence, copyable troubleshooting reports and bounded retries.",
6
6
  "author": "HakureiMonika",