@deepseek-harness-tui/dsh-tui 0.8.3 → 0.8.5

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 (198) hide show
  1. package/README.md +29 -10
  2. package/bin/dsh-tui.js +50 -11
  3. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
  4. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
  5. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
  6. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
  7. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
  8. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
  9. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
  10. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
  11. package/dsh-ecosystem-spec/registry/README.md +14 -14
  12. package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
  13. package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
  14. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
  15. package/lib/types/activityPrefs.d.ts +10 -0
  16. package/lib/types/activityPrefs.d.ts.map +1 -1
  17. package/lib/types/activityPrefs.js +17 -0
  18. package/lib/types/commands.d.ts.map +1 -1
  19. package/lib/types/commands.js +1 -0
  20. package/lib/types/components/LogoV2.d.ts +4 -1
  21. package/lib/types/components/LogoV2.d.ts.map +1 -1
  22. package/lib/types/components/LogoV2.js +8 -3
  23. package/lib/types/components/MessageList.d.ts +4 -1
  24. package/lib/types/components/MessageList.d.ts.map +1 -1
  25. package/lib/types/components/MessageList.js +4 -4
  26. package/lib/types/components/PromptInput.d.ts.map +1 -1
  27. package/lib/types/components/PromptInput.js +1 -3
  28. package/lib/types/components/SplitDiffView.d.ts +3 -1
  29. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  30. package/lib/types/components/SplitDiffView.js +12 -5
  31. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  32. package/lib/types/components/StreamingMarkdown.js +10 -2
  33. package/lib/types/components/TipsPanel.d.ts +14 -0
  34. package/lib/types/components/TipsPanel.d.ts.map +1 -0
  35. package/lib/types/components/TipsPanel.js +37 -0
  36. package/lib/types/components/activityFrames.d.ts +7 -28
  37. package/lib/types/components/activityFrames.d.ts.map +1 -1
  38. package/lib/types/components/activityFrames.js +7 -84
  39. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  40. package/lib/types/components/messages/AssistantThinkingMessage.js +1 -1
  41. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +4 -1
  42. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  43. package/lib/types/components/messages/AssistantToolUseMessage.js +14 -3
  44. package/lib/types/dsh-adapter/channel.d.ts +17 -0
  45. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  46. package/lib/types/dsh-adapter/channel.js +169 -33
  47. package/lib/types/dsh-adapter/index.d.ts +5 -0
  48. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  49. package/lib/types/dsh-adapter/index.js +19 -0
  50. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  51. package/lib/types/dsh-adapter/plugin.js +176 -4
  52. package/lib/types/dsh-adapter/settings-sections.d.ts +13 -0
  53. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/settings-sections.js +31 -6
  55. package/lib/types/effortPrefs.d.ts +7 -6
  56. package/lib/types/effortPrefs.d.ts.map +1 -1
  57. package/lib/types/effortPrefs.js +7 -6
  58. package/lib/types/i18n.d.ts +42 -14
  59. package/lib/types/i18n.d.ts.map +1 -1
  60. package/lib/types/i18n.js +11 -4
  61. package/lib/types/ink/ink.d.ts +10 -0
  62. package/lib/types/ink/ink.d.ts.map +1 -1
  63. package/lib/types/ink/ink.js +35 -1
  64. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  65. package/lib/types/ink/parse-keypress.js +15 -0
  66. package/lib/types/screens/Chat.d.ts.map +1 -1
  67. package/lib/types/screens/Chat.js +44 -7
  68. package/lib/types/screens/Settings.d.ts.map +1 -1
  69. package/lib/types/screens/Settings.js +109 -50
  70. package/lib/types/screens/StatusLine.d.ts +8 -0
  71. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  72. package/lib/types/screens/StatusLine.js +65 -42
  73. package/lib/types/settings-sections.d.ts +1 -1
  74. package/lib/types/settings-sections.d.ts.map +1 -1
  75. package/lib/types/tips.d.ts +30 -0
  76. package/lib/types/tips.d.ts.map +1 -0
  77. package/lib/types/tips.js +575 -0
  78. package/lib/types/tuiDisplayPrefs.d.ts +47 -0
  79. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -0
  80. package/lib/types/tuiDisplayPrefs.js +60 -0
  81. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
  82. package/node_modules/@dsh-std/command/LICENSE +21 -21
  83. package/node_modules/@dsh-std/command/README.md +26 -26
  84. package/node_modules/@dsh-std/command/README.zh.md +28 -28
  85. package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
  86. package/node_modules/@dsh-std/command/package.json +22 -22
  87. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  88. package/node_modules/@dsh-std/connection/LICENSE +21 -21
  89. package/node_modules/@dsh-std/connection/README.md +42 -42
  90. package/node_modules/@dsh-std/connection/README.zh.md +42 -42
  91. package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  92. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  93. package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  94. package/node_modules/@dsh-std/connection/package.json +20 -20
  95. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  96. package/node_modules/@dsh-std/core/LICENSE +21 -21
  97. package/node_modules/@dsh-std/core/README.md +13 -13
  98. package/node_modules/@dsh-std/core/README.zh.md +13 -13
  99. package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  100. package/node_modules/@dsh-std/core/package.json +21 -21
  101. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  102. package/node_modules/@dsh-std/manifest/LICENSE +21 -21
  103. package/node_modules/@dsh-std/manifest/README.md +5 -5
  104. package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  105. package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  106. package/node_modules/@dsh-std/manifest/package.json +20 -20
  107. package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  108. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
  109. package/node_modules/@dsh-std/messages/LICENSE +21 -21
  110. package/node_modules/@dsh-std/messages/README.md +5 -5
  111. package/node_modules/@dsh-std/messages/README.zh.md +7 -7
  112. package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
  113. package/node_modules/@dsh-std/messages/package.json +20 -20
  114. package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
  115. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  116. package/node_modules/@dsh-std/presentation/LICENSE +21 -21
  117. package/node_modules/@dsh-std/presentation/README.md +27 -27
  118. package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  119. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  120. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  121. package/node_modules/@dsh-std/presentation/package.json +25 -25
  122. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
  123. package/node_modules/@dsh-std/storage/LICENSE +21 -21
  124. package/node_modules/@dsh-std/storage/README.md +5 -5
  125. package/node_modules/@dsh-std/storage/README.zh.md +7 -7
  126. package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
  127. package/node_modules/@dsh-std/storage/package.json +20 -20
  128. package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
  129. package/package.json +8 -3
  130. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  131. package/presets/liangshen/agent.cordis.yml +7 -5
  132. package/presets/liangshen/custom-bash.mjs +184 -9
  133. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  134. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
  135. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
  136. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
  137. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  138. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  139. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  140. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
  141. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  142. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
  143. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
  144. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
  145. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  146. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
  147. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  148. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
  149. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
  150. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  151. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  152. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
  153. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  154. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  155. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
  156. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
  157. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  158. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  159. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  160. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
  161. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  162. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
  163. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
  164. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
  165. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  166. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
  167. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  168. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
  169. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
  170. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
  171. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  172. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
  173. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  174. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
  175. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
  176. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
  177. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  178. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
  179. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  180. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
  181. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
  182. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
  183. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  184. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  185. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  186. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
  187. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  188. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
  189. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
  190. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
  191. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  192. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
  193. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  194. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
  195. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
  196. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
  197. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  198. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
@@ -1 +1 @@
1
- {"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../../src/screens/Settings.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA2CxD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CA0VlB"}
1
+ {"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../../src/screens/Settings.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA+CxD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CA0ZlB"}
@@ -46,6 +46,7 @@ export function Settings({ channel, onClose, }) {
46
46
  const [sections, setSections] = React.useState(() => channel.settingsSections());
47
47
  const [mode, setMode] = React.useState('list');
48
48
  const [editing, setEditing] = React.useState(null);
49
+ const [activeGroup, setActiveGroup] = React.useState(null);
49
50
  const [focusIndex, setFocusIndex] = React.useState(0);
50
51
  const [notice, setNotice] = React.useState(undefined);
51
52
  /** Configured status of every secret field's credential ref. */
@@ -100,8 +101,26 @@ export function Settings({ channel, onClose, }) {
100
101
  };
101
102
  // eslint-disable-next-line react-hooks/exhaustive-deps
102
103
  }, [host, sections, secretProbe]);
103
- /** Focusable rows in display order: every field of every section. */
104
- const focusable = sections.flatMap(section => section.fields.map(field => ({ ns: section.ns, field })));
104
+ const activeSection = activeGroup === null ? undefined : sections.find(section => section.ns === activeGroup.ns);
105
+ const activeGroupSpec = activeSection?.groups?.find(group => group.id === activeGroup?.id);
106
+ React.useEffect(() => {
107
+ if (activeGroup !== null && activeGroupSpec === undefined) {
108
+ setActiveGroup(null);
109
+ setFocusIndex(0);
110
+ setWindowStart(0);
111
+ }
112
+ }, [activeGroup, activeGroupSpec]);
113
+ /** Focusable rows in display order for the current page. */
114
+ const focusable = activeSection !== undefined && activeGroupSpec !== undefined
115
+ ? activeSection.fields
116
+ .filter(field => field.group === activeGroupSpec.id)
117
+ .map(field => ({ kind: 'field', ns: activeSection.ns, field }))
118
+ : sections.flatMap(section => [
119
+ ...section.fields
120
+ .filter(field => field.group === undefined)
121
+ .map(field => ({ kind: 'field', ns: section.ns, field })),
122
+ ...(section.groups ?? []).map(group => ({ kind: 'group', ns: section.ns, group })),
123
+ ]);
105
124
  const effFocus = Math.min(focusIndex, Math.max(0, focusable.length - 1));
106
125
  const focused = focusable.length === 0 ? undefined : focusable[effFocus];
107
126
  const focusedForm = focused === undefined ? undefined : forms.get(focused.ns);
@@ -173,6 +192,12 @@ export function Settings({ channel, onClose, }) {
173
192
  setFocusIndex(Math.min(Math.max(0, focusable.length - 1), effFocus + 1));
174
193
  }
175
194
  else if (isPlainReturn(key) && focused !== undefined) {
195
+ if (focused.kind === 'group') {
196
+ setActiveGroup({ ns: focused.ns, id: focused.group.id });
197
+ setFocusIndex(0);
198
+ setWindowStart(0);
199
+ return;
200
+ }
176
201
  // A save in flight owns the section's drafts; starting an edit mid-write
177
202
  // is exactly the lost-draft race the staged model exists to prevent.
178
203
  if (focusedForm === undefined || !focusedForm.available || focusedForm.saving)
@@ -196,11 +221,17 @@ export function Settings({ channel, onClose, }) {
196
221
  bump();
197
222
  }
198
223
  else if (key.escape) {
199
- // Esc backs out one layer at a time: staged drafts first — ANY dirty
200
- // section, not just the focused one, so leaving never silently drops
201
- // an edit made two sections ago — the screen second. A save in flight
202
- // cannot be undone from here; let it settle instead of discarding
203
- // around it.
224
+ if (activeGroupSpec !== undefined) {
225
+ // Group navigation never settles drafts; the root page owns discard/exit.
226
+ setActiveGroup(null);
227
+ setFocusIndex(0);
228
+ setWindowStart(0);
229
+ return;
230
+ }
231
+ // At the root, Esc backs out one layer at a time: staged drafts first —
232
+ // ANY dirty section, not just the focused one — then the screen itself.
233
+ // A save in flight cannot be undone from here; let it settle instead of
234
+ // discarding around it.
204
235
  const dirty = [...forms.values()].filter(form => form.shell().dirty);
205
236
  if (dirty.length > 0) {
206
237
  if (dirty.some(form => form.saving))
@@ -219,7 +250,7 @@ export function Settings({ channel, onClose, }) {
219
250
  const ns = section.ns;
220
251
  const form = forms.get(ns);
221
252
  const state = form?.field(field) ?? { text: '', overridden: false, invalid: false };
222
- const isFocused = focused !== undefined && focused.ns === ns && focused.field === field;
253
+ const isFocused = focused?.kind === 'field' && focused.ns === ns && focused.field === field;
223
254
  const isEditing = isFocused && mode === 'edit' && editing !== null;
224
255
  const label = pick(field.label, field.descriptions);
225
256
  const hint = field.hint !== undefined ? pick(field.hint, field.hintDescriptions) : undefined;
@@ -248,58 +279,82 @@ export function Settings({ channel, onClose, }) {
248
279
  return (_jsxs(Box, { children: [_jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: isFocused ? '❯ ' : ' ' }), _jsx(Text, { bold: isFocused, children: label }), _jsx(Box, { flexGrow: 1 }), state.invalid && _jsxs(Text, { color: "error", children: [t('settings-field-invalid'), " "] }), state.overridden && _jsxs(Text, { dimColor: true, children: ["[", t('settings-badge-override'), "] "] }), form?.isStaged(field) === true && !isEditing && _jsx(Text, { color: "suggestion", children: "* " }), _jsx(Text, { color: isEditing || isFocused ? 'suggestion' : undefined, dimColor: !isFocused && !isEditing && state.text === '', children: value }), hint !== undefined && isFocused && (_jsxs(Text, { dimColor: true, children: ['\n ', hint] }))] }));
249
280
  };
250
281
  // ── Layout: a flat entry list with accounted line heights, windowed so the
251
- // focused field is always on screen no matter how long the list gets. ────
282
+ // focused row is always on screen no matter how long the current page gets. ─
252
283
  const entries = [];
253
284
  let focusCursor = 0;
254
- sections.forEach((section, sectionIndex) => {
255
- const form = forms.get(section.ns);
256
- const view = form?.namespace;
257
- const shell = form?.shell();
285
+ const addField = (section, field) => {
286
+ const isFocused = focused?.kind === 'field' && focused.ns === section.ns && focused.field === field;
287
+ const index = focusCursor;
288
+ focusCursor += 1;
258
289
  entries.push({
259
- key: `section:${section.ns}`,
260
- lines: sectionIndex === 0 ? 1 : 2,
261
- node: (_jsxs(Box, { flexDirection: "column", children: [sectionIndex > 0 && _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { bold: true, color: "permission", children: pick(section.title, section.descriptions) }), _jsxs(Text, { dimColor: true, children: [" (", section.ns, ")"] }), view?.applies === 'restart' && _jsxs(Text, { color: "warning", children: [" [", t('settings-badge-restart'), "]"] }), view === undefined && _jsxs(Text, { color: "warning", children: [" [", t('settings-section-unavailable'), "]"] }), shell?.dirty === true && _jsxs(Text, { color: "suggestion", children: [" [", t('settings-badge-dirty'), "]"] }), shell?.saving === true && _jsxs(Text, { dimColor: true, children: [" [", t('settings-badge-saving'), "]"] }), shell?.failed === true && _jsxs(Text, { color: "error", children: [" [", t('settings-badge-failed'), "]"] })] })] })),
290
+ key: `field:${section.ns}:${field.path.join('.')}`,
291
+ lines: field.hint !== undefined && isFocused ? 2 : 1,
292
+ focus: index,
293
+ node: renderField(section, field),
262
294
  });
263
- for (const field of section.fields) {
264
- const isFocused = focused !== undefined && focused.ns === section.ns && focused.field === field;
265
- const hasHint = field.hint !== undefined && isFocused;
266
- const index = focusCursor;
267
- focusCursor += 1;
295
+ };
296
+ if (activeSection !== undefined && activeGroupSpec !== undefined) {
297
+ const groupFields = activeSection.fields.filter(field => field.group === activeGroupSpec.id);
298
+ for (const field of groupFields)
299
+ addField(activeSection, field);
300
+ if (groupFields.length === 0) {
301
+ entries.push({ key: 'group:empty', lines: 1, node: _jsx(Text, { dimColor: true, children: t('settings-group-empty') }) });
302
+ }
303
+ }
304
+ else {
305
+ sections.forEach((section, sectionIndex) => {
306
+ const form = forms.get(section.ns);
307
+ const view = form?.namespace;
308
+ const shell = form?.shell();
268
309
  entries.push({
269
- key: `field:${section.ns}:${field.path.join('.')}`,
270
- lines: hasHint ? 2 : 1,
271
- focus: index,
272
- node: renderField(section, field),
310
+ key: `section:${section.ns}`,
311
+ lines: sectionIndex === 0 ? 1 : 2,
312
+ node: (_jsxs(Box, { flexDirection: "column", children: [sectionIndex > 0 && _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { bold: true, color: "permission", children: pick(section.title, section.descriptions) }), _jsxs(Text, { dimColor: true, children: [" (", section.ns, ")"] }), view?.applies === 'restart' && _jsxs(Text, { color: "warning", children: [" [", t('settings-badge-restart'), "]"] }), view === undefined && _jsxs(Text, { color: "warning", children: [" [", t('settings-section-unavailable'), "]"] }), shell?.dirty === true && _jsxs(Text, { color: "suggestion", children: [" [", t('settings-badge-dirty'), "]"] }), shell?.saving === true && _jsxs(Text, { dimColor: true, children: [" [", t('settings-badge-saving'), "]"] }), shell?.failed === true && _jsxs(Text, { color: "error", children: [" [", t('settings-badge-failed'), "]"] })] })] })),
273
313
  });
274
- }
275
- });
276
- const registeredNs = new Set(sections.map(section => section.ns));
277
- const readonlyNamespaces = namespaces.filter(entry => !registeredNs.has(entry.ns));
278
- if (readonlyNamespaces.length > 0) {
279
- entries.push({
280
- key: 'readonly:heading',
281
- lines: 2,
282
- node: (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, dimColor: true, children: t('settings-readonly-heading') })] })),
314
+ for (const field of section.fields) {
315
+ if (field.group === undefined)
316
+ addField(section, field);
317
+ }
318
+ for (const group of section.groups ?? []) {
319
+ const isFocused = focused?.kind === 'group' && focused.ns === section.ns && focused.group === group;
320
+ const index = focusCursor;
321
+ focusCursor += 1;
322
+ entries.push({
323
+ key: `group:${section.ns}:${group.id}`,
324
+ lines: 1,
325
+ focus: index,
326
+ node: (_jsxs(Box, { children: [_jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: isFocused ? '❯ ' : ' ' }), _jsx(Text, { bold: isFocused, children: pick(group.title, group.descriptions) }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { color: isFocused ? 'suggestion' : undefined, children: "\u203A" })] })),
327
+ });
328
+ }
283
329
  });
284
- for (const entry of readonlyNamespaces) {
330
+ const registeredNs = new Set(sections.map(section => section.ns));
331
+ const readonlyNamespaces = namespaces.filter(entry => !registeredNs.has(entry.ns));
332
+ if (readonlyNamespaces.length > 0) {
333
+ entries.push({
334
+ key: 'readonly:heading',
335
+ lines: 2,
336
+ node: (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, dimColor: true, children: t('settings-readonly-heading') })] })),
337
+ });
338
+ for (const entry of readonlyNamespaces) {
339
+ entries.push({
340
+ key: `readonly:${entry.ns}`,
341
+ lines: 1,
342
+ node: (_jsxs(Box, { children: [_jsxs(Text, { children: [' ', entry.ns] }), entry.applies === 'restart' && _jsxs(Text, { color: "warning", children: [" [", t('settings-badge-restart'), "]"] }), _jsxs(Text, { dimColor: true, children: [' ', valuePreview(entry.value, 60)] })] })),
343
+ });
344
+ }
285
345
  entries.push({
286
- key: `readonly:${entry.ns}`,
346
+ key: 'readonly:hint',
287
347
  lines: 1,
288
- node: (_jsxs(Box, { children: [_jsxs(Text, { children: [' ', entry.ns] }), entry.applies === 'restart' && _jsxs(Text, { color: "warning", children: [" [", t('settings-badge-restart'), "]"] }), _jsxs(Text, { dimColor: true, children: [' ', valuePreview(entry.value, 60)] })] })),
348
+ node: _jsxs(Text, { dimColor: true, children: [' ', t('settings-readonly-hint', { path: '~/.dsh/settings.yaml' })] }),
349
+ });
350
+ }
351
+ if (sections.length === 0 && readonlyNamespaces.length === 0) {
352
+ entries.push({
353
+ key: 'empty',
354
+ lines: 1,
355
+ node: _jsx(Text, { dimColor: true, children: t('settings-empty') }),
289
356
  });
290
357
  }
291
- entries.push({
292
- key: 'readonly:hint',
293
- lines: 1,
294
- node: _jsxs(Text, { dimColor: true, children: [' ', t('settings-readonly-hint', { path: '~/.dsh/settings.yaml' })] }),
295
- });
296
- }
297
- if (sections.length === 0 && readonlyNamespaces.length === 0) {
298
- entries.push({
299
- key: 'empty',
300
- lines: 1,
301
- node: _jsx(Text, { dimColor: true, children: t('settings-empty') }),
302
- });
303
358
  }
304
359
  // Focus-follow window: keep the focused entry fully inside the viewport.
305
360
  let totalLines = 0;
@@ -329,5 +384,9 @@ export function Settings({ channel, onClose, }) {
329
384
  entryOffset += entry.lines;
330
385
  return start >= windowStart && start + entry.lines <= windowStart + viewport;
331
386
  });
332
- return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: t('settings-title') }), _jsx(Box, { flexGrow: 1 }), host === undefined && _jsx(Text, { color: "warning", children: t('settings-unavailable') })] }), _jsx(Divider, {}), visible.map(entry => (_jsx(React.Fragment, { children: entry.node }, entry.key))), _jsx(Box, { flexGrow: 1 }), notice !== undefined && (_jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: notice.text })), _jsx(Divider, {}), _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: mode === 'edit' ? t('settings-hint-edit') : t('settings-hint-list') }) })] }));
387
+ const title = activeSection !== undefined && activeGroupSpec !== undefined
388
+ ? `${t('settings-title')} › ${pick(activeSection.title, activeSection.descriptions)} › ${pick(activeGroupSpec.title, activeGroupSpec.descriptions)}`
389
+ : t('settings-title');
390
+ const navigationHint = activeGroupSpec === undefined ? t('settings-hint-list') : t('settings-hint-group');
391
+ return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: title }), _jsx(Box, { flexGrow: 1 }), host === undefined && _jsx(Text, { color: "warning", children: t('settings-unavailable') })] }), _jsx(Divider, {}), visible.map(entry => (_jsx(React.Fragment, { children: entry.node }, entry.key))), _jsx(Box, { flexGrow: 1 }), notice !== undefined && (_jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: notice.text })), _jsx(Divider, {}), _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: mode === 'edit' ? t('settings-hint-edit') : navigationHint }) })] }));
333
392
  }
@@ -28,4 +28,12 @@ export declare function StatusLine({ channel, selectionActive, helpOpen, wake, }
28
28
  tick: number;
29
29
  };
30
30
  }): React.JSX.Element;
31
+ type UsageSnapshot = {
32
+ input: number;
33
+ cacheRead: number;
34
+ cacheWrite: number;
35
+ };
36
+ /** Return the prompt-cache hit rate, or nothing when usage is unavailable. */
37
+ export declare function formatCacheHitRate(usage: UsageSnapshot | undefined): string | undefined;
38
+ export {};
31
39
  //# sourceMappingURL=StatusLine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAQvD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,eAAuB,EACvB,QAAgB,EAChB,IAAI,GACL,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD,qBA2MA"}
1
+ {"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAQvD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,eAAuB,EACvB,QAAgB,EAChB,IAAI,GACL,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD,qBAsPA;AAED,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKvF"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text, useTerminalSize, useTheme } from '../ui.js';
3
3
  import { formatTokens } from '../cc/format.js';
4
4
  import { t } from '../i18n.js';
5
+ import { formatContextUsage, normalizeStatusBar } from '../tuiDisplayPrefs.js';
5
6
  import { Byline } from '../components/design-system/Byline.js';
6
7
  import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
7
8
  import { modeDisplayName } from '../sessionModes.js';
@@ -18,30 +19,32 @@ import { renderContextBar, renderTpsGauge, renderTpsSparkline, speedColor, } fro
18
19
  export function StatusLine({ channel, selectionActive = false, helpOpen = false, wake, }) {
19
20
  const { columns } = useTerminalSize();
20
21
  const [themeName] = useTheme();
22
+ const statusBar = normalizeStatusBar(channel.statusBar);
21
23
  const usage = channel.lastUsage;
24
+ const contextUsed = usage === undefined
25
+ ? undefined
26
+ : usage.input + usage.cacheRead + usage.cacheWrite;
22
27
  const contextParts = [];
23
- // Session-mode marker (Shift+Tab cycle): hidden on the unmarked base
24
- // mode (index 0); sage while a plan-declaring mode is in force.
25
- if (channel.modeIndex > 0) {
26
- contextParts.push(_jsx(Text, { color: channel.mode.plan === true ? 'planMode' : 'warning', children: modeDisplayName(channel.mode) }, "mode"));
27
- }
28
- if (channel.reasoningEffort !== undefined) {
28
+ if (statusBar.thinking && channel.reasoningEffort !== undefined) {
29
29
  contextParts.push(_jsx(Text, { color: "inactiveShimmer", children: channel.reasoningEffort }, "effort"));
30
30
  }
31
- if (usage !== undefined && usage.cacheRead > 0) {
32
- // Cache hit rate of the context fed to the model (read / total), one
33
- // decimal — the absolute read count lives in the context bar's system
34
- // segment, the rate is the glanceable health signal.
35
- const total = usage.input + usage.cacheRead + usage.cacheWrite;
36
- const rate = total > 0 ? (usage.cacheRead / total) * 100 : 0;
37
- contextParts.push(_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: t('status-cache-label') }), _jsxs(Text, { color: "inactiveShimmer", children: [rate.toFixed(1), "%"] })] }, "cache"));
31
+ if (statusBar.mode && channel.modeIndex > 0) {
32
+ contextParts.push(_jsx(Text, { color: channel.mode.plan === true ? 'planMode' : 'warning', children: modeDisplayName(channel.mode) }, "mode"));
33
+ }
34
+ const formattedContext = statusBar.contextUsage
35
+ ? formatContextUsage(contextUsed, channel.contextWindow, statusBar.compact)
36
+ : undefined;
37
+ const contextUsagePart = formattedContext === undefined
38
+ ? undefined
39
+ : (_jsxs(Text, { color: "inactiveShimmer", children: [_jsx(Text, { dimColor: true, children: "ctx " }), formattedContext] }, "context"));
40
+ if (statusBar.cache) {
41
+ const cacheRate = formatCacheHitRate(usage);
42
+ if (cacheRate !== undefined) {
43
+ contextParts.push(_jsxs(Text, { color: "inactiveShimmer", children: [_jsx(Text, { dimColor: true, children: t('status-cache-label') }), cacheRate] }, "cache"));
44
+ }
38
45
  }
39
- // TPS readout sits right after the model so a crowded footer truncates
40
- // the trailing fields (tokens/think/cache), never the speedometer. One
41
- // number only: the live value (gauge while streaming, sparkline of past
42
- // turns once samples exist) — no μ/p95 clutter.
43
46
  const tpsParts = [];
44
- if (channel.tps !== undefined) {
47
+ if (statusBar.tps && channel.tps !== undefined) {
45
48
  if (channel.working && channel.tpsSamples.length === 0) {
46
49
  tpsParts.push(_jsxs(Text, { children: [renderTpsGauge(channel.tps, channel.tps), ' ', _jsxs(Text, { dimColor: true, children: [Math.round(channel.tps), " tps"] })] }, "tps"));
47
50
  }
@@ -55,57 +58,77 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
55
58
  tpsParts.push(_jsxs(Text, { dimColor: true, children: [Math.round(channel.tps), " t/s"] }, "tps"));
56
59
  }
57
60
  }
58
- // Left group: every field sits at soft white (inactiveShimmer) instead of
59
- // the previous uniform dim grey — readable against dark terminals.
60
61
  const leftParts = [
61
- _jsx(Text, { color: "inactiveShimmer", children: channel.model }, "model"),
62
+ ...(statusBar.model
63
+ ? [_jsx(Text, { color: "inactiveShimmer", children: channel.model }, "model")]
64
+ : []),
62
65
  ...tpsParts,
63
66
  ...contextParts,
64
- _jsxs(Text, { color: "inactiveShimmer", children: [formatTokens(channel.tokens.input), "\u2192", formatTokens(channel.tokens.output)] }, "tokens"),
67
+ ...(statusBar.tokens
68
+ ? [
69
+ _jsxs(Text, { color: "inactiveShimmer", children: [formatTokens(channel.tokens.input), "\u2192", formatTokens(channel.tokens.output)] }, "tokens"),
70
+ ]
71
+ : []),
65
72
  ];
66
- // Right group: git branch in muted steel blue, cwd a soft white, the
67
- // session title dimmest (it truncates first anyway).
68
73
  const rightParts = [
69
- ...(channel.gitBranch
74
+ ...(statusBar.gitBranch && channel.gitBranch
70
75
  ? [
71
76
  _jsx(Text, { color: "professionalBlue", children: channel.gitBranch }, "git"),
72
77
  ]
73
78
  : []),
74
- _jsx(Text, { color: "inactiveShimmer", children: basename(channel.displayCwd) }, "cwd"),
75
- ...(channel.sessionTitle
79
+ ...(statusBar.cwd
80
+ ? [
81
+ _jsx(Text, { color: "inactiveShimmer", children: statusBar.compact ? basename(channel.displayCwd) : channel.displayCwd }, "cwd"),
82
+ ]
83
+ : []),
84
+ ...(statusBar.sessionTitle && channel.sessionTitle
76
85
  ? [
77
86
  _jsx(Text, { dimColor: true, children: channel.sessionTitle }, "title"),
78
87
  ]
79
88
  : []),
80
89
  ];
81
- // Row 3: the mode hint — and, while idle, the working-activity turn
82
- // summary (the live working line itself moves to the spinner slot above
83
- // the input while a turn runs, so the two never duplicate).
84
90
  const hint = selectionActive
85
- ? 'esc to return to input'
91
+ ? t('statusline-hint-select')
86
92
  : channel.working
87
- ? 'esc to interrupt'
88
- : !helpOpen
89
- ? '? for shortcuts'
93
+ ? t('statusline-hint-working')
94
+ : statusBar.shortcutHint && !helpOpen
95
+ ? t('statusline-hint-shortcuts')
90
96
  : '';
91
97
  const activity = channel.workingActivity;
92
- const showActivity = !channel.working &&
98
+ const showActivity = statusBar.activity &&
99
+ !channel.working &&
93
100
  activity !== undefined &&
94
101
  activity.line !== '' &&
95
102
  activity.phase !== 'idle';
103
+ const showTrajectory = statusBar.trajectory && wake !== undefined;
96
104
  const barWidth = columns - 4;
97
105
  let bar = null;
98
- // Theme-aware free segment: the light palette's near-white fill (#E8E8E8)
99
- // reads as a glaring white band on dark terminals — swap it for a deep
100
- // blue-gray there while keeping the light palette as-is (dark-ansi carries
101
- // `ansi:` color names, so map by theme name rather than palette tokens).
102
106
  const barColors = themeName === 'light'
103
107
  ? undefined
104
108
  : { freeFill: '#2E3440', freeText: '#8D95A6' };
105
- if (channel.contextBarEnabled && barWidth >= 14 && channel.contextWindow !== undefined) {
106
- bar = renderContextBar(channel.contextSegments, usage !== undefined ? usage.input + usage.cacheRead + usage.cacheWrite : 0, channel.contextWindow, barWidth, barColors);
109
+ if (statusBar.contextBar &&
110
+ channel.contextBarEnabled &&
111
+ barWidth >= 14 &&
112
+ usage !== undefined &&
113
+ channel.contextWindow !== undefined) {
114
+ bar = renderContextBar(channel.contextSegments, contextUsed ?? 0, channel.contextWindow, barWidth, barColors);
107
115
  }
108
- return (_jsx(Box, { paddingX: 1, width: columns, flexShrink: 0, children: _jsxs(Box, { flexDirection: "column", width: "100%", children: [bar ? _jsx(Text, { children: bar }) : null, _jsxs(Box, { flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsx(Text, { wrap: "truncate", children: _jsx(Byline, { children: leftParts }) }), _jsx(Box, { justifyContent: "flex-end", flexShrink: 2, children: _jsx(Text, { wrap: "truncate", children: _jsx(Byline, { children: rightParts }) }) })] }), _jsxs(Box, { height: 1, overflow: "hidden", flexDirection: "row", justifyContent: "space-between", gap: 2, children: [showActivity && activity !== undefined ? (_jsx(ActivityLine, { activity: activity, activityFrames: channel.activityFrames, warnPct: contextPressurePct(usage, channel.contextWindow), warnDanger: (contextPressurePct(usage, channel.contextWindow) ?? 0) >= 95 })) : hint ? (_jsx(Text, { color: "inactiveShimmer", children: hint })) : null, showActivity && hint ? (_jsx(Text, { color: "inactiveShimmer", wrap: "truncate", children: hint })) : null, wake !== undefined ? (_jsx(MiniWake, { band: wake.band, hint: wake.hint, tick: wake.tick })) : null] })] }) }));
116
+ const compactLeftParts = [...leftParts, ...rightParts];
117
+ const fullLeftParts = contextUsagePart === undefined
118
+ ? leftParts
119
+ : [...leftParts, contextUsagePart];
120
+ const hasStatusFields = compactLeftParts.length > 0 || contextUsagePart !== undefined;
121
+ const showSupplementalRow = hint !== '' || showActivity || showTrajectory;
122
+ return (_jsx(Box, { paddingX: 1, width: columns, flexShrink: 0, children: _jsxs(Box, { flexDirection: "column", width: "100%", children: [bar ? _jsx(Text, { children: bar }) : null, hasStatusFields ? statusBar.compact ? (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { wrap: "truncate", children: _jsx(Byline, { children: compactLeftParts }) }) }), contextUsagePart ? (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { wrap: "truncate", children: _jsx(Byline, { children: [contextUsagePart] }) }) })) : null] })) : (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { wrap: "truncate", children: _jsx(Byline, { children: fullLeftParts }) }) }), _jsx(Box, { justifyContent: "flex-end", flexShrink: 2, children: _jsx(Text, { wrap: "truncate", children: _jsx(Byline, { children: rightParts }) }) })] })) : null, showSupplementalRow ? _jsxs(Box, { height: 1, overflow: "hidden", flexDirection: "row", justifyContent: "space-between", gap: 2, children: [_jsxs(Box, { flexDirection: "row", flexGrow: 1, justifyContent: showActivity && hint ? 'space-between' : 'flex-start', gap: 2, children: [showActivity && activity !== undefined ? (_jsx(ActivityLine, { activity: activity, activityFrames: channel.activityFrames, warnPct: contextPressurePct(usage, channel.contextWindow), warnDanger: (contextPressurePct(usage, channel.contextWindow) ?? 0) >= 95 })) : hint ? (_jsx(Text, { color: "inactiveShimmer", children: hint })) : null, showActivity && hint ? (_jsx(Text, { color: "inactiveShimmer", wrap: "truncate", children: hint })) : null] }), showTrajectory && wake !== undefined ? (_jsx(MiniWake, { band: wake.band, hint: wake.hint, tick: wake.tick })) : null] }) : null] }) }));
123
+ }
124
+ /** Return the prompt-cache hit rate, or nothing when usage is unavailable. */
125
+ export function formatCacheHitRate(usage) {
126
+ if (usage === undefined)
127
+ return undefined;
128
+ const total = usage.input + usage.cacheRead + usage.cacheWrite;
129
+ if (!Number.isFinite(total) || total <= 0)
130
+ return undefined;
131
+ return `${((usage.cacheRead / total) * 100).toFixed(1)}%`;
109
132
  }
110
133
  function basename(path) {
111
134
  const parts = path.split(/[\\/]/);
@@ -1,4 +1,4 @@
1
1
  export { name, TuiSettingsSectionsRuntime } from './dsh-adapter/settings-sections.js';
2
- export type { TuiSettingsFieldKind, TuiSettingsFieldOption, TuiSettingsFieldWrite, TuiSettingsField, TuiSettingsSection, } from './dsh-adapter/settings-sections.js';
2
+ export type { TuiSettingsFieldKind, TuiSettingsFieldOption, TuiSettingsGroup, TuiSettingsFieldWrite, TuiSettingsField, TuiSettingsSection, } from './dsh-adapter/settings-sections.js';
3
3
  export { default } from './dsh-adapter/settings-sections.js';
4
4
  //# sourceMappingURL=settings-sections.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings-sections.d.ts","sourceRoot":"","sources":["../../src/settings-sections.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AACrF,YAAY,EACV,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAA"}
1
+ {"version":3,"file":"settings-sections.d.ts","sourceRoot":"","sources":["../../src/settings-sections.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AACrF,YAAY,EACV,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAA"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 精简 Tips 池 —— 启动首屏轮换(LogoV2)与 `/tips` 命令面板共用。
3
+ *
4
+ * 设计约定:
5
+ * - 每条 tip 一句话,中文 ≤ 约 60 字符、英文 ≤ 约 100 字符,首屏单行可读
6
+ * (窄终端自动截断);
7
+ * - 文案只讲"用户能立刻用上"的操作,不讲实现细节;
8
+ * - id 稳定(面板按 id 去重/排序),group 用于 `/tips` 面板分组展示;
9
+ * - 与 docs/user-guide.md 同源:文档是详版,这里是精版,覆盖全部功能面。
10
+ */
11
+ export type TipGroup = 'keys' | 'commands' | 'workflow' | 'display' | 'pitfalls';
12
+ export interface Tip {
13
+ id: string;
14
+ group: TipGroup;
15
+ zh: string;
16
+ en: string;
17
+ }
18
+ export declare const TIP_GROUP_LABELS: Record<TipGroup, {
19
+ zh: string;
20
+ en: string;
21
+ }>;
22
+ export declare const TIPS: readonly Tip[];
23
+ /**
24
+ * 启动随机轮换选择:每次启动随机取一条,让首屏每次都有新鲜感。
25
+ * random 可注入以便测试固定(默认 Math.random)。
26
+ */
27
+ export declare function pickRandomTip(random?: () => number): Tip;
28
+ /** 按分组取 tips(/tips 面板展示用,保持 TIPS 内顺序)。 */
29
+ export declare function tipsByGroup(group: TipGroup): Tip[];
30
+ //# sourceMappingURL=tips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tips.d.ts","sourceRoot":"","sources":["../../src/tips.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;AAEhF,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,QAAQ,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;CACX;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAMzE,CAAA;AAED,eAAO,MAAM,IAAI,EAAE,SAAS,GAAG,EAsiB9B,CAAA;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,MAAM,MAAoB,GAAG,GAAG,CAErE;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,GAAG,EAAE,CAElD"}