dsh-code 0.6.1 → 0.8.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 (56) hide show
  1. package/README.en.md +20 -6
  2. package/README.md +20 -6
  3. package/lib/index.mjs +3952 -1315
  4. package/lib/startup.mjs +21 -9
  5. package/lib/theme-BEi4i_aN.mjs +624 -0
  6. package/lib/types/app.d.ts +108 -4
  7. package/lib/types/history.d.ts +15 -4
  8. package/lib/types/index.d.ts +49 -0
  9. package/lib/types/kernel-panels.d.ts +28 -0
  10. package/lib/types/mentions.d.ts +29 -12
  11. package/lib/types/models.d.ts +66 -0
  12. package/lib/types/permissions.d.ts +37 -0
  13. package/lib/types/presets.d.ts +2 -0
  14. package/lib/types/provider-settings.d.ts +144 -0
  15. package/lib/types/questions.d.ts +2 -0
  16. package/lib/types/render/animations.d.ts +177 -2
  17. package/lib/types/render/lines.d.ts +6 -0
  18. package/lib/types/render/markdown.d.ts +3 -3
  19. package/lib/types/render/projection.d.ts +123 -3
  20. package/lib/types/render/status.d.ts +35 -24
  21. package/lib/types/render/text.d.ts +14 -7
  22. package/lib/types/render/tool-detail.d.ts +3 -1
  23. package/lib/types/render/tool-preview.d.ts +4 -1
  24. package/lib/types/session-directory.d.ts +15 -0
  25. package/lib/types/startup.d.ts +12 -4
  26. package/lib/types/store.d.ts +13 -2
  27. package/lib/types/theme-panel.d.ts +24 -0
  28. package/lib/types/theme.d.ts +158 -2
  29. package/lib/types/version.d.ts +5 -0
  30. package/package.json +1 -1
  31. package/src/app.ts +1283 -206
  32. package/src/approval.ts +11 -2
  33. package/src/history.ts +20 -5
  34. package/src/index.ts +1207 -905
  35. package/src/kernel-panels.ts +518 -419
  36. package/src/mentions.ts +57 -27
  37. package/src/models.ts +200 -66
  38. package/src/permissions.ts +85 -0
  39. package/src/presets.ts +12 -0
  40. package/src/provider-settings.ts +520 -0
  41. package/src/questions.ts +15 -5
  42. package/src/render/animations.ts +373 -2
  43. package/src/render/lines.ts +21 -6
  44. package/src/render/markdown.ts +302 -4
  45. package/src/render/projection.ts +1419 -659
  46. package/src/render/status.ts +650 -603
  47. package/src/render/text.ts +28 -9
  48. package/src/render/tool-detail.ts +81 -40
  49. package/src/render/tool-preview.ts +18 -2
  50. package/src/session-directory.ts +44 -5
  51. package/src/skills.ts +8 -4
  52. package/src/startup.ts +119 -109
  53. package/src/store.ts +26 -8
  54. package/src/theme-panel.ts +72 -0
  55. package/src/theme.ts +206 -70
  56. package/src/version.ts +16 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Provider settings adapter for the TUI `/model` provider-management panel:
3
+ * the same-process equivalent of the web host's Models page join
4
+ * (`packages/client/ui-settings-models`), reading the advisory `ctx.llm`
5
+ * registry, the redacted `ctx.settings` descriptors, and the value-free
6
+ * `ctx.credentials` facts directly. Secrets never cross this module: settings
7
+ * are read with `redactSecrets: true`, credentials are only ever described
8
+ * (never resolved), and every message is single-line without embedding key
9
+ * data.
10
+ *
11
+ * @module @deepseek-ai/dsh-tui/provider-settings
12
+ */
13
+ import type { Context } from '@deepseek-ai/cordis';
14
+ /**
15
+ * The conventional credential reference for a provider route: `<ROUTE>_API_KEY`
16
+ * with the route uppercased and every non-alphanumeric run collapsed to one
17
+ * underscore — the exact derivation the official Models page uses
18
+ * (`deriveKeyRef` in `ui-settings-models`), so a key saved here is found there.
19
+ * @param provider - provider route id (e.g. `pi-ai`, `minimax-cn`).
20
+ * @returns the derived reference name (e.g. `PI_AI_API_KEY`).
21
+ */
22
+ export declare function deriveCredentialRef(provider: string): string;
23
+ /** Value-free facts about one credential reference — never the value. */
24
+ export interface ProviderCredentialFacts {
25
+ /** Whether the reference currently resolves to a stored value. */
26
+ readonly configured: boolean;
27
+ /** Source layer supplying the value; absent while unconfigured. */
28
+ readonly source?: string;
29
+ /** Whether a write through this panel would currently succeed. */
30
+ readonly writable: boolean;
31
+ }
32
+ /**
33
+ * One row's credential state: value-free facts once the reference was
34
+ * described, a bounded error when that describe failed (the row itself is
35
+ * never dropped), or `undefined` when the row names no reference to describe —
36
+ * an unmanaged active provider, a dormant route, or a profile authenticating
37
+ * through the provider's own path.
38
+ */
39
+ export type ProviderCredentialView = ({
40
+ readonly kind: 'facts';
41
+ } & ProviderCredentialFacts) | {
42
+ readonly kind: 'error';
43
+ readonly message: string;
44
+ };
45
+ /**
46
+ * One provider row in the TUI provider-management panel: the configurable
47
+ * directory entry joined with its settings profile and credential facts.
48
+ * Every mutation below addresses this row, and the caller passes the row back
49
+ * after re-loading so the revision/ref facts are current.
50
+ */
51
+ export interface ProviderTargetView {
52
+ /** Provider route id (`GenerateOptions.provider`). */
53
+ readonly provider: string;
54
+ /** Human-readable provider name. */
55
+ readonly displayName: string;
56
+ /** Whether an adapter currently serves this route. */
57
+ readonly active: boolean;
58
+ /** User-settings namespace whose section configures this provider; '' when unmanaged. */
59
+ readonly settingsNs: string;
60
+ /** Path from that section's root to this provider's profile; [] when the whole section is the profile. */
61
+ readonly settingsPath: readonly string[];
62
+ /** Revision of the owning settings section at load (0 when no namespace resolved). */
63
+ readonly settingsRevision: number;
64
+ /** Whether the resolved profile exists (the whole section, or at `settingsPath`). */
65
+ readonly configured: boolean;
66
+ /** Whether only the user settings layer carries the profile, so removal restores the base. */
67
+ readonly removable: boolean;
68
+ /** The credential reference the resolved profile names, when one does. */
69
+ readonly credentialRef?: string;
70
+ /** The conventional reference a save uses for a dormant or ref-less profile. */
71
+ readonly suggestedRef: string;
72
+ /** Credential facts, a bounded describe error, or undefined when there is no ref to describe. */
73
+ readonly credential: ProviderCredentialView | undefined;
74
+ /** The owning adapter reports this route as hand-declared (absent when it draws no distinction). */
75
+ readonly declared?: boolean;
76
+ }
77
+ /** The resolved provider/settings/credential join. */
78
+ export interface ProviderSettingsDirectory {
79
+ /** Provider rows: configurable-directory order first, active-unmanaged rows after. */
80
+ readonly rows: readonly ProviderTargetView[];
81
+ /** Whether the settings provider accepts writes (mirrors the web page's flag). */
82
+ readonly writable: boolean;
83
+ /** Non-fatal join failures (settings/directory reads), for a degradation notice. */
84
+ readonly failures: readonly string[];
85
+ }
86
+ /** Subscribe to the same provider-directory invalidations as the official Web Models page. */
87
+ export declare function subscribeProviderSettings(ctx: Context, listener: () => void): () => void;
88
+ /** A single-line, bounded error from the provider-management adapter. */
89
+ export declare class ProviderSettingsError extends Error {
90
+ constructor(message: string);
91
+ }
92
+ /**
93
+ * Join the configurable-provider directory, the redacted settings
94
+ * namespaces, and the referenced credentials into panel rows, web-parity:
95
+ * - directory entries merge with `listProviders()` to mark each live or
96
+ * dormant, and routes registered without a directory declaration appear as
97
+ * read-only/unmanaged rows (no settings address);
98
+ * - a whole-section entry is configured whenever its namespace resolves;
99
+ * a path-addressed one only when the profile resolves there;
100
+ * - a row is removable when the user layer alone carries its profile;
101
+ * - only refs named by resolved profiles are described, and a per-ref failure
102
+ * degrades to that row's bounded error instead of losing it.
103
+ * Absent `settings`/`credentials` services are tolerated the same way.
104
+ * @param ctx - context carrying the `llm` service (settings/credentials optional).
105
+ * @returns the resolved directory; empty rows when `llm` is unavailable.
106
+ */
107
+ export declare function loadProviderSettings(ctx: Context): Promise<ProviderSettingsDirectory>;
108
+ /**
109
+ * Store a provider API key, web-parity: validate with `normalizeApiKey`
110
+ * (single-line, actionable errors that never echo the key), materialize the
111
+ * profile/`apiKeyEnv` through `settings.mutate` first when the resolved
112
+ * profile names no reference (dormant route or ref-less profile), then store
113
+ * under the trusted named ref or the derived conventional ref. An existing
114
+ * whole-section DeepSeek whose resolved profile already names
115
+ * `DEEPSEEK_API_KEY` needs no settings mutation. Env-supplied read-only keys
116
+ * are refused before any service call.
117
+ * @param ctx - context carrying `settings` (when materializing) and `credentials`.
118
+ * @param target - the joined row to write through.
119
+ * @param rawKey - the key exactly as typed; surrounding whitespace is trimmed.
120
+ * @throws {@link ProviderSettingsError} with a single-line, key-free message.
121
+ */
122
+ export declare function saveProviderCredential(ctx: Context, target: ProviderTargetView, rawKey: string): Promise<void>;
123
+ /**
124
+ * Remove the currently named credential without touching the provider
125
+ * profile. Only the resolved profile's own reference is unset; a dormant or
126
+ * ref-less row (nothing to remove), an already-absent key, and an
127
+ * env-supplied read-only key are rejected safely before any service call.
128
+ * @param ctx - context carrying the `credentials` service.
129
+ * @param target - the joined row whose named credential to unset.
130
+ * @throws {@link ProviderSettingsError} with a single-line, key-free message.
131
+ */
132
+ export declare function unsetProviderCredential(ctx: Context, target: ProviderTargetView): Promise<void>;
133
+ /**
134
+ * Remove a user-added provider profile, web-parity: only `removable` rows may
135
+ * be removed; a page-managed credential — the derived ref, configured and
136
+ * writable — is unset first (so a second-step failure leaves the row visible
137
+ * and the operation retryable), then `settings.mutate` unsets
138
+ * `target.settingsPath`. Both steps are idempotent. A hand-named credential
139
+ * ref may be shared elsewhere and is left alone.
140
+ * @param ctx - context carrying `credentials` and `settings`.
141
+ * @param target - the joined row to remove.
142
+ * @throws {@link ProviderSettingsError} with a single-line, key-free message.
143
+ */
144
+ export declare function removeProviderSettings(ctx: Context, target: ProviderTargetView): Promise<void>;
@@ -22,6 +22,8 @@ export interface PendingQuestion {
22
22
  resolve(answers: AskUserQuestionAnswer): void;
23
23
  /** Reject the provider promise as aborted (also used for Esc cancel). */
24
24
  reject(error: Error): void;
25
+ /** Detach the request's abort listener once the question settles (internal). */
26
+ detachAbort?(): void;
25
27
  }
26
28
  /** The pending-question snapshot the renderer subscribes to. */
27
29
  export interface QuestionSnapshot {
@@ -3,11 +3,20 @@
3
3
  * the StateDot "ongoing" pixel chase (3×3 ring, 125ms flat-hold brightness
4
4
  * steps, 1s cycle) becomes the single-cell stepped pulse below and the
5
5
  * full-ring clockwise braille chase in {@link BUSY_CHASE_FRAMES}, and the
6
- * streaming caret blink is the Claude-Code convention. Pure functions only —
7
- * the Ink layer owns timers and colors.
6
+ * streaming caret blink is the Claude-Code convention.
7
+ *
8
+ * The DeepSeek model-switch easter egg ports Codex's effort-ignition "Wave"
9
+ * style (`codex-rs/tui/src/bottom_pane/effort_ignition_styles.rs`): switching
10
+ * INTO an official DeepSeek route sweeps a blue wave across the composer's
11
+ * input row — one column per cell, `backgroundColor` = the sampled wave
12
+ * color — then, on the deepseek (Ultra-equivalent) tier, drops the `· ✦ ✧`
13
+ * sparkle sequence into the rightmost blank cell before fading. The prompt
14
+ * marker keeps the tier accent afterwards (persistent, like Codex's prompt
15
+ * charge). Pure functions only — the Ink layer owns timers and colors.
8
16
  *
9
17
  * @module @deepseek-ai/dsh-code/render/animations
10
18
  */
19
+ import type { RgbTriple } from '../theme.ts';
11
20
  /** Single-cell stepped pulse: flat holds mirroring the web's 125ms keyframes. */
12
21
  export declare const PULSE_FRAMES: readonly ["█", "█", "▆", "▃", "▁", "▃", "▆", "█"];
13
22
  /** Pulse frame for a monotonic tick. */
@@ -22,3 +31,169 @@ export declare const BUSY_CHASE_FRAMES: readonly ["⣾", "⣽", "⣻", "⢿", "
22
31
  export declare function busyChaseFrame(tick: number): string;
23
32
  /** Caret visibility: half the ticks on, half off (530ms blink). */
24
33
  export declare function caretVisible(tick: number): boolean;
34
+ /**
35
+ * The one-shot DeepSeek model-switch easter egg: when the status bar model
36
+ * label switches to an official DeepSeek route, the composer's input row
37
+ * plays Codex's effort-ignition "Wave" — a blue crest sweeping the content
38
+ * row column by column (background tint ≤ 0.55 under the draft), plus the
39
+ * Ultra-style `· ✦ ✧` sparkles on the deepseek tier — and the prompt marker
40
+ * keeps the tier accent afterwards. The Ink layer owns the timer and reads
41
+ * the ACTIVE palette anchors (`getPalette`); everything below is pure
42
+ * interpolation over the colors it is given.
43
+ */
44
+ /** Frame cadence of the DeepSeek wave: Codex's IGNITION_FRAME_TICK (33ms ≈ 30fps). */
45
+ export declare const DEEPSEEK_WAVE_TICK_MS = 33;
46
+ /**
47
+ * The two DeepSeek wave tiers. The concept maps Codex's reasoning tiers to
48
+ * model ids: `flash` runs the Max parameters, `deepseek` (pro models) runs
49
+ * the Ultra parameters (dual band + tail sparkles on the Wave style).
50
+ */
51
+ export type DeepseekWaveTier = 'flash' | 'deepseek';
52
+ /**
53
+ * The three ignition styles — Codex `IgnitionStyle`: a traveling crest
54
+ * (Wave), a drifting multi-hue band (Aurora), and an expanding ring (Pulse).
55
+ * One style is picked at random per trigger and never repeats the previous.
56
+ */
57
+ export type DeepseekWaveStyle = 'wave' | 'aurora' | 'pulse';
58
+ /** All styles in canonical order, for random selection and tests. */
59
+ export declare const DEEPSEEK_WAVE_STYLES: readonly DeepseekWaveStyle[];
60
+ /** Wave half-width in columns — Codex WAVE_HALF_WIDTH (9). */
61
+ export declare const WAVE_HALF_WIDTH = 9;
62
+ /** Pulse ring half-width in columns — Codex PULSE_HALF_WIDTH (4.5). */
63
+ export declare const PULSE_HALF_WIDTH = 4.5;
64
+ /** Sparkle start and frame cadence — Codex SPARK_START / SPARK_FRAME. */
65
+ export declare const SPARK_START_MS = 900;
66
+ export declare const SPARK_FRAME_MS = 100;
67
+ /** Sparkle glyphs in frame order — Codex SPARK_GLYPHS (`· ✦ ✧`). */
68
+ export declare const SPARK_GLYPHS: readonly ["·", "✦", "✧"];
69
+ /**
70
+ * Band tables — Codex `bands(style, tier)`. Each entry is a triple whose
71
+ * meaning depends on the style: Wave/Pulse use `(launch, travel, strength)`;
72
+ * Aurora uses `(speed, phase, hueIndex)`.
73
+ */
74
+ export type DeepseekWaveBand = readonly [number, number, number];
75
+ export declare const DEEPSEEK_WAVE_BANDS: Readonly<Record<DeepseekWaveStyle, Readonly<Record<DeepseekWaveTier, readonly DeepseekWaveBand[]>>>>;
76
+ /** Extra display time applied to every Codex ignition style. */
77
+ export declare const DEEPSEEK_WAVE_DURATION_EXTENSION_MS = 200;
78
+ /**
79
+ * Total visible duration: the Codex ignition duration plus 200ms so its motion
80
+ * remains readable in a busy terminal.
81
+ * @param tier - the active wave tier.
82
+ * @param style - the active ignition style.
83
+ * @returns the duration in milliseconds.
84
+ */
85
+ export declare function deepseekWaveDuration(tier: DeepseekWaveTier, style?: DeepseekWaveStyle): number;
86
+ /**
87
+ * Pick one ignition style at random, never repeating the previous one —
88
+ * Codex `IgnitionStyle::random`. Falls back to the remaining styles.
89
+ * @param previous - the style of the last trigger, if any.
90
+ * @returns a style different from `previous`.
91
+ */
92
+ export declare function deepseekWaveStyleRandom(previous: DeepseekWaveStyle | undefined): DeepseekWaveStyle;
93
+ /**
94
+ * Blank-cell background the wave tint blends toward — fixed approximations
95
+ * of the terminal's default background, mirroring Codex's
96
+ * `user_message_bg_rgb` (which derives a near-black / near-white bubble tint
97
+ * from the terminal background). The Ink layer picks the active theme's one.
98
+ */
99
+ export declare const WAVE_BASE_DARK: RgbTriple;
100
+ export declare const WAVE_BASE_LIGHT: RgbTriple;
101
+ /**
102
+ * Tier for a `provider/model` label: a model id containing `flash` runs the
103
+ * single-band flash tier; everything else (pro/reasoner/chat) runs the
104
+ * dual-band deepseek tier. Mirrors Codex's Max→Ultra mapping.
105
+ * @param model - the `provider/model` label of the applied model.
106
+ * @returns the wave tier for that model.
107
+ */
108
+ export declare function deepseekWaveTier(model: string): DeepseekWaveTier;
109
+ /**
110
+ * Cosine window — Codex `crest`: 1 exactly under the wave center, 0 from
111
+ * one half-width away.
112
+ * @param distance - distance from the crest center in half-widths.
113
+ * @returns the crest strength in 0..1.
114
+ */
115
+ export declare function crest(distance: number): number;
116
+ /**
117
+ * Cubic ease-in-out — Codex `ease_in_out`: flat at both ends, steepest in
118
+ * the middle, so the crest accelerates and eases instead of sliding linearly.
119
+ * @param progress - raw progress (clamped to 0..1).
120
+ * @returns the eased progress in 0..1.
121
+ */
122
+ export declare function easeInOut(progress: number): number;
123
+ /**
124
+ * Fade-in/fade-out envelope — Codex `envelope`: linear ramp over `fadeIn`
125
+ * at the start and `fadeOut` at the end, plateau at 1 between, 0 outside the
126
+ * total. The Wave style keeps the envelope at 1 (Codex paints Wave without
127
+ * an envelope); exported for the Aurora-style fades and for tests.
128
+ * @param elapsed - seconds since the animation started.
129
+ * @param total - total duration in seconds.
130
+ * @param fadeIn - seconds of fade-in.
131
+ * @param fadeOut - seconds of fade-out.
132
+ * @returns the envelope value in 0..1.
133
+ */
134
+ export declare function envelope(elapsed: number, total: number, fadeIn: number, fadeOut: number): number;
135
+ /**
136
+ * The background color for one composer-row column at a tick — Codex
137
+ * `paint_bands` + `Canvas::tint` for all three styles. Bands overlap with a
138
+ * max for Wave/Pulse and a SUM for Aurora (Codex differs by style), the
139
+ * weighted hues mix per column (Wave/Pulse always end on hue 0), the tint
140
+ * blends the mixed hue toward the blank-cell base at the style's alpha cap,
141
+ * and Aurora applies its own fade envelope. Returns `null` when the column
142
+ * should stay transparent, so the row returns to no `backgroundColor` on
143
+ * both ends.
144
+ * @param tick - wave frame (0, 1, … at DEEPSEEK_WAVE_TICK_MS).
145
+ * @param column - column index in the content row (0..width-1).
146
+ * @param width - content-row width in columns.
147
+ * @param tier - the wave tier (flash = Max, deepseek = Ultra parameters).
148
+ * @param style - the ignition style.
149
+ * @param hues - the tier's three hues.
150
+ * @param base - the blank-cell base color the tint blends toward.
151
+ * @returns the blended RGB background, or null for transparent.
152
+ */
153
+ export declare function deepseekWaveColumnBg(tick: number, column: number, width: number, tier: DeepseekWaveTier, style: DeepseekWaveStyle, hues: readonly [RgbTriple, RgbTriple, RgbTriple], base: RgbTriple): RgbTriple | null;
154
+ /**
155
+ * The sparkle glyph for a tick — Codex `spark_frame`, sampled on the same
156
+ * proportionally slowed DeepSeek Wave timeline as the composer background.
157
+ * The Ink layer still must skip occupied cells.
158
+ * @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
159
+ * @returns the sparkle glyph, or null outside the stretched tail window.
160
+ */
161
+ export declare function deepseekWaveSpark(tick: number): string | null;
162
+ /**
163
+ * The composer BORDER color at a tick: the frame breathes with the wave —
164
+ * the palette's static dim blends toward the tier accent as the crest is
165
+ * alive and back, so the frame glows up while the wave sweeps and settles to
166
+ * dim on both ends (first==last frame==dim, no hard jump).
167
+ * @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
168
+ * @param tier - the wave tier.
169
+ * @param hues - the tier's three hues; the border blends toward hues[0].
170
+ * @param dim - the palette's static dim RGB (the resting border color).
171
+ * @returns the blended border RGB.
172
+ */
173
+ export declare function deepseekWaveBorderColor(tick: number, tier: DeepseekWaveTier, style: DeepseekWaveStyle, hues: readonly [RgbTriple, RgbTriple, RgbTriple], dim: RgbTriple): RgbTriple;
174
+ /**
175
+ * Whether the `deepseek` wordmark rides the wave at this tick: it fades in
176
+ * shortly after the first crest launches and out before the wave settles,
177
+ * so the brand name surfaces through the sweep's middle. The Ink layer
178
+ * places it in the row's blank mid-section (never over real draft text).
179
+ * @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
180
+ * @param tier - the wave tier.
181
+ * @returns true while the wordmark should be visible.
182
+ */
183
+ export declare function deepseekWaveWordVisible(tick: number, tier: DeepseekWaveTier, style?: DeepseekWaveStyle): boolean;
184
+ /**
185
+ * The per-character color for the `deepseek` wordmark: the tier's hues
186
+ * cycled per character (d→hue0, e→hue1, e→hue2, …), a brand-gradient text.
187
+ * @param index - character index in the wordmark.
188
+ * @param hues - the tier's three hues.
189
+ * @returns the hue for that character.
190
+ */
191
+ export declare function deepseekWaveWordHue(index: number, hues: readonly [RgbTriple, RgbTriple, RgbTriple]): RgbTriple;
192
+ /**
193
+ * True when a `provider/model` status label addresses the official DeepSeek
194
+ * route: either segment contains `deepseek` (case-insensitive), covering the
195
+ * `deepseek-official` provider route and its `deepseek-*` model ids.
196
+ * @param label - the status bar model label (`provider/model`).
197
+ * @returns whether the label names an official DeepSeek model.
198
+ */
199
+ export declare function isOfficialDeepSeekLabel(label: string): boolean;
@@ -24,6 +24,12 @@ export declare function styledLines(segments: readonly StyledSegment[], columns:
24
24
  export declare function textLines(text: string, columns: number, style?: LineStyle): readonly StyledLine[];
25
25
  /** Markdown rows re-hardened so a single long word cannot escape the budget. */
26
26
  export declare function markdownLines(text: string, columns: number): readonly StyledLine[];
27
+ /**
28
+ * Codex-style reasoning rows: the marker occupies the reply gutter and every
29
+ * wrapped or explicit continuation starts with the same two-column indent, so
30
+ * reasoning content and assistant Markdown share one left edge.
31
+ */
32
+ export declare function reasoningLines(text: string, columns: number): readonly StyledLine[];
27
33
  /**
28
34
  * Convert one durable transcript entry to its complete scrollable row model.
29
35
  * The source entry stays intact; only the caller's visible slice is rendered.
@@ -3,13 +3,13 @@
3
3
  * block/inline parser producing styled line segments the Ink renderer maps
4
4
  * to colored text. No ANSI here — the app owns color mapping, tests own the
5
5
  * structure. The subset mirrors what agent replies actually emit: headings,
6
- * emphasis, inline/fenced code, flat lists, blockquotes, links, rules, and
7
- * wrapped paragraphs. Unknown syntax degrades to plain text (never throws).
6
+ * emphasis, inline/fenced code, flat lists, blockquotes, links, rules, GFM
7
+ * tables, and wrapped paragraphs. Unknown syntax degrades to plain text.
8
8
  *
9
9
  * @module @deepseek-ai/dsh-code/render/markdown
10
10
  */
11
11
  /** Style classes the renderer emits; the app maps them to colors/props. */
12
- export type MdStyle = 'plain' | 'bold' | 'italic' | 'boldItalic' | 'code' | 'accent' | 'dim' | 'strike';
12
+ export type MdStyle = 'plain' | 'bold' | 'italic' | 'boldItalic' | 'code' | 'accent' | 'accentBold' | 'dim' | 'strike';
13
13
  /** One styled run of text. */
14
14
  export interface MdSegment {
15
15
  /** Visible text (no ANSI). */
@@ -138,6 +138,24 @@ export interface UsageTotals {
138
138
  /** Cache-read tokens over the whole log (0 when the adapter reports none). */
139
139
  cacheReadTokens: number;
140
140
  }
141
+ /**
142
+ * Estimated used tokens per context content type, folded from transcript
143
+ * events via {@link estimateTokens}. The segmented context bar's composition
144
+ * source: proportions across types are meaningful, absolute values are not
145
+ * (they never touch billing or the reported `lastPromptTokens`).
146
+ */
147
+ export interface ContextSegments {
148
+ /** Rendered system-prompt text (latest `request/header`) plus injected-context notices. */
149
+ system: number;
150
+ /** Direct human prompts (durable `user/message` rows). */
151
+ prompt: number;
152
+ /** Assistant text blocks (visible replies). */
153
+ assistant: number;
154
+ /** Assistant reasoning blocks (hidden thinking). */
155
+ thinking: number;
156
+ /** Tool call arguments plus result text. */
157
+ tools: number;
158
+ }
141
159
  /** Window-scoped figures the status line shows; timing uses event timestamps. */
142
160
  export interface TranscriptStats {
143
161
  /** Durable turns opened (`turn/start` events). */
@@ -154,6 +172,8 @@ export interface TranscriptStats {
154
172
  lastPromptTokens: number;
155
173
  /** Newest advertised route capacity, 0 when no adapter ever advertised one. */
156
174
  contextWindow: number;
175
+ /** Estimated used tokens per content type (the segmented bar's composition). */
176
+ contextSegments: ContextSegments;
157
177
  /** Summed first-token waits: `step/start` → first non-empty chunk, in ms. */
158
178
  ttftMs: number;
159
179
  /** Steps that produced a first chunk (the TTFT average's denominator). */
@@ -162,6 +182,14 @@ export interface TranscriptStats {
162
182
  decodeMs: number;
163
183
  /** Completion tokens over timed decode spans (the tok/s numerator). */
164
184
  decodeTokens: number;
185
+ /**
186
+ * Adapter-owned reasoning effort of the latest `request/header` config —
187
+ * the EFFECTIVE effort the session actually uses (a materialized model
188
+ * default is included, exactly as the adapter resolved it). Empty when the
189
+ * header carried none (provider-default behavior). The status line appends
190
+ * it to the model segment as `provider/model@effort`.
191
+ */
192
+ reasoningEffort: string;
165
193
  }
166
194
  /** The complete TUI transcript view for one session. */
167
195
  export interface TranscriptView {
@@ -208,7 +236,10 @@ export interface TranscriptView {
208
236
  /**
209
237
  * Fold-internal timing anchors, never rendered: open step and tool-call
210
238
  * start timestamps the next `assistant/message` / `tool/result` resolves
211
- * against. Keyed `turn:step` and by call id.
239
+ * against. Keyed `turn:step` and by call id. `turnSteps`/`turnTools`
240
+ * track which step/tool anchors still belong to the open turn so
241
+ * `turn/end` (and a superseding `step/start`) can sweep anchors an
242
+ * interruption left behind; `turnFiles` keys mutated paths by turn.
212
243
  */
213
244
  readonly anchors: {
214
245
  stepStart: Map<string, number>;
@@ -217,6 +248,8 @@ export interface TranscriptView {
217
248
  compactionTokens: Map<string, number>;
218
249
  lastPruneTokens: number;
219
250
  turnFiles: Map<number, Set<string>>;
251
+ turnSteps: Map<number, string>;
252
+ turnTools: Map<number, Set<string>>;
220
253
  };
221
254
  }
222
255
  /** A fresh, empty transcript view. */
@@ -228,8 +261,92 @@ export declare function createTranscriptView(): TranscriptView;
228
261
  * @returns the view after the event; the input view is never mutated.
229
262
  */
230
263
  export declare function projectEvent(view: TranscriptView, event: SessionEvent): TranscriptView;
264
+ /**
265
+ * Mutable replay accumulator: folds a persisted log into the identical view
266
+ * `projectEvent` would produce, but in near-linear time. Where `projectEvent`
267
+ * is copy-on-write — every append/scan rebuilds the whole `entries` array, so
268
+ * folding a full log costs O(N²) — the accumulator appends by push, resolves
269
+ * id-keyed updates (tool/result, command/done, retry-started) through index
270
+ * maps, and tombstones retired pending rows, so the whole log folds in O(N)
271
+ * plus one compaction pass when tombstones exist.
272
+ *
273
+ * Index maps never delete: every appended row registers its index, so an id
274
+ * lookup miss provably means no matching row exists and the update is an O(1)
275
+ * no-op (a malicious/orphan-heavy log cannot force per-orphan full-array
276
+ * scans). Each id maps to ALL of its indices, so a duplicate id updates every
277
+ * matching row exactly like the copy-on-write reducer.
278
+ *
279
+ * @internal Exported only so tests can (a) prove replay ≡ sequential
280
+ * `projectEvent` folds and (b) assert the linear complexity deterministically
281
+ * via {@link ReplayAccumulator.ops}, which counts entry-level container work
282
+ * instead of relying on wall-clock thresholds. No public consumer.
283
+ */
284
+ export interface ReplayAccumulator {
285
+ /** Working entry list; `undefined` marks a retired pending row (tombstone). */
286
+ entries: (TranscriptEntry | undefined)[];
287
+ /** callId → every index into `entries` holding a `tool` row with that id. */
288
+ toolIndex: Map<string, number[]>;
289
+ /** commandId → every index into `entries` holding a `command` row with that id. */
290
+ commandIndex: Map<string, number[]>;
291
+ /** retryId → every index into `entries` holding a `retry` row with that id. */
292
+ retryIndex: Map<string, number[]>;
293
+ /** messageId → every index into `entries` holding a `pending` row with that id. */
294
+ pendingIndex: Map<string, number[]>;
295
+ /** Tombstone count; zero means `entries` is already the final array. */
296
+ removedCount: number;
297
+ /** Mutable inbox id lists, mirroring `view.pending` order per target. */
298
+ pendingTurn: string[];
299
+ pendingStep: string[];
300
+ streaming: string;
301
+ streamingReasoning: string;
302
+ todos: readonly TodoItem[];
303
+ busy: boolean;
304
+ busySince: number;
305
+ model: string;
306
+ plan: boolean;
307
+ permission: string;
308
+ title: string;
309
+ sandbox: string;
310
+ goal: GoalFold | undefined;
311
+ stats: TranscriptStats;
312
+ stepStart: Map<string, number>;
313
+ toolStart: Map<string, number>;
314
+ firstChunkAt: Map<string, number>;
315
+ compactionTokens: Map<string, number>;
316
+ lastPruneTokens: number;
317
+ turnFiles: Map<number, Set<string>>;
318
+ turnSteps: Map<number, string>;
319
+ turnTools: Map<number, Set<string>>;
320
+ /** Entry-level container operations performed so far (test instrumentation). */
321
+ ops: number;
322
+ }
323
+ /** @internal A fresh replay accumulator whose state mirrors `createTranscriptView()`. */
324
+ export declare function createReplayAccumulator(): ReplayAccumulator;
325
+ /**
326
+ * Fold one session event into a replay accumulator. This mirrors
327
+ * {@link projectEvent} case for case — same stats arithmetic, same anchor
328
+ * set/delete behavior, same entry shapes — so the finished view is identical
329
+ * to a sequential fold; only the `entries` container operations are mutable.
330
+ *
331
+ * @internal Test-instrumentation path; `projectEvents` is the public entry.
332
+ */
333
+ export declare function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent): void;
334
+ /**
335
+ * Materialize the accumulated fold as a `TranscriptView`, compacting any
336
+ * retired tombstones. The anchors maps are handed through as-is (their
337
+ * content is identical to a sequential fold's).
338
+ *
339
+ * @internal Test-instrumentation path; `projectEvents` is the public entry.
340
+ */
341
+ export declare function finishReplay(acc: ReplayAccumulator): TranscriptView;
231
342
  /**
232
343
  * Fold a replayed event history into one view.
344
+ *
345
+ * Folding is near-linear in the log size: the mutable replay accumulator
346
+ * appends in place and resolves id-keyed updates through index maps, so a
347
+ * long persisted session replays without the O(N²) copy-on-write rebuilds a
348
+ * naive sequential fold would incur. The result is identical to folding
349
+ * {@link projectEvent} per event in order.
233
350
  * @param events - events in `seq` order.
234
351
  * @returns the folded view.
235
352
  */
@@ -243,8 +360,11 @@ export declare function projectEvents(events: readonly SessionEvent[]): Transcri
243
360
  * the inbox claims or cancels them durably (`agent/inbox/spliced` removals,
244
361
  * `user/message` retirement), and an append-only `<Static>` flush cannot
245
362
  * erase a row that vanishes from the view — the retired row would ghost on
246
- * screen until the next source-backed replay. Everything else (including a
247
- * completed tail) is final: later events only APPEND new rows.
363
+ * screen until the next source-backed replay. Running commands join the
364
+ * mutable boundary for the same reason in reverse: `command/done` mutates the
365
+ * row's state/summary, so a flushed row would keep its stale running mark
366
+ * until a resize-triggered replay. Everything else (including a completed
367
+ * tail) is final: later events only APPEND new rows.
248
368
  * @param entries - the view's transcript entries in order.
249
369
  * @returns the count of entries safe to flush (0 for an empty transcript).
250
370
  */