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/CHANGELOG.md +23 -0
- package/README.md +54 -122
- package/client.js +268 -124
- package/index.js +202 -12
- package/package.json +1 -1
- package/test/client.test.mjs +677 -188
- package/test/host.test.mjs +417 -3
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,
|
|
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) => {
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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",
|