dsh-code 1.0.7 → 1.2.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 (81) hide show
  1. package/README.en.md +70 -24
  2. package/README.md +71 -25
  3. package/bin/deepseek.mjs +202 -39
  4. package/cordis.patch.yml +13 -4
  5. package/lib/index.mjs +4063 -844
  6. package/lib/session-query.mjs +3 -2
  7. package/lib/startup.mjs +4 -4
  8. package/lib/{theme-DCT8Y2xf.mjs → theme-7u5Qo3dF.mjs} +657 -20
  9. package/lib/types/app.d.ts +100 -63
  10. package/lib/types/authorization-panel.d.ts +3 -3
  11. package/lib/types/git-workflow.d.ts +91 -2
  12. package/lib/types/i18n.d.ts +39 -0
  13. package/lib/types/index.d.ts +73 -1
  14. package/lib/types/input-split.d.ts +1 -1
  15. package/lib/types/kernel-panels.d.ts +86 -31
  16. package/lib/types/language-panel.d.ts +12 -0
  17. package/lib/types/locales/en.d.ts +450 -0
  18. package/lib/types/locales/zh.d.ts +9 -0
  19. package/lib/types/mentions.d.ts +7 -3
  20. package/lib/types/models.d.ts +14 -0
  21. package/lib/types/panel-accent.d.ts +28 -0
  22. package/lib/types/rainbow.d.ts +69 -0
  23. package/lib/types/render/animations.d.ts +42 -0
  24. package/lib/types/render/inspector.d.ts +26 -0
  25. package/lib/types/render/lines.d.ts +21 -1
  26. package/lib/types/render/markdown.d.ts +1 -1
  27. package/lib/types/render/projection.d.ts +95 -4
  28. package/lib/types/render/status.d.ts +8 -8
  29. package/lib/types/render/text.d.ts +6 -0
  30. package/lib/types/render/usage.d.ts +113 -0
  31. package/lib/types/session-directory.d.ts +17 -0
  32. package/lib/types/startup.d.ts +1 -1
  33. package/lib/types/terminal-title.d.ts +8 -0
  34. package/lib/types/theme-panel.d.ts +2 -2
  35. package/lib/types/theme.d.ts +271 -52
  36. package/lib/types/update-panel.d.ts +5 -5
  37. package/lib/types/update.d.ts +10 -1
  38. package/lib/types/version.d.ts +4 -3
  39. package/package.json +24 -7
  40. package/src/app.ts +1155 -478
  41. package/src/approval.ts +166 -166
  42. package/src/authorization-panel.ts +19 -16
  43. package/src/editor-keys.ts +371 -371
  44. package/src/git-workflow.ts +229 -3
  45. package/src/i18n.ts +68 -0
  46. package/src/index.ts +412 -76
  47. package/src/input-split.ts +3 -3
  48. package/src/kernel-panels.ts +471 -89
  49. package/src/keyboard.ts +5 -4
  50. package/src/language-panel.ts +53 -0
  51. package/src/locales/en.ts +489 -0
  52. package/src/locales/zh.ts +488 -0
  53. package/src/mentions.ts +8 -4
  54. package/src/models.ts +264 -212
  55. package/src/panel-accent.ts +41 -0
  56. package/src/presets.ts +1 -1
  57. package/src/provider-settings.ts +1 -1
  58. package/src/rainbow.ts +208 -0
  59. package/src/render/animations.ts +104 -6
  60. package/src/render/editor.ts +20 -20
  61. package/src/render/export.ts +116 -95
  62. package/src/render/inspector.ts +42 -0
  63. package/src/render/lines.ts +628 -415
  64. package/src/render/markdown.ts +15 -3
  65. package/src/render/projection.ts +429 -19
  66. package/src/render/status.ts +41 -35
  67. package/src/render/text.ts +14 -0
  68. package/src/render/tool-preview.ts +77 -77
  69. package/src/render/usage.ts +430 -0
  70. package/src/render/width.ts +2 -2
  71. package/src/session-directory.ts +8 -6
  72. package/src/session-query.ts +8 -4
  73. package/src/startup.ts +3 -3
  74. package/src/subagents.ts +229 -229
  75. package/src/terminal-title.ts +22 -5
  76. package/src/theme-panel.ts +17 -21
  77. package/src/theme.ts +281 -33
  78. package/src/update-panel.ts +37 -27
  79. package/src/update.ts +19 -3
  80. package/src/version.ts +58 -20
  81. package/src/whale-glyph.ts +23 -23
@@ -54,6 +54,19 @@ export declare function deepDivingGradientColor(index: number, tick: number, gra
54
54
  export declare function deepDivingSparkIntensity(tick: number): number;
55
55
  /** Blue RGB color for the breathing Deep diving sparkle. */
56
56
  export declare function deepDivingSparkColor(tick: number, base: RgbTriple, highlight: RgbTriple): RgbTriple;
57
+ /** One full prismatic flow lap — lively: violet → fuchsia → cyan → violet in 2.4s. */
58
+ export declare const FLOW_PERIOD_MS = 2400;
59
+ /**
60
+ * The prismatic flow color at one elapsed-time sample: a smoothstep walk
61
+ * around the anchor triangle, one full lap per {@link FLOW_PERIOD_MS}. Pure —
62
+ * the Ink layer owns the timer and passes its tick scaled by its own cadence
63
+ * (tick × tickMs). Callers gate on the animations preference and fall back to
64
+ * a static palette color when animation is off.
65
+ * @param elapsedMs - milliseconds since the flow started (any sign or size).
66
+ * @param anchors - the colors to walk, in order (theme.ts FLOW_ANCHORS).
67
+ * @returns the interpolated anchor color at this instant.
68
+ */
69
+ export declare function flowColor(elapsedMs: number, anchors: readonly RgbTriple[]): RgbTriple;
57
70
  /** Caret blink cadence: one blink step (on or off) per tick. */
58
71
  export declare const CARET_BLINK_TICK_MS = 530;
59
72
  /** Caret visibility: half the ticks on, half off (530ms blink). */
@@ -174,6 +187,35 @@ export declare function easeInOut(progress: number): number;
174
187
  * @returns the envelope value in 0..1.
175
188
  */
176
189
  export declare function envelope(elapsed: number, total: number, fadeIn: number, fadeOut: number): number;
190
+ /**
191
+ * The /rainbow celebration on the three-row composer band: a FIXED
192
+ * seven-color spectrum (not the rolled palette) that slides across every
193
+ * row as one ribbon. Triggered when switching to rainbow or rerolling the
194
+ * seed; independent of RAINBOW_SEED so the burst always reads as a prism.
195
+ */
196
+ export declare const RAINBOW_BURST_TICK_MS = 33;
197
+ export declare const RAINBOW_BURST_DURATION_MS = 1800;
198
+ export declare const RAINBOW_BURST_HUES: readonly RgbTriple[];
199
+ /**
200
+ * Smoothstep in 0..1, then wrap-lerp around the seven burst hues.
201
+ * Position 0 is red, wrapping back toward red at 1. Shared by the
202
+ * composer burst and the static rainbow whale header.
203
+ */
204
+ export declare function rainbowSpectrumHue(position: number): RgbTriple;
205
+ /**
206
+ * Background tint for one composer-band cell during the rainbow burst.
207
+ * Every row of a column shares the same hue (a solid ribbon); edge rows
208
+ * are slightly dimmer so the middle editor row reads as the crest. The
209
+ * spectrum slides ~1.2 widths over the burst, then fades to the band base.
210
+ * @param tick - frame index at {@link RAINBOW_BURST_TICK_MS}.
211
+ * @param column - band column (0..width-1).
212
+ * @param width - band width in columns.
213
+ * @param base - the theme's composerBand color to blend toward.
214
+ * @param row - band row (0..rows-1).
215
+ * @param rows - band height (composer is three rows: pad, editor, pad).
216
+ * @returns the blended RGB, or null after the burst (or at zero alpha).
217
+ */
218
+ export declare function rainbowBurstColumnBg(tick: number, column: number, width: number, base: RgbTriple, row?: number, rows?: number): RgbTriple | null;
177
219
  /**
178
220
  * The background color for one composer-band column at a tick — Codex
179
221
  * `paint_bands` + `Canvas::tint` for all three styles. Bands overlap with a
@@ -16,6 +16,32 @@ export interface InspectorViewport {
16
16
  }
17
17
  /** One transcript-to-composer gutter, collapsed on short terminals. */
18
18
  export declare function layoutGutterRows(rows: number): 0 | 1;
19
+ /** Measured chrome that sits below (or in) the live region. */
20
+ export interface LiveRegionChrome {
21
+ /** Terminal rows. */
22
+ readonly terminalRows: number;
23
+ /** Editor rows inside the composer band (frozen reports 1). */
24
+ readonly composerRows: number;
25
+ /** Status footer rows. */
26
+ readonly statusBarRows: 1 | 2;
27
+ /** Completion menu rows (0 when closed). */
28
+ readonly menuRows: number;
29
+ /** Transcript-to-composer gutter. */
30
+ readonly gutterRows: 0 | 1;
31
+ /** Notice line present. */
32
+ readonly notice: boolean;
33
+ /** Todo summary line present. */
34
+ readonly todo: boolean;
35
+ /** Agents summary line present. */
36
+ readonly agents: boolean;
37
+ }
38
+ /**
39
+ * Rows left for live transcript and streaming after pinning the composer and
40
+ * status at the bottom. Variable chrome (menu, notice, todos, agents, extra
41
+ * status row, extra editor rows) is deducted here so those rows cover the
42
+ * live region instead of growing the tree and moving the bottom bar.
43
+ */
44
+ export declare function liveRegionBudget(chrome: LiveRegionChrome): number;
19
45
  /**
20
46
  * Keep the inspector plus its persistent status/composer chrome below
21
47
  * `stdout.rows`: at equality Ink clears the terminal and rewrites all
@@ -2,7 +2,7 @@
2
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
- export type LineStyle = MdStyle | 'brand' | 'success' | 'error' | 'warn' | 'dimItalic';
5
+ export type LineStyle = MdStyle | 'brand' | 'success' | 'error' | 'warn' | 'dimItalic' | 'diffAdd' | 'diffDel' | 'promptRow' | 'promptQueuedRow' | 'promptSteeredRow';
6
6
  /** One styled run within a physical terminal row. */
7
7
  export interface StyledSegment {
8
8
  text: string;
@@ -14,6 +14,18 @@ export interface StyledLine {
14
14
  }
15
15
  /** Construct one segment without leaking mutable objects into cached rows. */
16
16
  export declare function lineSegment(text: string, style?: LineStyle): StyledSegment;
17
+ /**
18
+ * Classify one unified-diff row for coloring: additions and deletions carry
19
+ * the diff tint styles (background on rich terminals), hunk headers and file
20
+ * markers stay brand-blue, and everything else is dim context.
21
+ */
22
+ export declare function diffLineStyle(line: string): LineStyle;
23
+ /**
24
+ * Extend pure diff-tinted rows to the full width with same-styled padding so
25
+ * the tint reads as one unbroken bar (GitHub-style), including wrapped
26
+ * continuation rows; mixed or non-diff rows pass through untouched.
27
+ */
28
+ export declare function fillDiffLineBars(lines: readonly StyledLine[], columns: number): readonly StyledLine[];
17
29
  /**
18
30
  * Sanitize and hard-wrap styled content into exact physical rows.
19
31
  * Tabs become two visible spaces because terminal tab stops are contextual
@@ -30,6 +42,14 @@ export declare function markdownLines(text: string, columns: number): readonly S
30
42
  * reasoning content and assistant Markdown share one left edge.
31
43
  */
32
44
  export declare function reasoningLines(text: string, columns: number): readonly StyledLine[];
45
+ /**
46
+ * Style the lines of one user prompt for display: plain verbatim, except
47
+ * inside ```diff / ```patch fences where added and removed lines take the
48
+ * shared diff tints (the review prompt pastes its diff this way, and the
49
+ * user row does not go through the markdown renderer). The fence markers
50
+ * and file headers stay plain.
51
+ */
52
+ export declare function userPromptSegments(text: string): readonly StyledSegment[];
33
53
  /**
34
54
  * Convert one durable transcript entry to its complete scrollable row model.
35
55
  * The source entry stays intact; only the caller's visible slice is rendered.
@@ -9,7 +9,7 @@
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' | 'accentBold' | 'dim' | 'strike';
12
+ export type MdStyle = 'plain' | 'bold' | 'italic' | 'boldItalic' | 'code' | 'accent' | 'accentBold' | 'dim' | 'strike' | 'diffAdd' | 'diffDel';
13
13
  /** One styled run of text. */
14
14
  export interface MdSegment {
15
15
  /** Visible text (no ANSI). */
@@ -23,6 +23,12 @@ export interface UserEntry {
23
23
  images?: readonly ImageBlock['attachment'][];
24
24
  /** Durable file references carried by this prompt (0.1.5 file blocks). */
25
25
  files?: readonly FileAttachmentRef[];
26
+ /**
27
+ * How the prompt reached the agent, when it did not arrive as an ordinary
28
+ * submission: `queued` waited for this turn, `steered` joined it mid-flight.
29
+ * Absent for a prompt typed straight into an idle composer.
30
+ */
31
+ delivery?: 'queued' | 'steered';
26
32
  }
27
33
  /** One user message waiting in the agent inbox (the web's queued-message row). */
28
34
  export interface PendingEntry {
@@ -79,6 +85,68 @@ export interface ToolEntry {
79
85
  * result lands and only when something renderable exists.
80
86
  */
81
87
  detail: ToolDetail | undefined;
88
+ /**
89
+ * Nested PTC sub-dispatches (`run_code`) in start order, bounded to the
90
+ * newest {@link MAX_TOOL_SUB_DISPATCHES} rows.
91
+ */
92
+ subs: readonly ToolSubDispatch[];
93
+ /** Sub-dispatches evicted from the bounded window. */
94
+ subsDropped: number;
95
+ }
96
+ /**
97
+ * One nested PTC sub-dispatch under a `run_code` parent call: the durable
98
+ * `tool/ptc-dispatch-start`/`tool/ptc-dispatch` pair folded to one bounded
99
+ * row (upstream contract: pair by `subCallId`, every start settles, and the
100
+ * settle carries `tool/result`'s own vocabulary).
101
+ */
102
+ export interface ToolSubDispatch {
103
+ /** Opaque sub-call id pairing the start with its settle. */
104
+ subCallId: string;
105
+ /** Sub-call tool name. */
106
+ name: string;
107
+ /** Bounded arguments preview. */
108
+ preview: string;
109
+ /** Lifecycle; `running` until the paired settle lands. */
110
+ state: 'running' | 'done' | 'error';
111
+ /** Bounded first text block of the settle content, '' until it lands. */
112
+ summary: string;
113
+ /** Wall-clock duration (settle − start), 0 while running. */
114
+ durationMs: number;
115
+ }
116
+ /** Bounded sub-dispatch window per tool card (display budget only). */
117
+ export declare const MAX_TOOL_SUB_DISPATCHES = 12;
118
+ /** Bounded member window per workflow run card (display budget only). */
119
+ export declare const MAX_WORKFLOW_MEMBERS = 12;
120
+ /** One workflow member (an `agent()` call inside a `workflow` script). */
121
+ export interface WorkflowMember {
122
+ /** Member sequence within the run (the agent-start/agent-end pairing key). */
123
+ seq: number;
124
+ /** Display label. */
125
+ label: string;
126
+ /** Declared phase title, '' when none. */
127
+ phase: string;
128
+ /** Child session id (cross-links the subagent feed's rows). */
129
+ childId: string;
130
+ /** Settlement; `running` until the paired `tool-workflow/agent-end`. */
131
+ outcome: 'running' | 'completed' | 'failed' | 'cancelled';
132
+ }
133
+ /**
134
+ * One durable workflow run (the `tool-workflow/*` record a `workflow` or
135
+ * `ralph` tool appends to the parent session): run identity plus its
136
+ * bounded member list, live until `tool-workflow/run-end` settles.
137
+ */
138
+ export interface WorkflowEntry {
139
+ kind: 'workflow';
140
+ /** Stable run identity shared by every event of the run. */
141
+ runId: string;
142
+ /** Display name of the run. */
143
+ name: string;
144
+ /** Members in sequence order, bounded to the newest window. */
145
+ members: readonly WorkflowMember[];
146
+ /** Members evicted from the bounded window. */
147
+ membersDropped: number;
148
+ /** Run settlement; `running` until `tool-workflow/run-end`. */
149
+ state: 'running' | 'completed' | 'cancelled' | 'error';
82
150
  }
83
151
  /** One slash-command execution dispatched through `ctx.commands`. */
84
152
  export interface CommandEntry {
@@ -144,7 +212,7 @@ export interface FilesEntry {
144
212
  paths: readonly string[];
145
213
  }
146
214
  /** Ordered transcript items the renderer draws. */
147
- export type TranscriptEntry = UserEntry | PendingEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry;
215
+ export type TranscriptEntry = UserEntry | PendingEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry | WorkflowEntry;
148
216
  /** The live goal the status line badges, folded from `goal/change`. */
149
217
  export interface GoalFold {
150
218
  /** Human-requested completion objective. */
@@ -157,14 +225,21 @@ export interface GoalFold {
157
225
  /** Blocked explanation, empty outside the blocked phase. */
158
226
  blocked: string;
159
227
  }
160
- /** Cumulative token accounting folded from `assistant/message` usage reports. */
228
+ /**
229
+ * Cumulative token accounting folded from `assistant/message` usage reports.
230
+ * The buckets are disjoint and mirror the provider's report, so the prompt
231
+ * side is never double counted: reasoning tokens are already inside
232
+ * `outputTokens`, and cache reads are never folded into the uncached input.
233
+ */
161
234
  export interface UsageTotals {
162
- /** Prompt-side billed tokens: `inputTokens` plus both cache buckets. */
163
- inputTokens: number;
235
+ /** Prompt-side tokens billed outside the cache. */
236
+ uncachedInputTokens: number;
164
237
  /** Completion-side tokens over the whole log. */
165
238
  outputTokens: number;
166
239
  /** Cache-read tokens over the whole log (0 when the adapter reports none). */
167
240
  cacheReadTokens: number;
241
+ /** Cache-write tokens over the whole log (0 when the adapter reports none). */
242
+ cacheWriteTokens: number;
168
243
  }
169
244
  /**
170
245
  * Estimated used tokens per context content type, folded from transcript
@@ -308,6 +383,14 @@ export interface TranscriptView {
308
383
  'next-turn': readonly string[];
309
384
  'next-step': readonly string[];
310
385
  };
386
+ /**
387
+ * In-flight inbox messages by the list they were inserted into, kept until
388
+ * the durable user message that claims them lands. The claim itself is a
389
+ * plain splice that empties the pending row first, so this map — not
390
+ * {@link pending} — is what lets a settled prompt say it was queued or
391
+ * steered rather than typed into an idle composer.
392
+ */
393
+ claimOrigin: ReadonlyMap<string, 'next-turn' | 'next-step'>;
311
394
  /**
312
395
  * Fold-internal timing anchors, never rendered: open step and tool-call
313
396
  * start timestamps the next `assistant/message` / `tool/result` resolves
@@ -319,6 +402,8 @@ export interface TranscriptView {
319
402
  readonly anchors: {
320
403
  stepStart: Map<string, number>;
321
404
  toolStart: Map<string, number>;
405
+ /** Open PTC sub-dispatch starts by `subCallId` (duration anchors). */
406
+ subStart: Map<string, number>;
322
407
  firstChunkAt: Map<string, number>;
323
408
  compactionTokens: Map<string, number>;
324
409
  lastPruneTokens: number;
@@ -375,11 +460,15 @@ export interface ReplayAccumulator {
375
460
  retryIndex: Map<string, number[]>;
376
461
  /** messageId → every index into `entries` holding a `pending` row with that id. */
377
462
  pendingIndex: Map<string, number[]>;
463
+ /** runId → every index into `entries` holding a `workflow` row with that id. */
464
+ workflowIndex: Map<string, number[]>;
378
465
  /** Tombstone count; zero means `entries` is already the final array. */
379
466
  removedCount: number;
380
467
  /** Mutable inbox id lists, mirroring `view.pending` order per target. */
381
468
  pendingTurn: string[];
382
469
  pendingStep: string[];
470
+ /** Mutable mirror of `view.claimOrigin` (see the reducer's field doc). */
471
+ claimOrigin: Map<string, 'next-turn' | 'next-step'>;
383
472
  streaming: string;
384
473
  streamingReasoning: string;
385
474
  todos: readonly TodoItem[];
@@ -398,6 +487,8 @@ export interface ReplayAccumulator {
398
487
  stats: TranscriptStats;
399
488
  stepStart: Map<string, number>;
400
489
  toolStart: Map<string, number>;
490
+ /** Open PTC sub-dispatch starts by `subCallId` (duration anchors). */
491
+ subStart: Map<string, number>;
401
492
  firstChunkAt: Map<string, number>;
402
493
  compactionTokens: Map<string, number>;
403
494
  lastPruneTokens: number;
@@ -10,13 +10,8 @@
10
10
  * @module @deepseek-ai/dsh-tui/render/status
11
11
  */
12
12
  import type { TranscriptStats } from './projection.ts';
13
- /**
14
- * Compact token count: 517 / 12.2K / 517K / 1.2M (one decimal under three
15
- * digits), mirroring the web composer's StatsLine format.
16
- * @param n - token count.
17
- * @returns display string.
18
- */
19
- export declare function formatTokens(n: number): string;
13
+ import { formatTokens } from './text.ts';
14
+ export { formatTokens };
20
15
  /**
21
16
  * Compact duration: 45.2s under a minute, 2m42s from there on.
22
17
  * @param ms - duration in milliseconds.
@@ -24,7 +19,9 @@ export declare function formatTokens(n: number): string;
24
19
  */
25
20
  export declare function formatDuration(ms: number): string;
26
21
  /**
27
- * Cache-hit share of billed prompt-side input.
22
+ * Cache-hit share of billed prompt-side input. The denominator is the same
23
+ * billed total the /usage panel shows (uncached input plus both cache
24
+ * buckets), so the two readouts can never disagree.
28
25
  * @param usage - cumulative token totals.
29
26
  * @returns percent rounded to one decimal place, or null when no input was billed.
30
27
  */
@@ -70,7 +67,10 @@ export declare const STATUS_GROUP_SEPARATOR = " | ";
70
67
  /** Separator between trailing state spans. */
71
68
  export declare const STATUS_ITEM_SEPARATOR = " \u00B7 ";
72
69
  /** The Codex-style mode cycle hint appended to the permission badge. */
70
+ /** English compatibility value for callers that only measure the default layout. */
73
71
  export declare const STATUS_CYCLE_HINT = " (shift+tab to cycle)";
72
+ /** Localized mode-cycle hint used by the live layout. */
73
+ export declare function statusCycleHint(): string;
74
74
  /**
75
75
  * Interior columns of the context bar. The layout starts every bar at this
76
76
  * width so the drop ladder can pre-measure the group, then degrades the
@@ -11,6 +11,12 @@
11
11
  *
12
12
  * @module @deepseek-ai/dsh-code/render/text
13
13
  */
14
+ /**
15
+ * Compact token count: exact below 1K, then one-decimal-ish K/M.
16
+ * @param n - token count.
17
+ * @returns display string.
18
+ */
19
+ export declare function formatTokens(n: number): string;
14
20
  /**
15
21
  * Escape control and deceptive characters so externally sourced text cannot
16
22
  * drive the terminal. C0/C1/DEL render as a literal `\xNN` escape; bidi,
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Session usage for the /usage panel: the provider-reported token totals, the
3
+ * context pressure and composition, and the exact per-turn accounting, folded
4
+ * into the styled rows the panel draws.
5
+ *
6
+ * Every figure comes from the harness token meter. The four token buckets are
7
+ * disjoint, so the prompt side is never double counted; the composition block
8
+ * is a density estimate and is labelled as one.
9
+ *
10
+ * @module @deepseek-ai/dsh-tui/render/usage
11
+ */
12
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
13
+ import type { TokenUsageProjection, TurnTokenUsage } from '@deepseek-ai/dsh-token-meter/client';
14
+ import { type StyledLine } from './lines.ts';
15
+ /** One completed turn's exact provider-reported accounting. */
16
+ export interface UsageTurn {
17
+ /** Durable turn number (`turn/start`). */
18
+ readonly turn: number;
19
+ readonly usage: TurnTokenUsage;
20
+ /**
21
+ * The model that billed this turn, or '' when nothing in the log names one.
22
+ * The meter's own `routes` is preferred; it is absent whenever ONE attempt
23
+ * went unattributed, so the turn's own assistant messages answer instead.
24
+ */
25
+ readonly model: string;
26
+ }
27
+ /**
28
+ * Everything the panel reads. The totals come from the mounted projection and
29
+ * the per-turn rows from the meter's own fold; a missing projection renders as
30
+ * explicitly unavailable rather than as zeros, because an unmounted deployment
31
+ * and a session with no traffic are different facts.
32
+ */
33
+ export interface UsageView {
34
+ /** Provider-reported usage over the whole durable log. */
35
+ readonly totals?: TokenUsageProjection;
36
+ /** Completed turns, oldest first. */
37
+ readonly turns: readonly UsageTurn[];
38
+ }
39
+ /** Prompt-side tokens the provider billed: uncached input plus both cache buckets. */
40
+ export declare function billedInputTokens(totals: TokenUsageProjection): number;
41
+ /** Prompt plus completion tokens over the whole log. */
42
+ export declare function usageTotalTokens(totals: TokenUsageProjection): number;
43
+ /**
44
+ * Cache-hit share of billed prompt-side input.
45
+ * @param totals - cumulative provider-reported buckets.
46
+ * @returns percent rounded to one decimal place, or null when nothing was billed.
47
+ */
48
+ export declare function usageCacheHitPercent(totals: TokenUsageProjection): number | null;
49
+ /** One complete turn's durable events, in log order. */
50
+ export interface TurnSlice {
51
+ readonly turn: number;
52
+ readonly events: readonly SessionEvent[];
53
+ }
54
+ /**
55
+ * Split a durable log into COMPLETE turns. A turn still running has no
56
+ * `turn/end` yet, and an unfinished attempt has no exact accounting, so the
57
+ * trailing slice is dropped rather than guessed at.
58
+ * @param events - the whole durable log, in log order.
59
+ * @returns one slice per `turn/start`…`turn/end` span, oldest first.
60
+ */
61
+ export declare function completedTurns(events: readonly SessionEvent[]): readonly TurnSlice[];
62
+ /**
63
+ * Exact usage for every completed turn that can be proven.
64
+ * @param events - the whole durable log, in log order.
65
+ * @param derive - the meter's per-turn fold, injected so this module stays pure.
66
+ * @returns one row per provable turn, oldest first; turns whose attempts did
67
+ * not all report usage are omitted rather than estimated, and so is a turn
68
+ * that billed nothing at all — an empty row is noise in a usage table.
69
+ */
70
+ export declare function turnUsages(events: readonly SessionEvent[], derive: (events: readonly SessionEvent[]) => TurnTokenUsage | undefined): readonly UsageTurn[];
71
+ /** A bucket some turn of a group did not report, leaving the group sum a floor. */
72
+ export type PartialBucket = 'cacheReadTokens' | 'cacheWriteTokens' | 'reasoningTokens';
73
+ /** One model's merged totals across every turn it billed. */
74
+ export interface ModelUsage {
75
+ /** Model names joined with ` + ` (a turn that switched models lists both). */
76
+ readonly model: string;
77
+ /** Turns attributed to this model. */
78
+ readonly turns: number;
79
+ readonly uncachedInputTokens: number;
80
+ readonly outputTokens: number;
81
+ readonly totalTokens: number;
82
+ /** Summed over the turns that reported it; absent when none did. */
83
+ readonly cacheReadTokens?: number;
84
+ /** Summed over the turns that reported it; absent when none did. */
85
+ readonly cacheWriteTokens?: number;
86
+ /** Summed over the turns that reported it; absent when none did. */
87
+ readonly reasoningTokens?: number;
88
+ /**
89
+ * Buckets some turn of the group left unreported. The corresponding sum (and
90
+ * the hit share derived from it) counts only what WAS reported, so the
91
+ * display marks it as a floor rather than hiding the group's known traffic.
92
+ */
93
+ readonly partial: readonly PartialBucket[];
94
+ }
95
+ /**
96
+ * Merge the per-turn rows by the model that billed them, biggest spender
97
+ * first. A turn lands in exactly one group — the group named by every model it
98
+ * used — so the sums stay additive and a mid-turn model switch never counts
99
+ * the same tokens twice. Each bucket is merged over the turns that reported
100
+ * it, and {@link ModelUsage.partial} records the ones that stayed silent.
101
+ * @param turns - provable per-turn rows, oldest first.
102
+ * @returns one row per model group.
103
+ */
104
+ export declare function modelTotals(turns: readonly UsageTurn[]): readonly ModelUsage[];
105
+ /** The model attribution for one turn: the meter's routes, else its messages. */
106
+ export declare function turnModel(slice: TurnSlice, usage: TurnTokenUsage): string;
107
+ /**
108
+ * Render the panel body.
109
+ * @param view - the projection totals plus the derived per-turn rows.
110
+ * @param columns - usable content columns inside the panel border.
111
+ * @returns bounded, styled rows ready to draw.
112
+ */
113
+ export declare function usageLines(view: UsageView, columns: number): readonly StyledLine[];
@@ -24,6 +24,23 @@ export interface SessionQueryService {
24
24
  listSessions(signal?: AbortSignal): Promise<SessionRecord[]>;
25
25
  readTitleSnapshots(ids: readonly string[], signal?: AbortSignal): Promise<TitleObservationResult[]>;
26
26
  readSession(id: string, signal?: AbortSignal): Promise<SessionLogSnapshot>;
27
+ /** Cross-session full-text search (SQLite FTS engine; openAt may gate it). */
28
+ searchSessions(request: {
29
+ query: string;
30
+ limit?: number;
31
+ }, exec?: {
32
+ signal?: AbortSignal;
33
+ }): Promise<{
34
+ items: readonly {
35
+ header: SessionHeader;
36
+ live: boolean;
37
+ persisted: boolean;
38
+ bestMatch: {
39
+ snippet: string;
40
+ time: number;
41
+ };
42
+ }[];
43
+ }>;
27
44
  }
28
45
  export type SessionScope = 'roots' | 'all';
29
46
  export type CwdScope = 'all' | 'current';
@@ -14,7 +14,7 @@
14
14
  * whose project directory matches the current working directory.
15
15
  * - `--session <id>` — create a new session under an explicit identity (the
16
16
  * id must not exist yet).
17
- * - `--theme <dark|light|auto>` — the color palette; auto follows the
17
+ * - `--theme <dark|light|prismatic|rainbow|auto>` — the color palette; auto follows the
18
18
  * terminal (dark fallback until OSC-11 detection lands).
19
19
  * - no flags — a fresh session with a minted id.
20
20
  *
@@ -28,6 +28,14 @@ export declare function terminalTitleSequence(text: string): string;
28
28
  /** Clear the managed title with an empty OSC payload; the terminal falls back
29
29
  * to its own default label. */
30
30
  export declare function clearTerminalTitleSequence(): string;
31
+ /**
32
+ * Resolve the VS Code stable user-settings file for one platform and
33
+ * environment: `%APPDATA%` on Windows, the bundle data folder on macOS (NOT
34
+ * XDG `~/.config` — VS Code never reads that path there, so writing it
35
+ * silently no-ops and the tab kept showing the process name "node"), and
36
+ * XDG config on Linux.
37
+ */
38
+ export declare function vscodeSettingsPath(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): string | undefined;
31
39
  /** Outcome of the VS Code settings alignment. */
32
40
  export interface VsCodeTitleSettingResult {
33
41
  wrote: boolean;
@@ -18,7 +18,7 @@ export declare function ThemePanel({ current, select, close }: {
18
18
  /** Theme name in force (the requested name; 'auto' included). */
19
19
  current: ThemeName;
20
20
  /** Accept one theme name: applied immediately and persisted by the runner. */
21
- select(name: ThemeName): void;
21
+ select: (name: ThemeName) => void;
22
22
  /** Close without changing the theme. */
23
- close(): void;
23
+ close: () => void;
24
24
  }): ReactElement;