dsh-code 0.4.0 → 0.6.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/README.en.md +217 -0
- package/README.md +216 -67
- package/bin/deepseek.mjs +70 -0
- package/cordis.patch.yml +62 -6
- package/lib/devtools-CdTl3MNy.mjs +3643 -0
- package/lib/index.mjs +27467 -673
- package/lib/rolldown-runtime-CMFfr-1z.mjs +26 -0
- package/lib/startup.mjs +34 -17
- package/lib/types/app.d.ts +29 -1
- package/lib/types/commands.d.ts +2 -0
- package/lib/types/history.d.ts +79 -0
- package/lib/types/index.d.ts +5 -5
- package/lib/types/internals.d.ts +2 -0
- package/lib/types/kernel-panels.d.ts +48 -0
- package/lib/types/plugin-inventory.d.ts +11 -0
- package/lib/types/presets.d.ts +32 -0
- package/lib/types/render/animations.d.ts +10 -1
- package/lib/types/render/inspector.d.ts +6 -0
- package/lib/types/render/projection.d.ts +21 -1
- package/lib/types/render/status.d.ts +131 -14
- package/lib/types/render/text.d.ts +9 -0
- package/lib/types/session-directory.d.ts +54 -0
- package/lib/types/session-switch.d.ts +17 -0
- package/lib/types/skills.d.ts +2 -0
- package/lib/types/startup.d.ts +11 -1
- package/package.json +117 -112
- package/src/app.ts +2543 -1969
- package/src/commands.ts +15 -1
- package/src/history.ts +136 -0
- package/src/index.ts +550 -155
- package/src/internals.ts +5 -0
- package/src/kernel-panels.ts +419 -0
- package/src/plugin-inventory.ts +47 -0
- package/src/presets.ts +64 -0
- package/src/render/animations.ts +14 -1
- package/src/render/export.ts +4 -0
- package/src/render/inspector.ts +23 -5
- package/src/render/lines.ts +21 -10
- package/src/render/markdown.ts +15 -1
- package/src/render/projection.ts +71 -8
- package/src/render/status.ts +522 -65
- package/src/render/text.ts +34 -6
- package/src/session-directory.ts +102 -0
- package/src/session-switch.ts +58 -0
- package/src/skills.ts +20 -7
- package/src/startup.ts +38 -20
- package/src/whale-glyph.ts +23 -23
- package/README.zh.md +0 -65
- package/src/pictures/1.png +0 -0
package/src/render/lines.ts
CHANGED
|
@@ -143,16 +143,27 @@ export function transcriptEntryLines(entry: TranscriptEntry, columns: number): r
|
|
|
143
143
|
lineSegment(entry.notice ? '⤷ ' : '❯ ', entry.notice ? 'dim' : 'brand'),
|
|
144
144
|
lineSegment(entry.text, entry.notice ? 'dim' : 'plain'),
|
|
145
145
|
], width)
|
|
146
|
-
case '
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
case 'pending':
|
|
147
|
+
// Codex PendingSteer: a queued prompt renders exactly like an ordinary
|
|
148
|
+
// user row, so the durable user/message retires it without any flicker.
|
|
149
|
+
return styledLines([
|
|
150
|
+
lineSegment('❯ ', 'brand'),
|
|
151
|
+
lineSegment(entry.text, 'plain'),
|
|
152
|
+
], width)
|
|
153
|
+
case 'assistant': {
|
|
154
|
+
const reasoning = entry.reasoning === ''
|
|
155
|
+
? []
|
|
156
|
+
: styledLines([
|
|
157
|
+
lineSegment(' ✻ ', 'dimItalic'),
|
|
158
|
+
lineSegment(entry.reasoning, 'dimItalic'),
|
|
159
|
+
], width)
|
|
160
|
+
// Every reply row carries the composer's two-column gutter, so reply
|
|
161
|
+
// text aligns with the input cursor (Codex LIVE_PREFIX alignment); the
|
|
162
|
+
// wrap budget shrinks by the same amount so no line double-wraps.
|
|
163
|
+
const body = markdownLines(entry.text, Math.max(10, width - 2))
|
|
164
|
+
.map(line => ({ segments: [{ text: ' ', style: 'plain' as const }, ...line.segments] }))
|
|
165
|
+
return [...reasoning, ...body]
|
|
166
|
+
}
|
|
156
167
|
case 'tool': {
|
|
157
168
|
const mark = entry.state === 'running' ? '●' : entry.state === 'error' ? '⨯' : '⏺'
|
|
158
169
|
const markStyle: LineStyle = entry.state === 'running' ? 'brand' : entry.state === 'error' ? 'error' : 'success'
|
package/src/render/markdown.ts
CHANGED
|
@@ -166,11 +166,18 @@ const ORDERED = /^\s*(\d+)[.)]\s+(.*)$/u
|
|
|
166
166
|
/** Render markdown text into styled lines of at most `width` columns. */
|
|
167
167
|
export function renderMarkdown(text: string, width: number): readonly MdLine[] {
|
|
168
168
|
const lines: MdLine[] = []
|
|
169
|
+
let separatorPending = false
|
|
169
170
|
const push = (segments: readonly MdSegment[]): void => {
|
|
170
171
|
for (const wrapped of wrapSegments(segments, Math.max(10, width))) {
|
|
171
172
|
lines.push({ segments: merge(wrapped) })
|
|
172
173
|
}
|
|
173
174
|
}
|
|
175
|
+
const startBlock = (): void => {
|
|
176
|
+
if (separatorPending && lines.length > 0 && lines.at(-1)?.segments.length !== 0) {
|
|
177
|
+
lines.push({ segments: [] })
|
|
178
|
+
}
|
|
179
|
+
separatorPending = false
|
|
180
|
+
}
|
|
174
181
|
const raw = text.replaceAll('\r', '')
|
|
175
182
|
const source = raw.split('\n')
|
|
176
183
|
let index = 0
|
|
@@ -178,6 +185,14 @@ export function renderMarkdown(text: string, width: number): readonly MdLine[] {
|
|
|
178
185
|
const line = source[index] ?? ''
|
|
179
186
|
index += 1
|
|
180
187
|
|
|
188
|
+
// Preserve one deliberate row between source blocks. Repeated blank
|
|
189
|
+
// lines collapse, and leading/trailing whitespace never grows output.
|
|
190
|
+
if (line.trim() === '') {
|
|
191
|
+
separatorPending = lines.length > 0
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
startBlock()
|
|
195
|
+
|
|
181
196
|
// Fenced code block: verbatim lines in code style, language label first.
|
|
182
197
|
const fence = FENCE.exec(line)
|
|
183
198
|
if (fence !== null) {
|
|
@@ -191,7 +206,6 @@ export function renderMarkdown(text: string, width: number): readonly MdLine[] {
|
|
|
191
206
|
continue
|
|
192
207
|
}
|
|
193
208
|
|
|
194
|
-
if (line.trim() === '') continue
|
|
195
209
|
if (RULE.test(line.trim())) {
|
|
196
210
|
push([seg(` ${'─'.repeat(Math.max(1, Math.floor(width / 4)))}`, 'dim')])
|
|
197
211
|
continue
|
package/src/render/projection.ts
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* @module @deepseek-ai/dsh-tui/render/projection
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { boundContextSummary, type ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
10
|
+
import { boundContextSummary, type ContentBlock, type MessageId } from '@deepseek-ai/dsh-llm'
|
|
11
11
|
import type { SessionEvent, TodoItem } from '@deepseek-ai/dsh-session'
|
|
12
12
|
// Type-only imports merge the plugin-owned SessionEventMap variants
|
|
13
|
-
// (command/*, compaction/*, goal/change, llm/retry*,
|
|
14
|
-
// permission/preset, sandbox/mode, session/title) into the union
|
|
15
|
-
// reducer switches on.
|
|
13
|
+
// (agent/inbox/spliced, command/*, compaction/*, goal/change, llm/retry*,
|
|
14
|
+
// plan/mode, permission/preset, sandbox/mode, session/title) into the union
|
|
15
|
+
// this reducer switches on.
|
|
16
|
+
import type {} from '@deepseek-ai/dsh-agent'
|
|
16
17
|
import type {} from '@deepseek-ai/dsh-commands'
|
|
17
18
|
import type {} from '@deepseek-ai/dsh-compaction'
|
|
18
19
|
import type {} from '@deepseek-ai/dsh-goal'
|
|
@@ -43,6 +44,17 @@ export interface UserEntry {
|
|
|
43
44
|
notice: boolean
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** One user message waiting in the agent inbox (the web's queued-message row). */
|
|
48
|
+
export interface PendingEntry {
|
|
49
|
+
kind: 'pending'
|
|
50
|
+
/** Stable message identity shared with the durable `user/message` that retires it. */
|
|
51
|
+
messageId: MessageId
|
|
52
|
+
/** Which inbox list holds the message: steering is consumed at the next step boundary. */
|
|
53
|
+
target: 'next-turn' | 'next-step'
|
|
54
|
+
/** Full message text — Codex PendingSteer renders queued prompts exactly like user rows. */
|
|
55
|
+
text: string
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
/** One assembled assistant reply. */
|
|
47
59
|
export interface AssistantEntry {
|
|
48
60
|
kind: 'assistant'
|
|
@@ -139,7 +151,7 @@ export interface FilesEntry {
|
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
/** Ordered transcript items the renderer draws. */
|
|
142
|
-
export type TranscriptEntry = UserEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry
|
|
154
|
+
export type TranscriptEntry = UserEntry | PendingEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry
|
|
143
155
|
|
|
144
156
|
/** The live goal the status line badges, folded from `goal/change`. */
|
|
145
157
|
export interface GoalFold {
|
|
@@ -223,6 +235,12 @@ export interface TranscriptView {
|
|
|
223
235
|
sandbox: string
|
|
224
236
|
/** Current long-running goal folded from the last `goal/change`, undefined when cleared. */
|
|
225
237
|
goal: GoalFold | undefined
|
|
238
|
+
/**
|
|
239
|
+
* Ordered live message ids per inbox target, mirrored from
|
|
240
|
+
* `agent/inbox/spliced` exactly like the upstream Inbox projection — the
|
|
241
|
+
* coordinates later removals resolve against.
|
|
242
|
+
*/
|
|
243
|
+
pending: { 'next-turn': readonly string[]; 'next-step': readonly string[] }
|
|
226
244
|
/**
|
|
227
245
|
* Fold-internal timing anchors, never rendered: open step and tool-call
|
|
228
246
|
* start timestamps the next `assistant/message` / `tool/result` resolves
|
|
@@ -256,11 +274,17 @@ export function createTranscriptView(): TranscriptView {
|
|
|
256
274
|
title: '',
|
|
257
275
|
sandbox: '',
|
|
258
276
|
goal: undefined,
|
|
277
|
+
pending: { 'next-turn': [], 'next-step': [] },
|
|
259
278
|
stats: { turns: 0, steps: 0, llmMs: 0, toolMs: 0, usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0 }, lastPromptTokens: 0, contextWindow: 0, ttftMs: 0, ttftSteps: 0, decodeMs: 0, decodeTokens: 0 },
|
|
260
279
|
anchors: { stepStart: new Map(), toolStart: new Map(), firstChunkAt: new Map(), compactionTokens: new Map(), lastPruneTokens: 0, turnFiles: new Map() },
|
|
261
280
|
}
|
|
262
281
|
}
|
|
263
282
|
|
|
283
|
+
/** Full prompt text of a queued message (identical to the durable user row it retires into). */
|
|
284
|
+
function pendingText(content: readonly ContentBlock[]): string {
|
|
285
|
+
return textOf(content)
|
|
286
|
+
}
|
|
287
|
+
|
|
264
288
|
/**
|
|
265
289
|
* Fold one session event into an updated view (copy-on-write).
|
|
266
290
|
* @param view - the view before the event.
|
|
@@ -270,17 +294,56 @@ export function createTranscriptView(): TranscriptView {
|
|
|
270
294
|
export function projectEvent(view: TranscriptView, event: SessionEvent): TranscriptView {
|
|
271
295
|
switch (event.type) {
|
|
272
296
|
case 'user/message': {
|
|
297
|
+
// A queued row retires when its durable user message lands (the agent
|
|
298
|
+
// claims the inbox and logs the same message identity) — the transient
|
|
299
|
+
// steering/queued preview yields to the real transcript entry.
|
|
300
|
+
const message = event.data
|
|
301
|
+
let entries = view.entries
|
|
302
|
+
let pending = view.pending
|
|
303
|
+
for (const target of ['next-turn', 'next-step'] as const) {
|
|
304
|
+
const index = pending[target].indexOf(message.id)
|
|
305
|
+
if (index < 0) continue
|
|
306
|
+
pending = { ...pending, [target]: pending[target].filter((_, i) => i !== index) }
|
|
307
|
+
entries = entries.filter(entry => !(entry.kind === 'pending' && entry.messageId === message.id))
|
|
308
|
+
}
|
|
273
309
|
// Injected context (plugin/model-continuation sources) stays collapsed
|
|
274
310
|
// to a bounded notice row, exactly like collapsed transcript context
|
|
275
311
|
// elsewhere in the product; only direct human prompts render in full.
|
|
276
|
-
const message = event.data
|
|
277
312
|
if (message.source.kind === 'user') {
|
|
278
|
-
return { ...view, entries: [...
|
|
313
|
+
return { ...view, pending, entries: [...entries, { kind: 'user', text: textOf(message.content), notice: false }] }
|
|
279
314
|
}
|
|
280
315
|
const notice = message.source.kind === 'plugin' && message.source.form === 'notice'
|
|
281
316
|
? message.source.summary
|
|
282
317
|
: message.source.kind
|
|
283
|
-
return { ...view, entries: [...
|
|
318
|
+
return { ...view, pending, entries: [...entries, { kind: 'user', text: boundContextSummary(notice), notice: true }] }
|
|
319
|
+
}
|
|
320
|
+
case 'agent/inbox/spliced': {
|
|
321
|
+
// The durable inbox mutation (web queue-mirror contract, event-sourced):
|
|
322
|
+
// removals drop the projected rows at their inbox coordinates, inserted
|
|
323
|
+
// messages gain a pending row at their log position.
|
|
324
|
+
const { target, start, removedCount = 0, inserted } = event.data
|
|
325
|
+
const ids = view.pending[target]
|
|
326
|
+
const removed = ids.slice(start, start + removedCount)
|
|
327
|
+
const nextIds = [
|
|
328
|
+
...ids.slice(0, start),
|
|
329
|
+
...ids.slice(start + removedCount),
|
|
330
|
+
...inserted.map(message => message.id),
|
|
331
|
+
]
|
|
332
|
+
let entries = view.entries
|
|
333
|
+
if (removed.length > 0) {
|
|
334
|
+
const removedSet = new Set(removed)
|
|
335
|
+
entries = entries.filter(entry =>
|
|
336
|
+
!(entry.kind === 'pending' && entry.target === target && removedSet.has(entry.messageId)))
|
|
337
|
+
}
|
|
338
|
+
for (const message of inserted) {
|
|
339
|
+
entries = [...entries, {
|
|
340
|
+
kind: 'pending',
|
|
341
|
+
messageId: message.id,
|
|
342
|
+
target,
|
|
343
|
+
text: pendingText(message.content),
|
|
344
|
+
}]
|
|
345
|
+
}
|
|
346
|
+
return { ...view, entries, pending: { ...view.pending, [target]: nextIds } }
|
|
284
347
|
}
|
|
285
348
|
case 'assistant/chunk': {
|
|
286
349
|
const chunk = event.data.chunk
|