dsh-code 0.9.1 → 1.0.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.
Files changed (59) hide show
  1. package/README.en.md +29 -13
  2. package/README.md +264 -248
  3. package/bin/deepseek.mjs +100 -6
  4. package/cordis.patch.yml +29 -1
  5. package/lib/index.mjs +2223 -687
  6. package/lib/startup.mjs +21 -11
  7. package/lib/{theme-BEi4i_aN.mjs → theme-DCT8Y2xf.mjs} +13 -9
  8. package/lib/types/app.d.ts +66 -14
  9. package/lib/types/attachments.d.ts +7 -0
  10. package/lib/types/editor.d.ts +6 -0
  11. package/lib/types/fork.d.ts +8 -0
  12. package/lib/types/git-workflow.d.ts +23 -0
  13. package/lib/types/index.d.ts +6 -0
  14. package/lib/types/kernel-panels.d.ts +39 -0
  15. package/lib/types/keyboard.d.ts +43 -0
  16. package/lib/types/mentions.d.ts +28 -38
  17. package/lib/types/presets.d.ts +1 -3
  18. package/lib/types/provider-settings.d.ts +16 -0
  19. package/lib/types/render/animations.d.ts +10 -39
  20. package/lib/types/render/editor.d.ts +137 -0
  21. package/lib/types/render/export.d.ts +1 -1
  22. package/lib/types/render/lines.d.ts +6 -2
  23. package/lib/types/render/markdown.d.ts +3 -1
  24. package/lib/types/render/projection.d.ts +29 -3
  25. package/lib/types/render/status.d.ts +5 -12
  26. package/lib/types/session-directory.d.ts +1 -3
  27. package/lib/types/startup.d.ts +14 -11
  28. package/lib/types/store.d.ts +11 -9
  29. package/lib/types/subagents.d.ts +3 -3
  30. package/lib/types/theme.d.ts +14 -1
  31. package/lib/types/version.d.ts +15 -2
  32. package/package.json +153 -141
  33. package/src/app.ts +4455 -3917
  34. package/src/attachments.ts +44 -0
  35. package/src/editor.ts +51 -0
  36. package/src/fork.ts +31 -0
  37. package/src/git-workflow.ts +87 -0
  38. package/src/index.ts +1510 -1374
  39. package/src/internals.ts +14 -1
  40. package/src/kernel-panels.ts +914 -798
  41. package/src/keyboard.ts +125 -0
  42. package/src/mentions.ts +72 -117
  43. package/src/presets.ts +1 -4
  44. package/src/provider-settings.ts +94 -0
  45. package/src/render/animations.ts +25 -55
  46. package/src/render/editor.ts +398 -0
  47. package/src/render/export.ts +79 -79
  48. package/src/render/lines.ts +342 -236
  49. package/src/render/markdown.ts +99 -26
  50. package/src/render/projection.ts +102 -19
  51. package/src/render/status.ts +713 -650
  52. package/src/render/text.ts +150 -150
  53. package/src/render/tool-detail.ts +3 -1
  54. package/src/session-directory.ts +3 -3
  55. package/src/startup.ts +136 -119
  56. package/src/store.ts +23 -11
  57. package/src/subagents.ts +13 -5
  58. package/src/theme.ts +214 -206
  59. package/src/version.ts +58 -1
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Terminal animation frame tables derived from the web design language:
3
3
  * the StateDot "ongoing" pixel chase (3×3 ring, 125ms flat-hold brightness
4
- * steps, 1s cycle) becomes the single-cell stepped pulse below and the
5
- * full-ring clockwise braille chase in {@link BUSY_CHASE_FRAMES}, and the
6
- * streaming caret blink is the Claude-Code convention.
4
+ * steps, 1s cycle) becomes the full-ring clockwise braille chase in
5
+ * {@link BUSY_CHASE_FRAMES}, and the streaming caret blink is the
6
+ * Claude-Code convention.
7
7
  *
8
8
  * The DeepSeek model-switch easter egg ports Codex's effort-ignition "Wave"
9
9
  * style (`codex-rs/tui/src/bottom_pane/effort_ignition_styles.rs`): switching
@@ -17,10 +17,6 @@
17
17
  * @module @deepseek-ai/dsh-code/render/animations
18
18
  */
19
19
  import type { RgbTriple } from '../theme.ts';
20
- /** Single-cell stepped pulse: flat holds mirroring the web's 125ms keyframes. */
21
- export declare const PULSE_FRAMES: readonly ["█", "█", "▆", "▃", "▁", "▃", "▆", "█"];
22
- /** Pulse frame for a monotonic tick. */
23
- export declare function pulseFrame(tick: number): string;
24
20
  /**
25
21
  * The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
26
22
  * ring trail clockwise around the eight outer positions, one braille glyph
@@ -59,15 +55,8 @@ export type DeepseekWaveTier = 'flash' | 'deepseek' | 'unknown';
59
55
  * One style is picked at random per trigger and never repeats the previous.
60
56
  */
61
57
  export type DeepseekWaveStyle = 'wave' | 'aurora' | 'pulse';
62
- /** All styles in canonical order, for random selection and tests. */
63
- export declare const DEEPSEEK_WAVE_STYLES: readonly DeepseekWaveStyle[];
64
58
  /** Wave half-width in columns — Codex WAVE_HALF_WIDTH (9). */
65
59
  export declare const WAVE_HALF_WIDTH = 9;
66
- /** Pulse ring half-width in columns — Codex PULSE_HALF_WIDTH (4.5). */
67
- export declare const PULSE_HALF_WIDTH = 4.5;
68
- /** Sparkle start and frame cadence — Codex SPARK_START / SPARK_FRAME. */
69
- export declare const SPARK_START_MS = 900;
70
- export declare const SPARK_FRAME_MS = 100;
71
60
  /** Sparkle glyphs in frame order — Codex SPARK_GLYPHS (`· ✦ ✧`). */
72
61
  export declare const SPARK_GLYPHS: readonly ["·", "✦", "✧"];
73
62
  /**
@@ -77,8 +66,6 @@ export declare const SPARK_GLYPHS: readonly ["·", "✦", "✧"];
77
66
  */
78
67
  export type DeepseekWaveBand = readonly [number, number, number];
79
68
  export declare const DEEPSEEK_WAVE_BANDS: Readonly<Record<DeepseekWaveStyle, Readonly<Record<DeepseekWaveTier, readonly DeepseekWaveBand[]>>>>;
80
- /** Extra display time applied to every Codex ignition style. */
81
- export declare const DEEPSEEK_WAVE_DURATION_EXTENSION_MS = 200;
82
69
  /**
83
70
  * Total visible duration: the Codex ignition duration plus 200ms so its motion
84
71
  * remains readable in a busy terminal.
@@ -94,14 +81,6 @@ export declare function deepseekWaveDuration(tier: DeepseekWaveTier, style?: Dee
94
81
  * @returns a style different from `previous`.
95
82
  */
96
83
  export declare function deepseekWaveStyleRandom(previous: DeepseekWaveStyle | undefined): DeepseekWaveStyle;
97
- /**
98
- * Blank-cell background the wave tint blends toward — fixed approximations
99
- * of the terminal's default background, mirroring Codex's
100
- * `user_message_bg_rgb` (which derives a near-black / near-white bubble tint
101
- * from the terminal background). The Ink layer picks the active theme's one.
102
- */
103
- export declare const WAVE_BASE_DARK: RgbTriple;
104
- export declare const WAVE_BASE_LIGHT: RgbTriple;
105
84
  /**
106
85
  * Tier for a `provider/model` label: a model id containing `flash` runs the
107
86
  * single-band flash tier; everything else (pro/reasoner/chat) runs the
@@ -137,14 +116,16 @@ export declare function easeInOut(progress: number): number;
137
116
  */
138
117
  export declare function envelope(elapsed: number, total: number, fadeIn: number, fadeOut: number): number;
139
118
  /**
140
- * The background color for one composer-row column at a tick — Codex
119
+ * The background color for one composer-band column at a tick — Codex
141
120
  * `paint_bands` + `Canvas::tint` for all three styles. Bands overlap with a
142
121
  * max for Wave/Pulse and a SUM for Aurora (Codex differs by style), the
143
122
  * weighted hues mix per column (Wave/Pulse always end on hue 0), the tint
144
123
  * blends the mixed hue toward the blank-cell base at the style's alpha cap,
145
124
  * and Aurora applies its own fade envelope. Returns `null` when the column
146
125
  * should stay transparent, so the row returns to no `backgroundColor` on
147
- * both ends.
126
+ * both ends. With `rows > 1` each row samples the same timeline shifted by a
127
+ * per-row phase offset, so the crest cascades down the band instead of
128
+ * painting every row identically.
148
129
  * @param tick - wave frame (0, 1, … at DEEPSEEK_WAVE_TICK_MS).
149
130
  * @param column - column index in the content row (0..width-1).
150
131
  * @param width - content-row width in columns.
@@ -152,9 +133,11 @@ export declare function envelope(elapsed: number, total: number, fadeIn: number,
152
133
  * @param style - the ignition style.
153
134
  * @param hues - the tier's three hues.
154
135
  * @param base - the blank-cell base color the tint blends toward.
136
+ * @param row - row index in the band (0..rows-1; default 0 = old single-row).
137
+ * @param rows - band height in rows (default 1).
155
138
  * @returns the blended RGB background, or null for transparent.
156
139
  */
157
- export declare function deepseekWaveColumnBg(tick: number, column: number, width: number, tier: DeepseekWaveTier, style: DeepseekWaveStyle, hues: readonly [RgbTriple, RgbTriple, RgbTriple], base: RgbTriple): RgbTriple | null;
140
+ export declare function deepseekWaveColumnBg(tick: number, column: number, width: number, tier: DeepseekWaveTier, style: DeepseekWaveStyle, hues: readonly [RgbTriple, RgbTriple, RgbTriple], base: RgbTriple, row?: number, rows?: number): RgbTriple | null;
158
141
  /**
159
142
  * The sparkle glyph for a tick — Codex `spark_frame`, sampled on the same
160
143
  * proportionally slowed DeepSeek Wave timeline as the composer background.
@@ -163,18 +146,6 @@ export declare function deepseekWaveColumnBg(tick: number, column: number, width
163
146
  * @returns the sparkle glyph, or null outside the stretched tail window.
164
147
  */
165
148
  export declare function deepseekWaveSpark(tick: number): string | null;
166
- /**
167
- * The composer BORDER color at a tick: the frame breathes with the wave —
168
- * the palette's static dim blends toward the tier accent as the crest is
169
- * alive and back, so the frame glows up while the wave sweeps and settles to
170
- * dim on both ends (first==last frame==dim, no hard jump).
171
- * @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
172
- * @param tier - the wave tier.
173
- * @param hues - the tier's three hues; the border blends toward hues[0].
174
- * @param dim - the palette's static dim RGB (the resting border color).
175
- * @returns the blended border RGB.
176
- */
177
- export declare function deepseekWaveBorderColor(tick: number, tier: DeepseekWaveTier, style: DeepseekWaveStyle, hues: readonly [RgbTriple, RgbTriple, RgbTriple], dim: RgbTriple): RgbTriple;
178
149
  /**
179
150
  * Whether the `deepseek` wordmark rides the wave at this tick: it fades in
180
151
  * shortly after the first crest launches and out before the wave settles,
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Pure composer editor model with Codex textarea semantics: a grapheme
3
+ * cursor over a column-safe multiline layout, word/piece motion, single-entry
4
+ * kill + yank, and the shell-recall boundary gate that keeps Up/Down usable
5
+ * inside a multiline draft.
6
+ *
7
+ * The model is intentionally string-offset based (UTF-16 indices clamped to
8
+ * grapheme boundaries) so the React state stays two primitives
9
+ * (value, cursor) and every operation here stays pure and testable.
10
+ *
11
+ * Word motion deviates from Codex's UAX#29 segmentation in one deliberate
12
+ * way: a run of same-class characters is ONE piece, so a CJK run moves as a
13
+ * single word (two hanzi are one Alt+B step, not two).
14
+ *
15
+ * @module @deepseek-ai/dsh-code/render/editor
16
+ */
17
+ /** One grapheme cluster with its source span and display width in cells. */
18
+ export interface GraphemeSpan {
19
+ text: string;
20
+ start: number;
21
+ end: number;
22
+ width: number;
23
+ }
24
+ /**
25
+ * Split text into grapheme clusters. Falls back to code points when
26
+ * Intl.Segmenter is unavailable; the fallback still keeps surrogate pairs
27
+ * (emoji) atomic so the cursor can never split one.
28
+ */
29
+ export declare function splitGraphemes(text: string): readonly GraphemeSpan[];
30
+ /** Clamp a cursor offset to the nearest grapheme boundary (surrogates, ZWJ, marks stay whole). */
31
+ export declare function clampCursor(value: string, offset: number): number;
32
+ /**
33
+ * Delete the final grapheme cluster (append-only drafts without a cursor).
34
+ * Surrogate pairs and multi-codepoint emoji stay whole instead of leaving a
35
+ * lone trailing code unit behind.
36
+ */
37
+ export declare function deleteLastGrapheme(text: string): string;
38
+ /**
39
+ * Step the cursor by whole graphemes (negative steps left). The cursor is
40
+ * assumed to sit on a boundary; any drift is clamped first.
41
+ */
42
+ export declare function moveCursorBy(value: string, offset: number, delta: number): number;
43
+ /**
44
+ * Normalize text entering the draft: CRLF/CR become LF, tabs become two
45
+ * spaces (terminal tab stops are contextual and cannot join a deterministic
46
+ * row budget), and every other C0 control byte plus DEL is REMOVED — the
47
+ * draft is data, so a stray ESC (Windows Terminal file drops) disappears
48
+ * instead of rendering as literal backslash-x-1-b text. Newlines survive.
49
+ */
50
+ export declare function sanitizeDraftText(text: string): string;
51
+ /** One physical editor row: wrapped text plus its boundary map. */
52
+ export interface EditorRowModel {
53
+ /** Display text of the row (never contains `\n`; sanitized upstream). */
54
+ readonly text: string;
55
+ /** Source offset of the first grapheme on the row. */
56
+ readonly start: number;
57
+ /** Source offset just past the last grapheme on the row (before its newline). */
58
+ readonly end: number;
59
+ /** Boundary offsets on the row, start to end inclusive. */
60
+ readonly offsets: readonly number[];
61
+ /** Display column of each boundary; `columns[i]` pairs with `offsets[i]`. */
62
+ readonly columns: readonly number[];
63
+ /** Code-unit cut in `text` at each boundary; `cuts[i]` pairs with `offsets[i]`. */
64
+ readonly cuts: readonly number[];
65
+ }
66
+ /** The wrapped physical-row model of one draft. */
67
+ export interface EditorModel {
68
+ readonly rows: readonly EditorRowModel[];
69
+ readonly length: number;
70
+ }
71
+ /**
72
+ * Hard-wrap the draft into column-safe physical rows. Wide graphemes never
73
+ * split across rows (a grapheme that does not fit flushes the row first) and
74
+ * explicit newlines end their row without occupying a cell.
75
+ */
76
+ export declare function editorModel(value: string, columns: number): EditorModel;
77
+ /** Where a cursor offset renders: the physical row and its display column. */
78
+ export interface CaretSite {
79
+ row: number;
80
+ column: number;
81
+ }
82
+ /** Map a cursor offset to its caret site on the wrapped rows. */
83
+ export declare function caretSite(model: EditorModel, offset: number): CaretSite;
84
+ /**
85
+ * Move the caret across physical rows keeping a preferred display column
86
+ * (Codex `preferred_col`): horizontal moves reset the preference, vertical
87
+ * moves reuse it, clamped to each row's width.
88
+ */
89
+ export declare function moveCursorVertically(model: EditorModel, offset: number, preferredColumn: number, delta: number): number;
90
+ /** The start/end offsets of the logical line containing the cursor. */
91
+ export declare function lineBounds(value: string, offset: number): {
92
+ start: number;
93
+ end: number;
94
+ };
95
+ /**
96
+ * Codex `beginning_of_previous_word`: skip whitespace left, then land on the
97
+ * START of the trailing non-space piece (extending over separator pieces).
98
+ */
99
+ export declare function moveWordLeft(value: string, offset: number): number;
100
+ /**
101
+ * Codex `end_of_next_word`: skip whitespace right, then land on the END of
102
+ * the leading non-space piece (extending over separator pieces).
103
+ */
104
+ export declare function moveWordRight(value: string, offset: number): number;
105
+ /** One edit outcome: the next draft value, cursor, and killed span (if any). */
106
+ export interface EditResult {
107
+ value: string;
108
+ cursor: number;
109
+ /** Text removed into the kill buffer; undefined when nothing was killed. */
110
+ killed: string | undefined;
111
+ }
112
+ /** Delete the grapheme cluster before the cursor. */
113
+ export declare function deleteBackward(value: string, cursor: number): EditResult;
114
+ /** Delete the grapheme cluster at the cursor. */
115
+ export declare function deleteForward(value: string, cursor: number): EditResult;
116
+ /** Delete back to the start of the previous word (fills the kill buffer). */
117
+ export declare function deleteWordBackward(value: string, cursor: number): EditResult;
118
+ /** Delete forward to the end of the next word (fills the kill buffer). */
119
+ export declare function deleteWordForward(value: string, cursor: number): EditResult;
120
+ /** Ctrl+U: kill from the line start to the cursor; at BOL, kill the newline. */
121
+ export declare function killToLineStart(value: string, cursor: number): EditResult;
122
+ /** Ctrl+K: kill from the cursor to the line end; at EOL, kill the newline. */
123
+ export declare function killToLineEnd(value: string, cursor: number): EditResult;
124
+ /** Insert sanitized text at the cursor. */
125
+ export declare function insertText(value: string, cursor: number, text: string): EditResult;
126
+ /**
127
+ * Composer editor row budget: the editor itself never grows past this many
128
+ * physical rows; deeper drafts scroll internally to keep the caret visible.
129
+ * Short terminals collapse toward one row so the live transcript keeps room.
130
+ */
131
+ export declare function composerMaxRows(terminalRows: number): number;
132
+ /**
133
+ * Codex `should_handle_navigation`: Up/Down walk history only from an empty
134
+ * draft, or from a boundary of a draft that still exactly matches the last
135
+ * recalled entry. Any interior cursor position keeps vertical caret movement.
136
+ */
137
+ export declare function shouldRecallNavigate(value: string, cursor: number, lastRecalled: string | null): boolean;
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @module @deepseek-ai/dsh-code/render/export
7
7
  */
8
- import type { TranscriptView } from './projection.ts';
8
+ import { type TranscriptView } from './projection.ts';
9
9
  /**
10
10
  * Render the transcript as a standalone markdown document.
11
11
  * @param view - the folded transcript view to export.
@@ -1,5 +1,5 @@
1
1
  /** Width-safe styled physical rows for bounded terminal panels. */
2
- import type { TranscriptEntry } from './projection.ts';
2
+ import { type TranscriptEntry } from './projection.ts';
3
3
  import { type MdStyle } from './markdown.ts';
4
4
  /** Presentation classes mapped to Ink colors by the app boundary. */
5
5
  export type LineStyle = MdStyle | 'brand' | 'success' | 'error' | 'warn' | 'dimItalic';
@@ -33,5 +33,9 @@ export declare function reasoningLines(text: string, columns: number): readonly
33
33
  /**
34
34
  * Convert one durable transcript entry to its complete scrollable row model.
35
35
  * The source entry stays intact; only the caller's visible slice is rendered.
36
+ * Wrapped continuations keep a hanging indent aligned under each row's
37
+ * content (Codex history-cell alignment) instead of resetting to column 0.
36
38
  */
37
- export declare function transcriptEntryLines(entry: TranscriptEntry, columns: number): readonly StyledLine[];
39
+ export declare function transcriptEntryLines(entry: TranscriptEntry, columns: number, showReasoning?: boolean, reasoningToggleHint?: boolean): readonly StyledLine[];
40
+ /** Settled-history variant carrying the Ctrl+R reasoning fold. */
41
+ export declare function settledEntryLines(entry: TranscriptEntry, columns: number, showReasoning: boolean): readonly StyledLine[];
@@ -24,4 +24,6 @@ export interface MdLine {
24
24
  /** Visible width of a run in columns (CJK counts double). */
25
25
  export declare function visibleColumns(text: string): number;
26
26
  /** Render markdown text into styled lines of at most `width` columns. */
27
- export declare function renderMarkdown(text: string, width: number): readonly MdLine[];
27
+ export declare function renderMarkdown(text: string, width: number, options?: {
28
+ physicalWrap?: boolean;
29
+ }): readonly MdLine[];
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module @deepseek-ai/dsh-tui/render/projection
8
8
  */
9
- import { type MessageId } from '@deepseek-ai/dsh-llm';
9
+ import { type ImageBlock, type MessageId } from '@deepseek-ai/dsh-llm';
10
10
  import type { SessionEvent, TodoItem } from '@deepseek-ai/dsh-session';
11
11
  import { type ToolDetail } from './tool-detail.ts';
12
12
  /** One user prompt line. */
@@ -17,6 +17,8 @@ export interface UserEntry {
17
17
  /** True for collapsed injected context (plugin/continuation notices), which
18
18
  * the renderer marks with a dim ↳ instead of the user ❯ prompt. */
19
19
  notice: boolean;
20
+ /** Durable image references carried by this prompt. */
21
+ images?: readonly ImageBlock['attachment'][];
20
22
  }
21
23
  /** One user message waiting in the agent inbox (the web's queued-message row). */
22
24
  export interface PendingEntry {
@@ -27,20 +29,32 @@ export interface PendingEntry {
27
29
  target: 'next-turn' | 'next-step';
28
30
  /** Full message text — Codex PendingSteer renders queued prompts exactly like user rows. */
29
31
  text: string;
32
+ /** Durable image references queued with this prompt. */
33
+ images?: readonly ImageBlock['attachment'][];
30
34
  }
31
- /** One assembled assistant reply. */
35
+ /** One authoritative assembled assistant reply. */
32
36
  export interface AssistantEntry {
33
37
  kind: 'assistant';
34
38
  /** Joined text blocks of the assistant message. */
35
39
  text: string;
36
- /** Joined reasoning blocks of the same message, empty when the model thought out loud. */
40
+ /** Joined reasoning blocks from the same assembled message. */
37
41
  reasoning: string;
42
+ /** True when a cancelled stream's delivered prefix was finalized as this
43
+ * entry (rc.8 `assistant/message.interrupted`) — rendered with a marker. */
44
+ interrupted?: true;
38
45
  }
39
46
  /** One model-requested tool invocation and its settled state. */
40
47
  export interface ToolEntry {
41
48
  kind: 'tool';
42
49
  /** Correlation id shared with the matching `tool/result`. */
43
50
  callId: string;
51
+ /**
52
+ * Global tool-call ordinal across the whole transcript (1, 2, 3…, never
53
+ * reset between turns). The tool-card badge and every error line that
54
+ * references the failed call share this number, so "call N" in an error
55
+ * always names the exact card the badge shows.
56
+ */
57
+ ordinal: number;
44
58
  /** Tool name as the model addressed it. */
45
59
  name: string;
46
60
  /** Raw arguments JSON string exactly as the model produced it. */
@@ -203,6 +217,12 @@ export interface TranscriptView {
203
217
  streamingReasoning: string;
204
218
  /** Latest whole-list todo snapshot from `todo/write`, empty when none. */
205
219
  todos: readonly TodoItem[];
220
+ /**
221
+ * Global tool-call ordinal counter: the number the NEXT `tool/call` lands
222
+ * with (1-based). Never reset, so the counter and the badges/error lines
223
+ * stay consistent across turns and resumed sessions.
224
+ */
225
+ toolCallOrdinal: number;
206
226
  /** True while a durable turn is open (`turn/start` … `turn/end`). */
207
227
  busy: boolean;
208
228
  /** `turn/start` time of the open turn (0 while idle) — the web TurnStatus clock anchor. */
@@ -254,6 +274,10 @@ export interface TranscriptView {
254
274
  turnTools: Map<number, Set<string>>;
255
275
  };
256
276
  }
277
+ /** Human-readable bounded image labels for transcript, inspector, and export surfaces. */
278
+ export declare function imageLabels(images: readonly ImageBlock['attachment'][] | undefined): string;
279
+ /** Prompt text with its durable image labels, without exposing local paths or bytes. */
280
+ export declare function promptDisplayText(entry: Pick<UserEntry | PendingEntry, 'text' | 'images'>): string;
257
281
  /** A fresh, empty transcript view. */
258
282
  export declare function createTranscriptView(): TranscriptView;
259
283
  /**
@@ -302,6 +326,8 @@ export interface ReplayAccumulator {
302
326
  streaming: string;
303
327
  streamingReasoning: string;
304
328
  todos: readonly TodoItem[];
329
+ /** Global tool-call ordinal counter (see `TranscriptView.toolCallOrdinal`). */
330
+ toolCallOrdinal: number;
305
331
  busy: boolean;
306
332
  busySince: number;
307
333
  model: string;
@@ -23,13 +23,6 @@ export declare function formatTokens(n: number): string;
23
23
  * @returns display string.
24
24
  */
25
25
  export declare function formatDuration(ms: number): string;
26
- /**
27
- * Compact decode rate: one decimal under a hundred, whole below a thousand,
28
- * then thousands (15.3 / 124 / 1.2K).
29
- * @param n - tokens per second.
30
- * @returns display string.
31
- */
32
- export declare function formatRate(n: number): string;
33
26
  /**
34
27
  * Cache-hit share of billed prompt-side input.
35
28
  * @param usage - cumulative token totals.
@@ -80,13 +73,12 @@ export declare const STATUS_ITEM_SEPARATOR = " \u00B7 ";
80
73
  export declare const STATUS_CYCLE_HINT = " (shift+tab to cycle)";
81
74
  /**
82
75
  * Interior columns of the segmented context bar (content-type segments plus
83
- * the free tail whose right edge carries the usage readout). Fixed so the
84
- * row-2 drop ladder can pre-measure the group; the bar shrinks its labels and
85
- * readout inside this budget rather than asking the layout for more room.
76
+ * the free tail whose right edge carries the usage readout). The layout
77
+ * starts every bar at this width so the drop ladder can pre-measure the
78
+ * group, then shrinks the bar inside a tighter budget before dropping it
79
+ * (see CONTEXT_MIN_WIDTH) rather than asking the layout for more room.
86
80
  */
87
81
  export declare const CONTEXT_BAR_WIDTH = 24;
88
- /** Occupancy at which the usage readout flips from brand blue to amber. */
89
- export declare const CONTEXT_WARN_PERCENT = 90;
90
82
  /**
91
83
  * Render context occupancy as ONE stepless bar: a solid DeepSeek-blue fill
92
84
  * run, a dim dotted free track, and the usage readout riding the track's
@@ -193,4 +185,5 @@ export declare function permissionTone(permission: string): StatusTone;
193
185
  export declare function layoutStatusBar(facts: StatusFacts, stats: TranscriptStats, columns: number, options?: {
194
186
  busy?: boolean;
195
187
  items?: readonly string[];
188
+ contextWidth?: number;
196
189
  }): StatusLayout;
@@ -50,10 +50,8 @@ export interface SessionRow {
50
50
  readonly preset: string;
51
51
  readonly title?: string;
52
52
  }
53
- /** True when the header describes a subagent conversation (durable lineage). */
53
+ /** True only for delegated subagents; ordinary forks also carry lineage. */
54
54
  export declare function isSubagentSession(header: SessionHeader): boolean;
55
- /** Platform-consistent path equality for session cwd comparisons. */
56
- export declare function samePath(left: string | undefined, right: string): boolean;
57
55
  /**
58
56
  * Unique header match by exact id or unique id prefix (root and subagent
59
57
  * headers alike); the caller applies any lineage gate.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * The interactive terminal app's command-line provider: parses `--resume`,
3
- * `--continue`, `--session`, `--mode`, `--theme`, and `--help`, then
3
+ * `--continue`, `--session`, `--mode`, `--theme`, `--image`, an optional
4
+ * initial prompt, and `--help`, then
4
5
  * publishes {@link TUI_STARTUP_SERVICE} for the runner to consume lazily.
5
6
  * Follows the headless bundle's startup shape (a commander action publishing
6
7
  * a service through {@link parseCmdline}).
@@ -25,32 +26,33 @@ import { type ThemeName } from './theme.ts';
25
26
  export declare const name = "tui-startup";
26
27
  /** Services required before the invocation can be resolved. */
27
28
  export declare const inject: string[];
28
- /** Service provided by this plugin and injected by the terminal runner. */
29
- export declare const TUI_STARTUP_SERVICE = "tuiStartup";
30
29
  /** How the runner obtains its session identity. */
31
- export type TuiStartup = {
30
+ export type TuiStartup = ({
32
31
  readonly kind: 'fresh';
33
32
  readonly mode?: string;
34
- readonly theme?: ThemeName;
35
- } | {
33
+ } & TuiStartupInput) | ({
36
34
  readonly kind: 'named';
37
35
  readonly sessionId: string;
38
36
  readonly mode?: string;
39
- readonly theme?: ThemeName;
40
- } | {
37
+ } & TuiStartupInput) | ({
41
38
  readonly kind: 'resume';
42
39
  readonly sessionId: string;
43
- readonly theme?: ThemeName;
44
- } | {
40
+ } & TuiStartupInput) | ({
45
41
  readonly kind: 'latest';
42
+ } & TuiStartupInput);
43
+ interface TuiStartupInput {
46
44
  readonly theme?: ThemeName;
47
- };
45
+ readonly prompt?: string;
46
+ readonly images?: readonly string[];
47
+ }
48
48
  export interface TuiStartupOptions {
49
49
  readonly resume?: string;
50
50
  readonly continue?: boolean;
51
51
  readonly session?: string;
52
52
  readonly mode?: string;
53
53
  readonly theme?: ThemeName;
54
+ readonly prompt?: string;
55
+ readonly images?: readonly string[];
54
56
  }
55
57
  /** Pure option policy shared by Commander and tests. */
56
58
  export declare function resolveTuiStartup(options: TuiStartupOptions): TuiStartup;
@@ -60,3 +62,4 @@ export declare function resolveTuiStartup(options: TuiStartupOptions): TuiStartu
60
62
  * @param ctx - plugin context carrying the command line and exit request.
61
63
  */
62
64
  export declare function apply(ctx: Context): void;
65
+ export {};
@@ -5,15 +5,17 @@
5
5
  *
6
6
  * Notification coalescing: the fold stays synchronous — `getView()` always
7
7
  * returns the latest state the moment `apply` returns — but listener
8
- * notification is scheduled on a microtask and deduplicated, so N events
9
- * delivered inside one synchronous drain (the zai/GLM adapter drains its
10
- * token buffer in sub-millisecond bursts) produce ONE React re-render.
11
- * Synchronous per-event notification instead cascades one
12
- * `useSyncExternalStore` force-update per token inside a single flush; the
13
- * reconciler counts those as nested passive updates and floods React's
14
- * "Maximum update depth exceeded" warning past 50 events, besides rendering
15
- * the whole live tree once per token. A microtask keeps latency within the
16
- * same macrotask, before Ink's throttled paint.
8
+ * notification is frame-throttled (~16ms) and deduplicated. The zai/GLM
9
+ * adapter delivers tokens as a sustained stream of sub-millisecond,
10
+ * microtask-spaced bursts: per-burst notification renders at microtask
11
+ * cadence, which chained SyncLane `useSyncExternalStore` rerenders past
12
+ * React's nested-update limit ("Maximum update depth exceeded"), while a
13
+ * bare `setImmediate` merges a whole macrotask turn's bursts into one
14
+ * chunky repaint (streaming text visibly staggers). The frame budget gives
15
+ * both: an event ≥16ms after the last paint notifies via `setImmediate`
16
+ * (sub-millisecond latency for sparse/first tokens), and anything denser
17
+ * defers to the next 16ms boundary — a 60fps render cap that also breaks
18
+ * the nesting chain by construction.
17
19
  *
18
20
  * @module @deepseek-ai/dsh-tui/store
19
21
  */
@@ -9,9 +9,9 @@
9
9
  * running state, bounded last-activity text), capped at
10
10
  * {@link MAX_SUBAGENT_ROWS}. Rows are advisory display state, rebuilt from
11
11
  * live events; nothing here persists or replays. Notification is coalesced
12
- * to one microtask per delivery burst, mirroring the transcript store's
13
- * contract (per-token synchronous notify once cascaded past React's nested
14
- * update limit on the GLM thinking path).
12
+ * by the same ~16ms frame throttle as the transcript store (per-burst
13
+ * microtask notify chained SyncLane rerenders past React's nested update
14
+ * limit; a bare macrotask merge repaints a whole turn's bursts at once).
15
15
  *
16
16
  * @module @deepseek-ai/dsh-code/subagents
17
17
  */
@@ -17,7 +17,7 @@
17
17
  /** One RGB triple for a palette token. */
18
18
  export type RgbTriple = readonly [number, number, number];
19
19
  /** Palette token keys shared by every theme. */
20
- export type ThemeToken = 'brand' | 'brandBright' | 'brandMid' | 'brandDeep' | 'dim' | 'success' | 'error' | 'warn' | 'text' | 'code';
20
+ export type ThemeToken = 'brand' | 'brandBright' | 'brandMid' | 'brandDeep' | 'dim' | 'success' | 'error' | 'warn' | 'text' | 'code' | 'composerBand';
21
21
  /** One full color palette: every token key mapped to an RGB triple. */
22
22
  export type ThemePalette = Readonly<Record<ThemeToken, RgbTriple>>;
23
23
  /** Selectable theme names: dark, light, or auto (terminal-sensed). */
@@ -50,6 +50,8 @@ export declare const DARK_PALETTE: {
50
50
  readonly text: readonly [236, 240, 246];
51
51
  /** Inline/fenced code — soft sky blue, distinct from brand accents. */
52
52
  readonly code: readonly [125, 211, 252];
53
+ /** Composer three-row band base — neutral light gray, hue-free so wave tints read on it. */
54
+ readonly composerBand: readonly [46, 48, 52];
53
55
  };
54
56
  /**
55
57
  * Light palette tuned for white terminals: the same token keys as dark with
@@ -78,6 +80,8 @@ export declare const LIGHT_PALETTE: {
78
80
  readonly text: readonly [21, 21, 23];
79
81
  /** Inline/fenced code — Tailwind cyan-700, distinct from brand accents. */
80
82
  readonly code: readonly [14, 116, 144];
83
+ /** Composer three-row band base — neutral light gray, hue-free so wave tints read on it. */
84
+ readonly composerBand: readonly [229, 231, 235];
81
85
  };
82
86
  /** Every palette by theme name; auto resolves through {@link resolveTheme}. */
83
87
  export declare const PALETTES: {
@@ -102,6 +106,8 @@ export declare const PALETTES: {
102
106
  readonly text: readonly [236, 240, 246];
103
107
  /** Inline/fenced code — soft sky blue, distinct from brand accents. */
104
108
  readonly code: readonly [125, 211, 252];
109
+ /** Composer three-row band base — neutral light gray, hue-free so wave tints read on it. */
110
+ readonly composerBand: readonly [46, 48, 52];
105
111
  };
106
112
  readonly light: {
107
113
  /** Primary brand blue — unchanged, ≈4.9:1 AA on white. */
@@ -124,6 +130,8 @@ export declare const PALETTES: {
124
130
  readonly text: readonly [21, 21, 23];
125
131
  /** Inline/fenced code — Tailwind cyan-700, distinct from brand accents. */
126
132
  readonly code: readonly [14, 116, 144];
133
+ /** Composer three-row band base — neutral light gray, hue-free so wave tints read on it. */
134
+ readonly composerBand: readonly [229, 231, 235];
127
135
  };
128
136
  };
129
137
  /**
@@ -131,6 +139,9 @@ export declare const PALETTES: {
131
139
  * two-palette switch keep compiling and painting identically (the default
132
140
  * theme IS dark). New code should read the active palette through
133
141
  * {@link getPalette} so a theme switch reaches it.
142
+ *
143
+ * @deprecated Read the active palette through {@link getPalette}; this
144
+ * compatibility alias is removed in the next minor release.
134
145
  */
135
146
  export declare const TUI_RGB: {
136
147
  /** Primary brand blue — `--dsw-static-deepseek-500`. */
@@ -153,6 +164,8 @@ export declare const TUI_RGB: {
153
164
  readonly text: readonly [236, 240, 246];
154
165
  /** Inline/fenced code — soft sky blue, distinct from brand accents. */
155
166
  readonly code: readonly [125, 211, 252];
167
+ /** Composer three-row band base — neutral light gray, hue-free so wave tints read on it. */
168
+ readonly composerBand: readonly [46, 48, 52];
156
169
  };
157
170
  /**
158
171
  * Resolve a theme name to the palette actually in use. `auto` detection
@@ -1,5 +1,18 @@
1
1
  /** Installed dsh-code version exposed by the terminal header. */
2
- /** Read one package manifest version without making terminal startup depend on it. */
3
- export declare function readPackageVersion(manifest?: import("url").URL): string;
4
2
  /** Version of the installed dsh-code package. */
5
3
  export declare const DSH_CODE_VERSION: string;
4
+ /**
5
+ * Resolve the running dsh CLI host's version from its entry file
6
+ * (`process.argv[1]`, e.g. `.../@deepseek-ai/dsh/lib/bin.js`). Only a manifest
7
+ * literally named `@deepseek-ai/dsh` counts, so an unrelated entry (vitest, a
8
+ * plain node script) resolves to undefined instead of faking a kernel version.
9
+ */
10
+ export declare function resolveDshHostVersion(entry?: string | undefined): string | undefined;
11
+ /**
12
+ * The dsh kernel version the TUI runs on, memoized after the first probe: the
13
+ * host process never changes within a run, and the header reads this on every
14
+ * Static replay.
15
+ */
16
+ export declare function dshKernelVersion(): string | undefined;
17
+ /** Test-only: forget the memoized kernel version so a new argv can be probed. */
18
+ export declare function _resetDshKernelVersionForTests(): void;