@toddzheng024/dscode-bundle 0.7.12 → 0.7.14

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 (167) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/cordis.patch.yml +6 -2
  3. package/package.json +7 -5
  4. package/plugins/code-review/index.mjs +5 -2
  5. package/plugins/compaction/threshold.mjs +12 -0
  6. package/plugins/credentials/index.mjs +13 -0
  7. package/plugins/grok/adapter.mjs +114 -0
  8. package/plugins/grok/auth.mjs +76 -0
  9. package/plugins/grok/billing.mjs +146 -0
  10. package/plugins/grok/index.mjs +118 -0
  11. package/plugins/grok/models.mjs +129 -0
  12. package/plugins/grok/status.mjs +28 -0
  13. package/plugins/grok/wire.mjs +268 -0
  14. package/plugins/i18n/messages.mjs +6 -6
  15. package/plugins/providers/catalog.mjs +2 -0
  16. package/plugins/session-metrics/view.mjs +34 -3
  17. package/vendor/compaction-basic/index.js +91 -1
  18. package/vendor/persistent/index.js +10 -9
  19. package/vendor/terminal/index.js +5 -0
  20. package/vendor/tui/lib/app.mjs +6711 -0
  21. package/vendor/tui/lib/approval.mjs +122 -0
  22. package/vendor/tui/lib/attachments.mjs +218 -0
  23. package/vendor/tui/lib/authorization-panel.mjs +242 -0
  24. package/vendor/tui/lib/authorization.mjs +111 -0
  25. package/vendor/tui/lib/commands.mjs +123 -0
  26. package/vendor/tui/lib/dscode/chat.mjs +84 -0
  27. package/vendor/tui/lib/dscode/flags.mjs +32 -0
  28. package/vendor/tui/lib/dscode/model-search.mjs +68 -0
  29. package/vendor/tui/lib/dscode/paste.mjs +87 -0
  30. package/vendor/tui/lib/dscode/telemetry.mjs +82 -0
  31. package/vendor/tui/lib/dscode/welcome.mjs +100 -0
  32. package/vendor/tui/lib/editor-keys.mjs +346 -0
  33. package/vendor/tui/lib/editor.mjs +50 -0
  34. package/vendor/tui/lib/fork.mjs +24 -0
  35. package/vendor/tui/lib/git-workflow.mjs +278 -0
  36. package/vendor/tui/{types/history.d.ts → lib/history.mjs} +69 -26
  37. package/vendor/tui/{types/i18n.d.ts → lib/i18n.mjs} +23 -15
  38. package/vendor/tui/lib/index.mjs +2200 -0
  39. package/vendor/tui/lib/input-split.mjs +170 -0
  40. package/vendor/tui/lib/internals.mjs +90 -0
  41. package/vendor/tui/lib/invariant.mjs +22 -0
  42. package/vendor/tui/lib/kernel-panels.mjs +1265 -0
  43. package/vendor/tui/lib/keyboard.mjs +283 -0
  44. package/vendor/tui/lib/language-panel.mjs +41 -0
  45. package/vendor/tui/lib/locales/en.mjs +495 -0
  46. package/vendor/tui/lib/locales/zh.mjs +495 -0
  47. package/vendor/tui/lib/mentions.mjs +150 -0
  48. package/vendor/tui/lib/model-capabilities.mjs +245 -0
  49. package/vendor/tui/lib/models.mjs +215 -0
  50. package/vendor/tui/{types/panel-accent.d.ts → lib/panel-accent.mjs} +11 -7
  51. package/vendor/tui/lib/permissions.mjs +45 -0
  52. package/vendor/tui/lib/plugin-inventory.mjs +27 -0
  53. package/vendor/tui/lib/presets.mjs +46 -0
  54. package/vendor/tui/lib/provider-settings.mjs +600 -0
  55. package/vendor/tui/lib/questions.mjs +116 -0
  56. package/vendor/tui/lib/rainbow.mjs +181 -0
  57. package/vendor/tui/lib/render/animations.mjs +665 -0
  58. package/vendor/tui/lib/render/editor.mjs +437 -0
  59. package/vendor/tui/lib/render/export.mjs +119 -0
  60. package/vendor/tui/lib/render/fuzzy.mjs +79 -0
  61. package/vendor/tui/lib/render/ime-cursor.mjs +135 -0
  62. package/vendor/tui/lib/render/inspector.mjs +93 -0
  63. package/vendor/tui/lib/render/lines.mjs +538 -0
  64. package/vendor/tui/lib/render/markdown.mjs +592 -0
  65. package/vendor/tui/lib/render/projection.mjs +1864 -0
  66. package/vendor/tui/lib/render/status.mjs +591 -0
  67. package/vendor/tui/lib/render/text.mjs +157 -0
  68. package/vendor/tui/lib/render/tool-detail.mjs +187 -0
  69. package/vendor/tui/lib/render/tool-preview.mjs +79 -0
  70. package/vendor/tui/lib/render/usage.mjs +336 -0
  71. package/vendor/tui/lib/render/width.mjs +191 -0
  72. package/vendor/tui/lib/session-directory.mjs +329 -0
  73. package/vendor/tui/lib/session-query.mjs +184 -0
  74. package/vendor/tui/lib/session-switch.mjs +51 -0
  75. package/vendor/tui/lib/settings-file.mjs +75 -0
  76. package/vendor/tui/lib/skills.mjs +119 -0
  77. package/vendor/tui/lib/startup.mjs +111 -0
  78. package/vendor/tui/lib/store.mjs +129 -0
  79. package/vendor/tui/lib/subagents.mjs +200 -0
  80. package/vendor/tui/lib/terminal-title.mjs +186 -0
  81. package/vendor/tui/lib/theme-panel.mjs +60 -0
  82. package/vendor/tui/lib/theme.mjs +379 -0
  83. package/vendor/tui/lib/update-panel.mjs +239 -0
  84. package/vendor/tui/lib/update.mjs +101 -0
  85. package/vendor/tui/lib/version.mjs +105 -0
  86. package/vendor/tui/lib/whale-glyph.mjs +20 -0
  87. package/vendor/tui/LICENSE +0 -21
  88. package/vendor/tui/devtools-CdTl3MNy.mjs +0 -3643
  89. package/vendor/tui/dscode-email/cli.mjs +0 -32
  90. package/vendor/tui/dscode-email/contacts.mjs +0 -36
  91. package/vendor/tui/dscode-email/gmail-oauth.mjs +0 -69
  92. package/vendor/tui/dscode-email/gmail-store.mjs +0 -2
  93. package/vendor/tui/dscode-email/gmail.mjs +0 -194
  94. package/vendor/tui/dscode-email/imap.mjs +0 -136
  95. package/vendor/tui/dscode-email/inbox.d.mts +0 -25
  96. package/vendor/tui/dscode-email/inbox.mjs +0 -76
  97. package/vendor/tui/dscode-email/smtp.mjs +0 -69
  98. package/vendor/tui/dscode-email/store.mjs +0 -32
  99. package/vendor/tui/dscode-providers/catalog.mjs +0 -136
  100. package/vendor/tui/dscode-providers/effort.mjs +0 -35
  101. package/vendor/tui/dscode-providers/openrouter-account.mjs +0 -171
  102. package/vendor/tui/index.mjs +0 -44441
  103. package/vendor/tui/invariant.mjs +0 -21
  104. package/vendor/tui/rolldown-runtime-CMFfr-1z.mjs +0 -26
  105. package/vendor/tui/session-query.mjs +0 -150
  106. package/vendor/tui/startup.mjs +0 -109
  107. package/vendor/tui/theme-7u5Qo3dF.mjs +0 -1265
  108. package/vendor/tui/types/app.d.ts +0 -348
  109. package/vendor/tui/types/approval.d.ts +0 -59
  110. package/vendor/tui/types/attachments.d.ts +0 -52
  111. package/vendor/tui/types/authorization-panel.d.ts +0 -22
  112. package/vendor/tui/types/authorization.d.ts +0 -36
  113. package/vendor/tui/types/commands.d.ts +0 -52
  114. package/vendor/tui/types/editor-keys.d.ts +0 -105
  115. package/vendor/tui/types/editor.d.ts +0 -6
  116. package/vendor/tui/types/fork.d.ts +0 -8
  117. package/vendor/tui/types/git-workflow.d.ts +0 -121
  118. package/vendor/tui/types/index.d.ts +0 -223
  119. package/vendor/tui/types/input-split.d.ts +0 -54
  120. package/vendor/tui/types/internals.d.ts +0 -26
  121. package/vendor/tui/types/invariant.d.ts +0 -15
  122. package/vendor/tui/types/kernel-panels.d.ts +0 -245
  123. package/vendor/tui/types/keyboard.d.ts +0 -80
  124. package/vendor/tui/types/language-panel.d.ts +0 -12
  125. package/vendor/tui/types/locales/en.d.ts +0 -450
  126. package/vendor/tui/types/locales/zh.d.ts +0 -9
  127. package/vendor/tui/types/mentions.d.ts +0 -85
  128. package/vendor/tui/types/model-capabilities.d.ts +0 -82
  129. package/vendor/tui/types/models.d.ts +0 -133
  130. package/vendor/tui/types/permissions.d.ts +0 -27
  131. package/vendor/tui/types/plugin-inventory.d.ts +0 -11
  132. package/vendor/tui/types/presets.d.ts +0 -22
  133. package/vendor/tui/types/provider-settings.d.ts +0 -239
  134. package/vendor/tui/types/questions.d.ts +0 -54
  135. package/vendor/tui/types/rainbow.d.ts +0 -69
  136. package/vendor/tui/types/render/animations.d.ts +0 -307
  137. package/vendor/tui/types/render/editor.d.ts +0 -163
  138. package/vendor/tui/types/render/export.d.ts +0 -9
  139. package/vendor/tui/types/render/fuzzy.d.ts +0 -21
  140. package/vendor/tui/types/render/ime-cursor.d.ts +0 -60
  141. package/vendor/tui/types/render/inspector.d.ts +0 -62
  142. package/vendor/tui/types/render/lines.d.ts +0 -86
  143. package/vendor/tui/types/render/markdown.d.ts +0 -29
  144. package/vendor/tui/types/render/projection.d.ts +0 -587
  145. package/vendor/tui/types/render/status.d.ts +0 -196
  146. package/vendor/tui/types/render/text.d.ts +0 -64
  147. package/vendor/tui/types/render/tool-detail.d.ts +0 -94
  148. package/vendor/tui/types/render/tool-preview.d.ts +0 -28
  149. package/vendor/tui/types/render/usage.d.ts +0 -113
  150. package/vendor/tui/types/render/width.d.ts +0 -29
  151. package/vendor/tui/types/session-directory.d.ts +0 -190
  152. package/vendor/tui/types/session-query.d.ts +0 -92
  153. package/vendor/tui/types/session-switch.d.ts +0 -17
  154. package/vendor/tui/types/settings-file.d.ts +0 -41
  155. package/vendor/tui/types/skills.d.ts +0 -47
  156. package/vendor/tui/types/startup.d.ts +0 -65
  157. package/vendor/tui/types/store.d.ts +0 -58
  158. package/vendor/tui/types/subagents.d.ts +0 -70
  159. package/vendor/tui/types/terminal-title.d.ts +0 -66
  160. package/vendor/tui/types/theme-panel.d.ts +0 -24
  161. package/vendor/tui/types/theme.d.ts +0 -434
  162. package/vendor/tui/types/update-panel.d.ts +0 -49
  163. package/vendor/tui/types/update.d.ts +0 -75
  164. package/vendor/tui/types/version.d.ts +0 -19
  165. package/vendor/tui/types/whale-glyph.d.ts +0 -6
  166. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/clipboard-image.swift +0 -0
  167. /package/vendor/tui/{dscode-clipboard-image → lib/dscode/clipboard-image}/index.mjs +0 -0
@@ -0,0 +1,665 @@
1
+ /**
2
+ * Terminal animation helpers derived from the web design language:
3
+ * thinking uses Codex's slow shimmer sweep, the busy composer marker uses the
4
+ * original braille chase, and the streaming caret blink is the Claude-Code
5
+ * convention.
6
+ *
7
+ * The DeepSeek model-switch easter egg ports Codex's effort-ignition "Wave"
8
+ * style (`codex-rs/tui/src/bottom_pane/effort_ignition_styles.rs`): switching
9
+ * INTO an official DeepSeek route sweeps a blue wave across the composer's
10
+ * input row — one column per cell, `backgroundColor` = the sampled wave
11
+ * color — then, on the deepseek (Ultra-equivalent) tier, drops the `· ✦ ✧`
12
+ * sparkle sequence into the rightmost blank cell before fading. The prompt
13
+ * marker keeps the tier accent afterwards (persistent, like Codex's prompt
14
+ * charge). Pure functions only — the Ink layer owns timers and colors.
15
+ *
16
+ * Wave and Pulse deliberately extend the Codex port after in-terminal
17
+ * testing: the per-row phase cascade was removed (Codex tints each column
18
+ * across the whole band), Wave became a WATER SURFACE — one continuous sine
19
+ * swell spanning the band, mirror-symmetric about the center column, its
20
+ * crests flowing outward from the center with a symmetric fade envelope
21
+ * (the deepseek tier adds one faster harmonic crossing it), painted with
22
+ * Aurora's recipe: wide soft gradients, mirrored second-hue mixing, and a
23
+ * low alpha cap — no hard core line — while Pulse became true
24
+ * two-dimensional, cell-aspect-corrected detonations: soft wide rings whose
25
+ * color grades across their width, expanding outward through a symmetric
26
+ * fade envelope and each trailing an echo ripple in the next blue. Aurora
27
+ * keeps Codex's geometry verbatim.
28
+ *
29
+ * @module @deepseek-ai/dsh-code/render/animations
30
+ */
31
+ /** Cadence for the original busy braille chase (8 frames × 125ms = 1s). */
32
+ export const BUSY_CHASE_TICK_MS = 125;
33
+ /** Original terminal StateDot chase frames. */
34
+ export const BUSY_CHASE_FRAMES = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
35
+ /** Chase frame for a monotonic tick. */
36
+ export function busyChaseFrame(tick) {
37
+ return BUSY_CHASE_FRAMES[tick % BUSY_CHASE_FRAMES.length] ?? BUSY_CHASE_FRAMES[0];
38
+ }
39
+ /** Clock cadence for the Codex-style Deep diving shimmer. */
40
+ export const DEEP_DIVING_SHIMMER_TICK_MS = 33;
41
+ /** Codex shimmer timing and geometry. */
42
+ export const DEEP_DIVING_SHIMMER_DURATION_MS = 2_000;
43
+ export const DEEP_DIVING_SHIMMER_PADDING = 10;
44
+ export const DEEP_DIVING_SHIMMER_HALF_WIDTH = 5;
45
+ export const DEEP_DIVING_SPARK_BREATH_DURATION_MS = 2_000;
46
+ /**
47
+ * Codex's 2-second shimmer sweep, expressed in terminal ticks. The sweep has
48
+ * ten virtual columns of padding on either side and a five-column cosine
49
+ * highlight band, so the text changes gently rather than cycling rapidly.
50
+ */
51
+ export function deepDivingShimmerIntensity(index, tick, graphemeCount) {
52
+ if (graphemeCount <= 0)
53
+ return 0;
54
+ const period = graphemeCount + DEEP_DIVING_SHIMMER_PADDING * 2;
55
+ const elapsed = ((tick * DEEP_DIVING_SHIMMER_TICK_MS) % DEEP_DIVING_SHIMMER_DURATION_MS + DEEP_DIVING_SHIMMER_DURATION_MS) % DEEP_DIVING_SHIMMER_DURATION_MS;
56
+ const position = elapsed / DEEP_DIVING_SHIMMER_DURATION_MS * period;
57
+ const distance = Math.abs(index + DEEP_DIVING_SHIMMER_PADDING - position);
58
+ if (distance > DEEP_DIVING_SHIMMER_HALF_WIDTH)
59
+ return 0;
60
+ const angle = Math.PI * distance / DEEP_DIVING_SHIMMER_HALF_WIDTH;
61
+ return 0.5 * (1 + Math.cos(angle));
62
+ }
63
+ /** Blue RGB color for one grapheme in the Codex-style shimmer. */
64
+ export function deepDivingGradientColor(index, tick, graphemeCount, base, highlight) {
65
+ return blendRgb(highlight, base, deepDivingShimmerIntensity(index, tick, graphemeCount));
66
+ }
67
+ /** Smooth breathing intensity for the always-visible Deep diving sparkle. */
68
+ export function deepDivingSparkIntensity(tick) {
69
+ const elapsed = ((tick * DEEP_DIVING_SHIMMER_TICK_MS) % DEEP_DIVING_SPARK_BREATH_DURATION_MS
70
+ + DEEP_DIVING_SPARK_BREATH_DURATION_MS) % DEEP_DIVING_SPARK_BREATH_DURATION_MS;
71
+ const phase = elapsed / DEEP_DIVING_SPARK_BREATH_DURATION_MS;
72
+ return 0.2 + 0.8 * (0.5 + 0.5 * Math.cos(Math.PI * 2 * phase));
73
+ }
74
+ /** Blue RGB color for the breathing Deep diving sparkle. */
75
+ export function deepDivingSparkColor(tick, base, highlight) {
76
+ return blendRgb(highlight, base, deepDivingSparkIntensity(tick));
77
+ }
78
+ /** One full prismatic flow lap — lively: violet → fuchsia → cyan → violet in 2.4s. */
79
+ export const FLOW_PERIOD_MS = 2_400;
80
+ /**
81
+ * The prismatic flow color at one elapsed-time sample: a smoothstep walk
82
+ * around the anchor triangle, one full lap per {@link FLOW_PERIOD_MS}. Pure —
83
+ * the Ink layer owns the timer and passes its tick scaled by its own cadence
84
+ * (tick × tickMs). Callers gate on the animations preference and fall back to
85
+ * a static palette color when animation is off.
86
+ * @param elapsedMs - milliseconds since the flow started (any sign or size).
87
+ * @param anchors - the colors to walk, in order (theme.ts FLOW_ANCHORS).
88
+ * @returns the interpolated anchor color at this instant.
89
+ */
90
+ export function flowColor(elapsedMs, anchors) {
91
+ if (anchors.length === 0)
92
+ throw new Error('flowColor needs at least one anchor');
93
+ if (anchors.length === 1)
94
+ return anchors[0];
95
+ const lap = ((elapsedMs % FLOW_PERIOD_MS) + FLOW_PERIOD_MS) % FLOW_PERIOD_MS;
96
+ const span = FLOW_PERIOD_MS / anchors.length;
97
+ const at = lap / span;
98
+ const index = Math.floor(at);
99
+ const phase = at - index;
100
+ const eased = phase * phase * (3 - 2 * phase);
101
+ return blendRgb(anchors[(index + 1) % anchors.length], anchors[index % anchors.length], eased);
102
+ }
103
+ /** Caret blink cadence: one blink step (on or off) per tick. */
104
+ export const CARET_BLINK_TICK_MS = 530;
105
+ /** Caret visibility: half the ticks on, half off (530ms blink). */
106
+ export function caretVisible(tick) {
107
+ return tick % 2 === 0;
108
+ }
109
+ /**
110
+ * The one-shot DeepSeek model-switch easter egg: when the status bar model
111
+ * label switches to an official DeepSeek route, the composer's input row
112
+ * plays Codex's effort-ignition "Wave" — a blue crest sweeping the content
113
+ * row column by column (background tint ≤ 0.55 under the draft), plus the
114
+ * Ultra-style `· ✦ ✧` sparkles on the deepseek tier — and the prompt marker
115
+ * keeps the tier accent afterwards. The Ink layer owns the timer and reads
116
+ * the ACTIVE palette anchors (`getPalette`); everything below is pure
117
+ * interpolation over the colors it is given.
118
+ */
119
+ /** Frame cadence of the DeepSeek wave: Codex's IGNITION_FRAME_TICK (33ms ≈ 30fps). */
120
+ export const DEEPSEEK_WAVE_TICK_MS = 33;
121
+ /** All styles in canonical order, for random selection. */
122
+ const DEEPSEEK_WAVE_STYLES = ['wave', 'aurora', 'pulse'];
123
+ /**
124
+ * The water surface: ONE continuous sine line spanning the whole band,
125
+ * mirror-symmetric about the center column, its crests flowing OUTWARD from
126
+ * the center (phase k·|x − center| − ω·t). No sweep window, no return trip —
127
+ * the surface fades in, flows, and fades out, symmetric in both space and
128
+ * time. The deepseek tier adds one faster, finer HARMONIC line whose crests
129
+ * cross the fundamental's: interleaved richness with both lines still
130
+ * symmetric and still only ever flowing outward.
131
+ */
132
+ export const WAVE_SURFACE_AMPLITUDE = 0.8;
133
+ export const WAVE_SURFACE_HARMONIC = 0.45;
134
+ export const WAVE_SURFACE_WAVELENGTH = 40;
135
+ export const WAVE_SURFACE_OMEGA = 9;
136
+ /** Vertical thickness in lane units — Aurora-wide: soft gradients, no hard edges. */
137
+ export const WAVE_SURFACE_THICKNESS = 1.2;
138
+ /**
139
+ * The mirrored second-hue profile: the space BELOW the surface carries a
140
+ * second blue at this strength, so color (not just brightness) varies
141
+ * continuously across the wave — Aurora-style hue mixing instead of a
142
+ * single flat tint.
143
+ */
144
+ export const WAVE_SURFACE_MIRROR = 0.6;
145
+ /** Aurora-style soft alpha: low gain, capped well under the pulse ring's. */
146
+ export const WAVE_SURFACE_ALPHA_GAIN = 0.45;
147
+ export const WAVE_SURFACE_ALPHA_CAP = 0.68;
148
+ /**
149
+ * Pulse ring geometry, softened to the Wave standard: a WIDE band
150
+ * (half-width 5.5) with a moderate peak riding the radius — all inside the
151
+ * hue blend, no hard white line — and an inner profile one hue over at a
152
+ * slightly smaller radius, so the ring's color grades continuously across
153
+ * its width (the radial analog of the water surface's mirrored hues).
154
+ */
155
+ const PULSE_HALF_WIDTH = 5.5;
156
+ const PULSE_PEAK_HALF_WIDTH = 1.8;
157
+ const PULSE_PEAK_GAIN = 0.35;
158
+ const PULSE_INNER_OFFSET = 2.5;
159
+ const PULSE_INNER_STRENGTH = 0.6;
160
+ /** The inner edge of each ring carries the tier's third blue. */
161
+ const PULSE_INNER_HUE = 2;
162
+ /**
163
+ * The trailing echo ripple: every pulse ring drags a second, weaker ring at
164
+ * a fraction of its radius in the NEXT hue of the tier's blues, fading in a
165
+ * little after the primary so the center hole opens first.
166
+ */
167
+ const PULSE_ECHO_RADIUS = 0.7;
168
+ const PULSE_ECHO_STRENGTH = 0.65;
169
+ const PULSE_ECHO_DELAY = 0.12;
170
+ /** Aurora-grade soft alpha for the detonation — a notch above the swell. */
171
+ export const PULSE_ALPHA_GAIN = 0.45;
172
+ export const PULSE_ALPHA_CAP = 0.72;
173
+ /**
174
+ * Terminal cell aspect (row height ÷ column width, ≈2.2 for common fonts).
175
+ * A ring computed in raw cell units looks vertically squashed; weighting row
176
+ * distance by the aspect makes the Pulse ring appear circular on screen.
177
+ */
178
+ const PULSE_ROW_ASPECT = 2.2;
179
+ /** Sparkle start and frame cadence — Codex SPARK_START / SPARK_FRAME. */
180
+ const SPARK_START_MS = 900;
181
+ const SPARK_FRAME_MS = 100;
182
+ /** Sparkle glyphs in frame order — Codex SPARK_GLYPHS (`· ✦ ✧`). */
183
+ export const SPARK_GLYPHS = ['·', '✦', '✧'];
184
+ export const DEEPSEEK_WAVE_BANDS = {
185
+ wave: {
186
+ // Wave-Max: one band sweeping 0.10s..0.85s.
187
+ flash: [[0.10, 0.75, 1.0]],
188
+ // Wave-Ultra: two offset bands for a richer crest.
189
+ deepseek: [[0.10, 0.70, 1.0], [0.35, 0.55, 1.0]],
190
+ // Into the Unknown reuses the Ultra parameters verbatim.
191
+ unknown: [[0.10, 0.70, 1.0], [0.35, 0.55, 1.0]],
192
+ },
193
+ aurora: {
194
+ // Aurora-Max: two drifting bands (hues 0 and 1).
195
+ flash: [[0.35, 0.15, 0.0], [-0.50, 0.60, 1.0]],
196
+ // Aurora-Ultra: a third band adds hue 2.
197
+ deepseek: [[0.35, 0.15, 0.0], [-0.50, 0.60, 1.0], [0.75, 0.35, 2.0]],
198
+ unknown: [[0.35, 0.15, 0.0], [-0.50, 0.60, 1.0], [0.75, 0.35, 2.0]],
199
+ },
200
+ pulse: {
201
+ // Pulse-Max: one expanding ring.
202
+ flash: [[0.10, 0.60, 1.0]],
203
+ // Pulse-Ultra: two rings (inner weaker, outer stronger).
204
+ deepseek: [[0.10, 0.55, 0.8], [0.45, 0.55, 1.1]],
205
+ unknown: [[0.10, 0.55, 0.8], [0.45, 0.55, 1.1]],
206
+ },
207
+ };
208
+ /** Extra display time applied to every Codex ignition style. */
209
+ const DEEPSEEK_WAVE_DURATION_EXTENSION_MS = 200;
210
+ /** Original Codex duration used as the animation's sampling timeline. */
211
+ function deepseekWaveBaseDuration(tier, style) {
212
+ // The unknown tier reuses the deepseek (pro) durations exactly.
213
+ const pro = tier === 'deepseek' || tier === 'unknown';
214
+ switch (style) {
215
+ case 'aurora': return pro ? 1600 : 1300;
216
+ case 'pulse': return pro ? 1250 : 900;
217
+ case 'wave': return pro ? 1300 : 1000;
218
+ }
219
+ }
220
+ /**
221
+ * Total visible duration: the Codex ignition duration plus 200ms so its motion
222
+ * remains readable in a busy terminal.
223
+ * @param tier - the active wave tier.
224
+ * @param style - the active ignition style.
225
+ * @returns the duration in milliseconds.
226
+ */
227
+ export function deepseekWaveDuration(tier, style = 'wave') {
228
+ return deepseekWaveBaseDuration(tier, style) + DEEPSEEK_WAVE_DURATION_EXTENSION_MS;
229
+ }
230
+ /** Map the extended display timeline back onto the original Codex samples. */
231
+ function deepseekWaveSampleElapsedMs(tick, tier, style) {
232
+ const base = deepseekWaveBaseDuration(tier, style);
233
+ return tick * DEEPSEEK_WAVE_TICK_MS * base / deepseekWaveDuration(tier, style);
234
+ }
235
+ /**
236
+ * Pick one ignition style at random, never repeating the previous one —
237
+ * Codex `IgnitionStyle::random`. Falls back to the remaining styles.
238
+ * @param previous - the style of the last trigger, if any.
239
+ * @returns a style different from `previous`.
240
+ */
241
+ export function deepseekWaveStyleRandom(previous) {
242
+ const candidates = DEEPSEEK_WAVE_STYLES.filter(style => style !== previous);
243
+ return candidates[Math.floor(Math.random() * candidates.length)] ?? 'wave';
244
+ }
245
+ /**
246
+ * Tier for a `provider/model` label: a MODEL ID containing `flash` runs the
247
+ * single-band flash tier; everything else (pro/reasoner/chat) runs the
248
+ * dual-band deepseek tier. Mirrors Codex's Max→Ultra mapping. Only the model
249
+ * segment (after the `/`) is matched, so a provider whose name contains
250
+ * `flash` cannot flip an unrelated model onto the flash tier.
251
+ * @param model - the `provider/model` label of the applied model.
252
+ * @returns the wave tier for that model.
253
+ */
254
+ export function deepseekWaveTier(model) {
255
+ const slash = model.indexOf('/');
256
+ const id = slash < 0 ? model : model.slice(slash + 1);
257
+ return id.toLowerCase().includes('flash') ? 'flash' : 'deepseek';
258
+ }
259
+ /**
260
+ * Cosine window — Codex `crest`: 1 exactly under the wave center, 0 from
261
+ * one half-width away.
262
+ * @param distance - distance from the crest center in half-widths.
263
+ * @returns the crest strength in 0..1.
264
+ */
265
+ export function crest(distance) {
266
+ if (distance >= 1)
267
+ return 0;
268
+ return 0.5 * (1 + Math.cos(Math.PI * distance));
269
+ }
270
+ /**
271
+ * Cubic ease-in-out — Codex `ease_in_out`: flat at both ends, steepest in
272
+ * the middle, so the crest accelerates and eases instead of sliding linearly.
273
+ * @param progress - raw progress (clamped to 0..1).
274
+ * @returns the eased progress in 0..1.
275
+ */
276
+ export function easeInOut(progress) {
277
+ const p = Math.min(1, Math.max(0, progress));
278
+ if (p < 0.5)
279
+ return 4 * p * p * p;
280
+ const inverse = -2 * p + 2;
281
+ return 1 - (inverse * inverse * inverse) / 2;
282
+ }
283
+ /**
284
+ * Fade-in/fade-out envelope — Codex `envelope`: linear ramp over `fadeIn`
285
+ * at the start and `fadeOut` at the end, plateau at 1 between, 0 outside the
286
+ * total. The Wave style keeps the envelope at 1 (Codex paints Wave without
287
+ * an envelope); exported for the Aurora-style fades and for tests.
288
+ * @param elapsed - seconds since the animation started.
289
+ * @param total - total duration in seconds.
290
+ * @param fadeIn - seconds of fade-in.
291
+ * @param fadeOut - seconds of fade-out.
292
+ * @returns the envelope value in 0..1.
293
+ */
294
+ export function envelope(elapsed, total, fadeIn, fadeOut) {
295
+ if (elapsed <= 0 || elapsed >= total)
296
+ return 0;
297
+ const rise = elapsed / Math.max(fadeIn, Number.EPSILON);
298
+ const fall = (total - elapsed) / Math.max(fadeOut, Number.EPSILON);
299
+ return Math.min(Math.max(Math.min(rise, fall), 0), 1);
300
+ }
301
+ /**
302
+ * The /rainbow celebration on the three-row composer band: a FIXED
303
+ * seven-color spectrum (not the rolled palette) that slides across every
304
+ * row as one ribbon. Triggered when switching to rainbow or rerolling the
305
+ * seed; independent of RAINBOW_SEED so the burst always reads as a prism.
306
+ */
307
+ export const RAINBOW_BURST_TICK_MS = 33;
308
+ export const RAINBOW_BURST_DURATION_MS = 1_800;
309
+ export const RAINBOW_BURST_HUES = [
310
+ [255, 59, 48],
311
+ [255, 149, 0],
312
+ [255, 204, 0],
313
+ [52, 199, 89],
314
+ [0, 199, 190],
315
+ [10, 132, 255],
316
+ [175, 82, 222],
317
+ ];
318
+ /**
319
+ * Smoothstep in 0..1, then wrap-lerp around the seven burst hues.
320
+ * Position 0 is red, wrapping back toward red at 1. Shared by the
321
+ * composer burst and the static rainbow whale header.
322
+ */
323
+ export function rainbowSpectrumHue(position) {
324
+ const hues = RAINBOW_BURST_HUES;
325
+ const x = ((position % 1) + 1) % 1;
326
+ const scaled = x * hues.length;
327
+ const index = Math.floor(scaled);
328
+ const t = scaled - index;
329
+ const eased = t * t * (3 - 2 * t);
330
+ const a = hues[index % hues.length];
331
+ const b = hues[(index + 1) % hues.length];
332
+ return [
333
+ Math.round(a[0] + (b[0] - a[0]) * eased),
334
+ Math.round(a[1] + (b[1] - a[1]) * eased),
335
+ Math.round(a[2] + (b[2] - a[2]) * eased),
336
+ ];
337
+ }
338
+ /**
339
+ * Background tint for one composer-band cell during the rainbow burst.
340
+ * Every row of a column shares the same hue (a solid ribbon); edge rows
341
+ * are slightly dimmer so the middle editor row reads as the crest. The
342
+ * spectrum slides ~1.2 widths over the burst, then fades to the band base.
343
+ * @param tick - frame index at {@link RAINBOW_BURST_TICK_MS}.
344
+ * @param column - band column (0..width-1).
345
+ * @param width - band width in columns.
346
+ * @param base - the theme's composerBand color to blend toward.
347
+ * @param row - band row (0..rows-1).
348
+ * @param rows - band height (composer is three rows: pad, editor, pad).
349
+ * @returns the blended RGB, or null after the burst (or at zero alpha).
350
+ */
351
+ export function rainbowBurstColumnBg(tick, column, width, base, row = 0, rows = 1) {
352
+ const elapsed = tick * RAINBOW_BURST_TICK_MS / 1000;
353
+ const total = RAINBOW_BURST_DURATION_MS / 1000;
354
+ const fade = envelope(elapsed, total, 0.18, 0.45);
355
+ if (fade <= 0)
356
+ return null;
357
+ const span = Math.max(1, width - 1);
358
+ const slide = elapsed / total * 1.2;
359
+ const mixed = rainbowSpectrumHue(column / span + slide);
360
+ const edge = rows > 1 && (row === 0 || row === rows - 1);
361
+ const alpha = Math.min(0.72 * fade * (edge ? 0.78 : 1), 0.70);
362
+ if (alpha < 0.02)
363
+ return null;
364
+ return blendRgb(mixed, base, alpha);
365
+ }
366
+ /**
367
+ * One band's contributions at a column — Codex `band_sample`, redesigned:
368
+ * Wave is a WATER SURFACE — one continuous sine line, mirror-symmetric
369
+ * about the center column, crests flowing outward from the center (band 1
370
+ * of the deepseek tier is a faster harmonic line crossing it). Pulse
371
+ * detonates in TWO dimensions: soft rings that keep expanding through a
372
+ * symmetric fade envelope, color grading across each ring's width, with a
373
+ * trailing echo ripple. Aurora matches Codex verbatim.
374
+ * @param style - the ignition style.
375
+ * @param band - the band triple (meaning depends on the style).
376
+ * @param elapsed - seconds since the animation started.
377
+ * @param column - column index in the content row (0..width-1).
378
+ * @param width - content-row width in columns.
379
+ * @param context - band geometry (row position, undulation flag, pulse span).
380
+ * @returns one or two `[hueIndex, strength, core]` contributions.
381
+ */
382
+ function bandSample(style, band, elapsed, column, width, context) {
383
+ const [first, second, third] = band;
384
+ switch (style) {
385
+ case 'wave': {
386
+ // The water surface: one continuous sine line across the whole band,
387
+ // mirror-symmetric about the center column (distance d = |x − center|
388
+ // drives the phase), crests flowing OUTWARD from the center — one
389
+ // direction, never a return trip. The band table's (launch, travel)
390
+ // becomes the line's fade envelope: fade in at `launch`, full until
391
+ // `launch + travel`, then fade out to the end — symmetric in time.
392
+ const fadeIn = first;
393
+ const fadeOut = Math.max(0.05, context.total - (first + second));
394
+ const fade = envelope(elapsed, context.total, fadeIn, fadeOut);
395
+ if (fade <= 0.01)
396
+ return [[0, 0, 0]];
397
+ // Band 0 is the fundamental swell; band 1 (deepseek tier) is a faster,
398
+ // finer harmonic crossing the fundamental's crests — two continuous
399
+ // symmetric lines interleaving instead of one shuttling back and forth.
400
+ const harmonic = context.bandIndex % 2 === 1;
401
+ const wavelength = harmonic ? WAVE_SURFACE_WAVELENGTH / 1.5 : WAVE_SURFACE_WAVELENGTH;
402
+ const omega = (harmonic ? 1.5 : 1) * WAVE_SURFACE_OMEGA;
403
+ const amplitude = (harmonic ? WAVE_SURFACE_HARMONIC : 1) * WAVE_SURFACE_AMPLITUDE;
404
+ const d = Math.abs(column - (width - 1) / 2);
405
+ const surface = amplitude * Math.sin(Math.PI * 2 * d / wavelength - omega * elapsed + (harmonic ? Math.PI / 2 : 0));
406
+ const fromSurface = Math.abs(context.u - surface);
407
+ const vertical = context.undulating ? crest(fromSurface / WAVE_SURFACE_THICKNESS) : 1;
408
+ // Aurora-style hue mixing: the MIRRORED profile below the surface
409
+ // carries a second blue (the harmonic band carries the third), so the
410
+ // color distribution flows continuously with the wave instead of one
411
+ // flat tint broken by a hard core line.
412
+ const mirrorHue = harmonic ? 1 : 2;
413
+ const below = context.undulating ? crest(Math.abs(context.u + surface) / WAVE_SURFACE_THICKNESS) : vertical;
414
+ return [
415
+ [0, fade * vertical, 0],
416
+ [mirrorHue, fade * below * WAVE_SURFACE_MIRROR, 0],
417
+ ];
418
+ }
419
+ case 'aurora': {
420
+ const center = (0.5 + 0.38 * Math.sin(Math.PI * 2 * (first * elapsed + second))) * width;
421
+ const halfWidth = Math.max(width * 0.22, 4);
422
+ return [[Math.trunc(third), crest(Math.abs(column - center) / halfWidth), 0]];
423
+ }
424
+ case 'pulse': {
425
+ // Symmetric lifetime: the band table's (launch, travel) becomes the
426
+ // fade envelope (fade in at launch, full until launch+travel, then
427
+ // fade to the end) while the radius keeps expanding THROUGH the fade —
428
+ // a shockwave that never hits a wall, it dissolves mid-flight.
429
+ const launch = first;
430
+ const travel = second;
431
+ const fadeOut = Math.max(0.05, context.total - (launch + travel));
432
+ const fade = envelope(elapsed, context.total, launch, fadeOut);
433
+ if (fade <= 0.01)
434
+ return [[0, 0, 0]];
435
+ const progress = (elapsed - launch) / travel;
436
+ const radius = (1 - (1 - progress) ** 3) * context.pulseSpan;
437
+ const decay = third * (1 - 0.35 * Math.min(Math.max(progress, 0), 1));
438
+ const distance = Math.hypot(column - width / 2, context.dy);
439
+ const fromRing = Math.abs(distance - radius);
440
+ // Wide soft band with a moderate peak riding the radius — all in the
441
+ // hue blend, no hard line — and an inner edge one hue over at a
442
+ // slightly smaller radius: the ring's color grades across its width.
443
+ const band = crest(fromRing / PULSE_HALF_WIDTH) + PULSE_PEAK_GAIN * crest(fromRing / PULSE_PEAK_HALF_WIDTH);
444
+ const inner = crest(Math.abs(distance - (radius - PULSE_INNER_OFFSET)) / PULSE_HALF_WIDTH);
445
+ // Trailing echo ripple in the next blue, fading in after the primary
446
+ // so the center hole opens first.
447
+ const echoGate = envelope(elapsed, context.total, launch + PULSE_ECHO_DELAY, fadeOut);
448
+ const echo = crest(Math.abs(distance - radius * PULSE_ECHO_RADIUS) / PULSE_HALF_WIDTH)
449
+ * PULSE_ECHO_STRENGTH * echoGate;
450
+ return [
451
+ [context.bandIndex, fade * band * decay, 0],
452
+ [PULSE_INNER_HUE, fade * inner * decay * PULSE_INNER_STRENGTH, 0],
453
+ [context.bandIndex + 1, fade * echo * decay, 0],
454
+ ];
455
+ }
456
+ }
457
+ }
458
+ /** Linear RGB blend — Codex `blend`: `fg * alpha + bg * (1 - alpha)`. */
459
+ function blendRgb(fg, bg, alpha) {
460
+ return [
461
+ Math.round(fg[0] * alpha + bg[0] * (1 - alpha)),
462
+ Math.round(fg[1] * alpha + bg[1] * (1 - alpha)),
463
+ Math.round(fg[2] * alpha + bg[2] * (1 - alpha)),
464
+ ];
465
+ }
466
+ /**
467
+ * Aurora-only per-row phase share of the duration: its drifting bands reach
468
+ * the top row first and the bottom row last, sweeping down the band. 0.12
469
+ * keeps the bottom row's lag inside the 200ms duration extension. Wave and
470
+ * Pulse deliberately share one timeline (see `deepseekWaveColumnBg`).
471
+ */
472
+ const DEEPSEEK_WAVE_ROW_PHASE = 0.12;
473
+ /**
474
+ * The background color for one composer-band column at a tick — Codex
475
+ * `paint_bands` + `Canvas::tint` for all three styles. Bands overlap with a
476
+ * max for Wave/Pulse and a SUM for Aurora (Codex differs by style), the
477
+ * weighted hues mix per column (Wave/Pulse always end on hue 0), the tint
478
+ * blends the mixed hue toward the blank-cell base at the style's alpha cap,
479
+ * and Aurora applies its own fade envelope. Returns `null` when the column
480
+ * should stay transparent, so the row returns to no `backgroundColor` on
481
+ * both ends. With `rows > 1`: Wave is a water surface — every column
482
+ * lights the row nearest the surface's current height, so the light reads
483
+ * as ONE continuous wavy line spanning the band, symmetric about the center
484
+ * column and flowing outward (a single-row band falls back to a flat glow);
485
+ * Pulse rings in two dimensions around the band's center cell with trailing
486
+ * echo ripples; only Aurora samples the timeline shifted by a per-row phase
487
+ * offset.
488
+ * @param tick - wave frame (0, 1, … at DEEPSEEK_WAVE_TICK_MS).
489
+ * @param column - column index in the content row (0..width-1).
490
+ * @param width - content-row width in columns.
491
+ * @param tier - the wave tier (flash = Max, deepseek = Ultra parameters).
492
+ * @param style - the ignition style.
493
+ * @param hues - the tier's three hues.
494
+ * @param base - the blank-cell base color the tint blends toward.
495
+ * @param row - row index in the band (0..rows-1; default 0 = old single-row).
496
+ * @param rows - band height in rows (default 1).
497
+ * @returns the blended RGB background, or null for transparent.
498
+ */
499
+ export function deepseekWaveColumnBg(tick, column, width, tier, style, hues, base, row = 0, rows = 1) {
500
+ const total = deepseekWaveBaseDuration(tier, style) / 1000;
501
+ // Only Aurora keeps the per-row phase cascade (its drifting bands stagger
502
+ // nicely). Wave and Pulse sample ONE shared timeline — Codex paints each
503
+ // column across the whole band height (`tint_column`), and the cascade we
504
+ // added on top of the port tore Pulse into misaligned per-row rings and
505
+ // smeared Wave's crest into a diagonal.
506
+ const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1000
507
+ - (style === 'aurora' ? (row - (rows - 1) / 2) * total * DEEPSEEK_WAVE_ROW_PHASE : 0);
508
+ const dy = style === 'pulse' ? (row - (rows - 1) / 2) * PULSE_ROW_ASPECT : 0;
509
+ const undulating = rows > 1;
510
+ const u = undulating ? (row - (rows - 1) / 2) / ((rows - 1) / 2) : 0;
511
+ // The span is EXACTLY the band's far corner: the ring front reaches the
512
+ // edges as the fade envelope closes, so the detonation stays visible for
513
+ // its whole lifetime instead of rushing off the band mid-flight (the old
514
+ // +2·half-width margin made the ring's second half invisible).
515
+ const pulseSpan = Math.hypot(width / 2, ((rows - 1) / 2) * PULSE_ROW_ASPECT);
516
+ const fade = style === 'aurora' ? envelope(elapsed, total, 0.25, 0.40) : 1;
517
+ const weights = [0, 0, 0];
518
+ let bandIndex = 0;
519
+ for (const band of DEEPSEEK_WAVE_BANDS[style][tier]) {
520
+ for (const [hue, strength] of bandSample(style, band, elapsed, column, width, { dy, u, undulating, pulseSpan, bandIndex, total })) {
521
+ weights[hue] = style === 'aurora' ? weights[hue] + strength : Math.max(weights[hue], strength);
522
+ }
523
+ bandIndex += 1;
524
+ }
525
+ const weight = weights[0] + weights[1] + weights[2];
526
+ if (weight <= 0.01)
527
+ return null;
528
+ let red = 0;
529
+ let green = 0;
530
+ let blue = 0;
531
+ for (let index = 0; index < 3; index += 1) {
532
+ red += weights[index] * hues[index][0];
533
+ green += weights[index] * hues[index][1];
534
+ blue += weights[index] * hues[index][2];
535
+ }
536
+ const mixed = [
537
+ Math.round(red / weight),
538
+ Math.round(green / weight),
539
+ Math.round(blue / weight),
540
+ ];
541
+ // Alpha caps, all Aurora-grade now: Aurora ≤0.50, the swell ≤0.68, the
542
+ // detonation a notch above at ≤0.72 — soft gradients everywhere, no hard
543
+ // lines, the color work done by hue mixing instead of brightness spikes.
544
+ const alpha = style === 'aurora'
545
+ ? Math.min(weight * 0.40, 0.50) * fade
546
+ : style === 'wave'
547
+ ? Math.min(weight * WAVE_SURFACE_ALPHA_GAIN, WAVE_SURFACE_ALPHA_CAP)
548
+ : Math.min(weight * PULSE_ALPHA_GAIN, PULSE_ALPHA_CAP);
549
+ if (alpha < 0.02)
550
+ return null;
551
+ return blendRgb(mixed, base, alpha);
552
+ }
553
+ /**
554
+ * The sparkle glyph for a tick — Codex `spark_frame`, sampled on the same
555
+ * proportionally slowed DeepSeek Wave timeline as the composer background.
556
+ * The Ink layer still must skip occupied cells.
557
+ * @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
558
+ * @returns the sparkle glyph, or null outside the stretched tail window.
559
+ */
560
+ export function deepseekWaveSpark(tick) {
561
+ const elapsed = deepseekWaveSampleElapsedMs(tick, 'deepseek', 'wave');
562
+ if (elapsed < SPARK_START_MS)
563
+ return null;
564
+ const frame = Math.floor((elapsed - SPARK_START_MS) / SPARK_FRAME_MS);
565
+ return SPARK_GLYPHS[frame] ?? null;
566
+ }
567
+ /**
568
+ * Whether the `deepseek` wordmark rides the wave at this tick: it fades in
569
+ * shortly after the first crest launches and out before the wave settles,
570
+ * so the brand name surfaces through the sweep's middle. The Ink layer
571
+ * places it in the row's blank mid-section (never over real draft text).
572
+ * @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
573
+ * @param tier - the wave tier.
574
+ * @returns true while the wordmark should be visible.
575
+ */
576
+ export function deepseekWaveWordVisible(tick, tier, style = 'wave') {
577
+ const total = deepseekWaveBaseDuration(tier, style) / 1000;
578
+ const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1000;
579
+ return envelope(elapsed, total, total * 0.2, total * 0.35) > 0.25;
580
+ }
581
+ /**
582
+ * The per-character color for the `deepseek` wordmark: the tier's hues
583
+ * cycled per character (d→hue0, e→hue1, e→hue2, …), a brand-gradient text.
584
+ * @param index - character index in the wordmark.
585
+ * @param hues - the tier's three hues.
586
+ * @returns the hue for that character.
587
+ */
588
+ export function deepseekWaveWordHue(index, hues) {
589
+ return hues[index % hues.length];
590
+ }
591
+ /**
592
+ * True when a `provider/model` status label addresses the official DeepSeek
593
+ * route: either segment contains `deepseek` (case-insensitive), covering the
594
+ * `deepseek-official` provider route and its `deepseek-*` model ids.
595
+ * @param label - the status bar model label (`provider/model`).
596
+ * @returns whether the label names an official DeepSeek model.
597
+ */
598
+ export function isOfficialDeepSeekLabel(label) {
599
+ const slash = label.indexOf('/');
600
+ const provider = slash < 0 ? label : label.slice(0, slash);
601
+ const model = slash < 0 ? '' : label.slice(slash + 1);
602
+ return provider.toLowerCase().includes('deepseek') || model.toLowerCase().includes('deepseek');
603
+ }
604
+ /**
605
+ * Known reasoning-effort ranks in ascending order. Effort ids are opaque
606
+ * adapter-owned strings, so the rank table covers the conventional names
607
+ * (off → low → medium → high → xhigh → max/ultra); an unrecognized id
608
+ * ranks as unknown (0), which never triggers the high-effort wave.
609
+ */
610
+ const EFFORT_RANK = {
611
+ off: 0,
612
+ none: 0,
613
+ low: 1,
614
+ medium: 2,
615
+ med: 2,
616
+ high: 3,
617
+ xhigh: 4,
618
+ 'x-high': 4,
619
+ 'very-high': 4,
620
+ max: 5,
621
+ maximum: 5,
622
+ ultra: 5,
623
+ };
624
+ /**
625
+ * True when an effective reasoning effort is STRICTLY above `high` — the
626
+ * trigger gate for the "Into the Unknown" wave on non-DeepSeek routes.
627
+ * Absent efforts and unrecognized ids never qualify.
628
+ * @param effort - the effective reasoning-effort id ('' or undefined when none).
629
+ * @returns whether the effort ranks above high.
630
+ */
631
+ export function effortAboveHigh(effort) {
632
+ if (effort === undefined || effort === '')
633
+ return false;
634
+ const rank = EFFORT_RANK[effort.trim().toLowerCase()];
635
+ return rank !== undefined && rank > 3;
636
+ }
637
+ /**
638
+ * Parse a persisted animations preference (`animations.json`): timed
639
+ * animations are on by default and only an explicit `false` disables them —
640
+ * a missing key, corrupt value, or absent file all mean enabled, so the
641
+ * /animation toggle degrades exactly like every other user preference.
642
+ * @param value - the raw parsed JSON value (expected boolean).
643
+ * @returns whether timed animations should run.
644
+ */
645
+ export function parseAnimationsPref(value) {
646
+ return value !== false;
647
+ }
648
+ /**
649
+ * One parsed `/animation` argument: '' toggles, `on|true|1` enables,
650
+ * `off|false|0` disables (case-insensitive, surrounding whitespace ignored),
651
+ * and anything else is a usage error the caller surfaces. Kept pure so the
652
+ * command's entire decision table is unit-testable.
653
+ * @param argument - the raw text after `/animation`.
654
+ * @returns `{ enabled }`, `'toggle'`, or `'usage'`.
655
+ */
656
+ export function parseAnimationsArgument(argument) {
657
+ const normalized = argument.trim().toLowerCase();
658
+ if (normalized === '')
659
+ return 'toggle';
660
+ if (normalized === 'on' || normalized === 'true' || normalized === '1')
661
+ return { enabled: true };
662
+ if (normalized === 'off' || normalized === 'false' || normalized === '0')
663
+ return { enabled: false };
664
+ return 'usage';
665
+ }