dsh-code 1.0.5 → 1.0.6
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.en.md +287 -286
- package/README.md +13 -12
- package/bin/deepseek.mjs +118 -3
- package/cordis.patch.yml +17 -7
- package/lib/index.mjs +1148 -347
- package/lib/types/app.d.ts +25 -6
- package/lib/types/attachments.d.ts +36 -4
- package/lib/types/index.d.ts +11 -2
- package/lib/types/provider-settings.d.ts +6 -11
- package/lib/types/render/animations.d.ts +74 -7
- package/lib/types/render/export.d.ts +0 -6
- package/lib/types/render/fuzzy.d.ts +21 -0
- package/lib/types/render/projection.d.ts +45 -4
- package/lib/types/session-directory.d.ts +48 -13
- package/lib/types/store.d.ts +3 -0
- package/package.json +168 -162
- package/src/app.ts +479 -198
- package/src/attachments.ts +110 -11
- package/src/commands.ts +35 -5
- package/src/index.ts +1868 -1779
- package/src/internals.ts +61 -40
- package/src/provider-settings.ts +12 -12
- package/src/render/animations.ts +606 -403
- package/src/render/export.ts +13 -3
- package/src/render/fuzzy.ts +83 -0
- package/src/render/projection.ts +1833 -1621
- package/src/session-directory.ts +94 -16
- package/src/skills.ts +23 -9
- package/src/store.ts +39 -1
- package/src/subagents.ts +26 -3
package/src/render/export.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { assertNever } from '@deepseek-ai/dsh-util-values'
|
|
10
|
-
import { imageLabels, type TranscriptView } from './projection.ts'
|
|
10
|
+
import { fileLabels, imageLabels, type TranscriptView } from './projection.ts'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Render the transcript as a standalone markdown document.
|
|
@@ -15,6 +15,10 @@ import { imageLabels, type TranscriptView } from './projection.ts'
|
|
|
15
15
|
* @param sessionId - the full session identity for the header.
|
|
16
16
|
* @returns the complete markdown text.
|
|
17
17
|
*/
|
|
18
|
+
/** Both user and queued rows export the same attachment label block. */
|
|
19
|
+
const attachmentLabels = (entry: { images?: readonly unknown[]; files?: readonly unknown[] }): string =>
|
|
20
|
+
[imageLabels(entry.images as never), fileLabels(entry.files as never)].filter(label => label !== '').join('\n')
|
|
21
|
+
|
|
18
22
|
export function buildExportMarkdown(view: TranscriptView, sessionId: string): string {
|
|
19
23
|
const out: string[] = [
|
|
20
24
|
view.title === ''
|
|
@@ -23,13 +27,19 @@ export function buildExportMarkdown(view: TranscriptView, sessionId: string): st
|
|
|
23
27
|
`> session ${sessionId}`,
|
|
24
28
|
'',
|
|
25
29
|
]
|
|
30
|
+
// The effective system prompt (v3 surface nodes) heads the export in a
|
|
31
|
+
// collapsed block: visible when audited, out of the way when scrolled.
|
|
32
|
+
if (view.systemPrompt !== '') {
|
|
33
|
+
out.push('<details><summary>system prompt</summary>', '', view.systemPrompt, '', '</details>', '')
|
|
34
|
+
}
|
|
26
35
|
for (const entry of view.entries) {
|
|
27
36
|
switch (entry.kind) {
|
|
28
37
|
case 'user':
|
|
29
38
|
if (entry.notice) {
|
|
30
39
|
out.push(`> ⤷ context: ${entry.text}`, '')
|
|
31
40
|
} else {
|
|
32
|
-
|
|
41
|
+
const attachments = attachmentLabels(entry)
|
|
42
|
+
out.push('## user', '', entry.text, ...(attachments === '' ? [] : [attachments]), '')
|
|
33
43
|
}
|
|
34
44
|
break
|
|
35
45
|
case 'assistant':
|
|
@@ -68,7 +78,7 @@ export function buildExportMarkdown(view: TranscriptView, sessionId: string): st
|
|
|
68
78
|
break
|
|
69
79
|
case 'pending':
|
|
70
80
|
// Codex PendingSteer: queued prompts export like ordinary user rows.
|
|
71
|
-
out.push('## user', '', entry.text, ...(
|
|
81
|
+
out.push('## user', '', entry.text, ...(attachmentLabels(entry) === '' ? [] : [attachmentLabels(entry)]), '')
|
|
72
82
|
break
|
|
73
83
|
default:
|
|
74
84
|
assertNever(entry, 'transcript entry kind')
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fuzzy ranking for `/` and `@` menu candidates: the query must be a
|
|
3
|
+
* case-insensitive ordered subsequence of the candidate name. Prefix hits
|
|
4
|
+
* rank first, then the strongest alignment score, then the source order of
|
|
5
|
+
* the input. Ported from the upstream web client's ui-primitives
|
|
6
|
+
* (rank-by-name.ts) so the terminal matches the web menu's discovery feel;
|
|
7
|
+
* the algorithm is unchanged, only the module's home moved.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-code/render/fuzzy
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** One match with its stable source position. */
|
|
13
|
+
interface Ranked<T> {
|
|
14
|
+
readonly item: T
|
|
15
|
+
readonly index: number
|
|
16
|
+
readonly prefix: boolean
|
|
17
|
+
readonly score: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Extra weight for name starts and separator boundaries. */
|
|
21
|
+
function boundaryBonus(name: string, index: number): number {
|
|
22
|
+
return index === 0 || name.charAt(index - 1) === '-' || name.charAt(index - 1) === '_' ? 8 : 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Score the strongest ordered-subsequence alignment in O(name × query).
|
|
27
|
+
* Boundary and adjacent matches earn weight; skipped and leading characters
|
|
28
|
+
* cost weight. Undefined when the query is not a subsequence of the name.
|
|
29
|
+
*/
|
|
30
|
+
function alignmentScore(name: string, query: string): number | undefined {
|
|
31
|
+
if (query.length > name.length) return undefined
|
|
32
|
+
const noMatch = Number.NEGATIVE_INFINITY
|
|
33
|
+
let previous = Array<number>(name.length).fill(noMatch)
|
|
34
|
+
for (let index = 0; index < name.length; index++) {
|
|
35
|
+
if (name.charAt(index) === query.charAt(0)) previous[index] = 1 + boundaryBonus(name, index) - index
|
|
36
|
+
}
|
|
37
|
+
for (let queryIndex = 1; queryIndex < query.length; queryIndex++) {
|
|
38
|
+
const current = Array<number>(name.length).fill(noMatch)
|
|
39
|
+
// Sweep the previous row once: `left` is its score one character back
|
|
40
|
+
// (the adjacent continuation), `leftLeft` two back (the earliest gapped one).
|
|
41
|
+
let left = noMatch
|
|
42
|
+
let leftLeft = noMatch
|
|
43
|
+
let bestGapped = noMatch
|
|
44
|
+
for (const [index, prior] of previous.entries()) {
|
|
45
|
+
if (leftLeft !== noMatch) bestGapped = Math.max(bestGapped, leftLeft + index - 2)
|
|
46
|
+
if (name.charAt(index) === query.charAt(queryIndex)) {
|
|
47
|
+
const bonus = 1 + boundaryBonus(name, index)
|
|
48
|
+
let score = noMatch
|
|
49
|
+
if (left !== noMatch) score = left + bonus + 4
|
|
50
|
+
if (bestGapped !== noMatch) score = Math.max(score, bestGapped + bonus + 1 - index)
|
|
51
|
+
current[index] = score
|
|
52
|
+
}
|
|
53
|
+
leftLeft = left
|
|
54
|
+
left = prior
|
|
55
|
+
}
|
|
56
|
+
previous = current
|
|
57
|
+
}
|
|
58
|
+
let best = noMatch
|
|
59
|
+
for (const score of previous) best = Math.max(best, score)
|
|
60
|
+
return best === noMatch ? undefined : best
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Rank named items by a menu query.
|
|
65
|
+
* @param items - candidates in source order (the caller's composition order
|
|
66
|
+
* is the final tie-breaker, e.g. local commands before registry entries).
|
|
67
|
+
* @param rawQuery - the text typed after the trigger, matched case-insensitively.
|
|
68
|
+
* @returns the matching items: prefix hits first, then by alignment score,
|
|
69
|
+
* then in source order. The input list itself for an empty query.
|
|
70
|
+
*/
|
|
71
|
+
export function rankByName<T extends { readonly name: string }>(items: readonly T[], rawQuery: string): readonly T[] {
|
|
72
|
+
const query = rawQuery.toLowerCase()
|
|
73
|
+
if (query === '') return items
|
|
74
|
+
const ranked: Ranked<T>[] = []
|
|
75
|
+
items.forEach((item, index) => {
|
|
76
|
+
const name = item.name.toLowerCase()
|
|
77
|
+
const score = alignmentScore(name, query)
|
|
78
|
+
if (score !== undefined) ranked.push({ item, index, prefix: name.startsWith(query), score })
|
|
79
|
+
})
|
|
80
|
+
ranked.sort((left, right) =>
|
|
81
|
+
Number(right.prefix) - Number(left.prefix) || right.score - left.score || left.index - right.index)
|
|
82
|
+
return ranked.map(match => match.item)
|
|
83
|
+
}
|