@deepseek-harness-tui/dsh-tui 0.8.4 → 0.8.6

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 (80) hide show
  1. package/lib/types/activityPrefs.d.ts +10 -0
  2. package/lib/types/activityPrefs.d.ts.map +1 -1
  3. package/lib/types/activityPrefs.js +17 -0
  4. package/lib/types/cc/figures.d.ts +7 -0
  5. package/lib/types/cc/figures.d.ts.map +1 -1
  6. package/lib/types/cc/figures.js +23 -0
  7. package/lib/types/cc/markdown.d.ts.map +1 -1
  8. package/lib/types/cc/markdown.js +47 -14
  9. package/lib/types/cc/syntaxTheme.d.ts +12 -0
  10. package/lib/types/cc/syntaxTheme.d.ts.map +1 -0
  11. package/lib/types/cc/syntaxTheme.js +76 -0
  12. package/lib/types/components/EffortChargeGlyph.d.ts +26 -0
  13. package/lib/types/components/EffortChargeGlyph.d.ts.map +1 -0
  14. package/lib/types/components/EffortChargeGlyph.js +70 -0
  15. package/lib/types/components/EffortInputBorder.d.ts +36 -0
  16. package/lib/types/components/EffortInputBorder.d.ts.map +1 -0
  17. package/lib/types/components/EffortInputBorder.js +73 -0
  18. package/lib/types/components/EffortTierBadge.d.ts +26 -0
  19. package/lib/types/components/EffortTierBadge.d.ts.map +1 -0
  20. package/lib/types/components/EffortTierBadge.js +97 -0
  21. package/lib/types/components/LoadedContextPanel.js +1 -1
  22. package/lib/types/components/MessageList.d.ts +4 -1
  23. package/lib/types/components/MessageList.d.ts.map +1 -1
  24. package/lib/types/components/MessageList.js +9 -11
  25. package/lib/types/components/PromptInput.d.ts.map +1 -1
  26. package/lib/types/components/PromptInput.js +8 -3
  27. package/lib/types/components/SplitDiffView.d.ts +4 -5
  28. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  29. package/lib/types/components/SplitDiffView.js +18 -65
  30. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  31. package/lib/types/components/StreamingMarkdown.js +10 -2
  32. package/lib/types/components/activityFrames.d.ts +7 -28
  33. package/lib/types/components/activityFrames.d.ts.map +1 -1
  34. package/lib/types/components/activityFrames.js +7 -94
  35. package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -1
  36. package/lib/types/components/design-system/ThemedBox.js +4 -1
  37. package/lib/types/components/design-system/ThemedText.js +2 -2
  38. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +12 -6
  39. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  40. package/lib/types/components/messages/AssistantThinkingMessage.js +25 -9
  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 +45 -17
  44. package/lib/types/components/messages/UserPromptMessage.d.ts +4 -6
  45. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  46. package/lib/types/components/messages/UserPromptMessage.js +5 -9
  47. package/lib/types/dsh-adapter/channel.d.ts +22 -0
  48. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  49. package/lib/types/dsh-adapter/channel.js +204 -19
  50. package/lib/types/dsh-adapter/index.d.ts +5 -0
  51. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  52. package/lib/types/dsh-adapter/index.js +19 -0
  53. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/plugin.js +176 -4
  55. package/lib/types/dsh-adapter/settings-sections.d.ts +13 -0
  56. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  57. package/lib/types/dsh-adapter/settings-sections.js +31 -6
  58. package/lib/types/ink/ink.d.ts +9 -0
  59. package/lib/types/ink/ink.d.ts.map +1 -1
  60. package/lib/types/ink/ink.js +24 -1
  61. package/lib/types/screens/Chat.d.ts.map +1 -1
  62. package/lib/types/screens/Chat.js +35 -6
  63. package/lib/types/screens/Settings.d.ts.map +1 -1
  64. package/lib/types/screens/Settings.js +109 -50
  65. package/lib/types/screens/StatusLine.d.ts +8 -0
  66. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  67. package/lib/types/screens/StatusLine.js +62 -39
  68. package/lib/types/settings-sections.d.ts +1 -1
  69. package/lib/types/settings-sections.d.ts.map +1 -1
  70. package/lib/types/theme.d.ts +12 -0
  71. package/lib/types/theme.d.ts.map +1 -1
  72. package/lib/types/theme.js +51 -21
  73. package/lib/types/tips.js +2 -2
  74. package/lib/types/trajectory/effortIgnition.d.ts +66 -0
  75. package/lib/types/trajectory/effortIgnition.d.ts.map +1 -0
  76. package/lib/types/trajectory/effortIgnition.js +142 -0
  77. package/lib/types/tuiDisplayPrefs.d.ts +47 -0
  78. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -0
  79. package/lib/types/tuiDisplayPrefs.js +60 -0
  80. package/package.json +6 -3
@@ -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
91
  ? t('statusline-hint-select')
86
92
  : channel.working
87
93
  ? t('statusline-hint-working')
88
- : !helpOpen
94
+ : statusBar.shortcutHint && !helpOpen
89
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"}
@@ -19,6 +19,8 @@ export type Theme = {
19
19
  autoAccept: string;
20
20
  bashBorder: string;
21
21
  claude: string;
22
+ toolNameMutate: string;
23
+ toolNameExec: string;
22
24
  claudeShimmer: string;
23
25
  claudeBlue_FOR_SYSTEM_SPINNER: string;
24
26
  claudeBlueShimmer_FOR_SYSTEM_SPINNER: string;
@@ -135,6 +137,16 @@ export type ThemeName = string;
135
137
  * (light/dark), unknown names fall back to `dark`.
136
138
  */
137
139
  export declare function getTheme(themeName: ThemeName): Theme;
140
+ /**
141
+ * Whether the active theme's RESOLVED palette renders on a light background.
142
+ * Keyed off the resolved palette's IDENTITY for the built-ins (auto resolves
143
+ * to the shared light/dark instance, so this covers auto-with-light-terminal
144
+ * that theme-NAME comparisons miss) and off the ink-text luminance for
145
+ * custom themes (light palettes pair with dark ink). The palette's
146
+ * `background` field is a badge fill, not the terminal background — never a
147
+ * lightness signal. Colour-pair variants (effort ignition hues) consume this.
148
+ */
149
+ export declare function isLightThemeActive(themeName: ThemeName): boolean;
138
150
  /**
139
151
  * Register the custom-theme resolver. Called once by ThemeProvider; the
140
152
  * resolver must return `undefined` for names it does not know so getTheme
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/theme.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,KAAK,GAAG;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,6BAA6B,EAAE,MAAM,CAAA;IACrC,oCAAoC,EAAE,MAAM,CAAA;IAC5C,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAElB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IAEtB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IAGvB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,qBAAqB,EAAE,MAAM,CAAA;IAE7B,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IAEnB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IAEtB,sBAAsB,EAAE,MAAM,CAAA;IAC9B,uBAAuB,EAAE,MAAM,CAAA;IAC/B,wBAAwB,EAAE,MAAM,CAAA;IAChC,yBAAyB,EAAE,MAAM,CAAA;IACjC,yBAAyB,EAAE,MAAM,CAAA;IACjC,yBAAyB,EAAE,MAAM,CAAA;IACjC,uBAAuB,EAAE,MAAM,CAAA;IAC/B,uBAAuB,EAAE,MAAM,CAAA;IAE/B,gBAAgB,EAAE,MAAM,CAAA;IAExB,YAAY,EAAE,MAAM,CAAA;IAEpB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,0BAA0B,EAAE,MAAM,CAAA;IAClC,wBAAwB,EAAE,MAAM,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,0BAA0B,EAAE,MAAM,CAAA;IAClC,qBAAqB,EAAE,MAAM,CAAA;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,qBAAqB,EAAE,MAAM,CAAA;IAC7B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;CAC/B,CAAA;AAED,kDAAkD;AAClD,eAAO,MAAM,WAAW,yCAA0C,CAAA;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,SAAS,CAAA;AAWrC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAE7D;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,IAAI,OAAO,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAA;AA2S9B;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,KAAK,CAiBpD;AASD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAWhE;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,GAC5C,IAAI,CAEN;AASD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAExD;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,KAAK,CAEtC"}
@@ -58,6 +58,8 @@ const darkTheme = {
58
58
  autoAccept: rgb('#B3A0D4'), // Soft violet
59
59
  bashBorder: rgb('#D194AE'), // Mist rose
60
60
  claude: rgb('#7DA1DE'), // Accent Soft — mist brand blue
61
+ toolNameMutate: rgb('#E5C07B'), // soft gold — Edit/Write (warm accent)
62
+ toolNameExec: rgb('#56B6C2'), // mist cyan — Bash/exec tools
61
63
  claudeShimmer: rgb('#ABC2EC'), // Border Blue for shimmer effect
62
64
  claudeBlue_FOR_SYSTEM_SPINNER: rgb('#7DA1DE'),
63
65
  claudeBlueShimmer_FOR_SYSTEM_SPINNER: rgb('#ABC2EC'),
@@ -93,16 +95,16 @@ const darkTheme = {
93
95
  toolDotWrite: rgb('#B3A0D4'), // soft violet — edit/write
94
96
  toolDotWeb: rgb('#7DA1DE'), // mist blue — web search/fetch
95
97
  toolDotTask: rgb('#D194AE'), // mist rose — subagent/jobs
96
- syntaxKeyword: rgb('#8FA8E8'), // mist blue
97
- syntaxString: rgb('#9FBF8F'), // soft sage
98
+ syntaxKeyword: rgb('#82AAFF'), // vivid blue (theme anchor)
99
+ syntaxString: rgb('#E5C07B'), // gold - echoes the user-message gold accent
98
100
  syntaxComment: rgb('#6B7280'), // neutral grey
99
- syntaxNumber: rgb('#D9A97E'), // warm tan
100
- syntaxFunction: rgb('#82B8C7'), // cyan blue
101
- syntaxType: rgb('#B39DDB'), // soft violet
101
+ syntaxNumber: rgb('#D19A66'), // warm orange
102
+ syntaxFunction: rgb('#56B6C2'), // vivid cyan
103
+ syntaxType: rgb('#C678DD'), // vivid violet
102
104
  syntaxVariable: rgb('#C9D1D9'), // near-text
103
105
  syntaxOperator: rgb('#93A1B0'), // blue grey
104
106
  syntaxPunctuation: rgb('#7A8694'), // dim blue grey
105
- syntaxConstant: rgb('#D98C9B'), // soft rose
107
+ syntaxConstant: rgb('#E48A9B'), // soft rose
106
108
  red_FOR_SUBAGENTS_ONLY: rgb('#D4685E'),
107
109
  blue_FOR_SUBAGENTS_ONLY: rgb('#7496D6'),
108
110
  green_FOR_SUBAGENTS_ONLY: rgb('#66B285'),
@@ -115,8 +117,8 @@ const darkTheme = {
115
117
  chromeYellow: rgb('#D8B270'),
116
118
  clawd_body: rgb('#D98A63'), // Warm mascot orange
117
119
  clawd_background: rgb('#000000'),
118
- userMessageBackground: rgb('#292D36'),
119
- userMessageBackgroundHover: rgb('#343945'),
120
+ userMessageBackground: '', // user turn: no fill, gold bold text only (Kimi style)
121
+ userMessageBackgroundHover: rgb('#3B5BDB'), // hover/expand: blue block with gold text
120
122
  messageActionsBackground: rgb('#2E333D'),
121
123
  selectionBg: rgb('#3B4A66'), // Mist-blue tint on dark
122
124
  bashMessageBackgroundColor: rgb('#2C3038'),
@@ -125,7 +127,7 @@ const darkTheme = {
125
127
  rate_limit_empty: rgb('#3C414B'),
126
128
  fastMode: rgb('#E09A58'),
127
129
  fastModeShimmer: rgb('#EAB478'),
128
- briefLabelYou: rgb('#ABC2EC'),
130
+ briefLabelYou: rgb('#FFDF80'),
129
131
  briefLabelClaude: rgb('#7DA1DE'),
130
132
  rainbow_red: rgb('#D98F8A'),
131
133
  rainbow_orange: rgb('#D9A97E'),
@@ -152,6 +154,8 @@ const lightTheme = {
152
154
  autoAccept: rgb('#9B86B8'), // Muted violet (from surface-alt pink-mist)
153
155
  bashBorder: rgb('#C07A93'), // Muted rose (from surface-alt pink-mist)
154
156
  claude: rgb('#3F6CC4'), // Primary Blue — brand
157
+ toolNameMutate: rgb('#8A6A00'), // deep gold - Edit/Write (warm accent)
158
+ toolNameExec: rgb('#0F7A8A'), // deep cyan - Bash/exec tools
155
159
  claudeShimmer: rgb('#5E88CC'), // Accent Blue for shimmer effect
156
160
  claudeBlue_FOR_SYSTEM_SPINNER: rgb('#3F6CC4'),
157
161
  claudeBlueShimmer_FOR_SYSTEM_SPINNER: rgb('#5E88CC'),
@@ -187,16 +191,16 @@ const lightTheme = {
187
191
  toolDotWrite: rgb('#7A5CA8'),
188
192
  toolDotWeb: rgb('#4A63A8'),
189
193
  toolDotTask: rgb('#B04A5A'),
190
- syntaxKeyword: rgb('#4A63A8'),
191
- syntaxString: rgb('#4E7A4E'),
194
+ syntaxKeyword: rgb('#2557C7'),
195
+ syntaxString: rgb('#8A6A00'),
192
196
  syntaxComment: rgb('#8A8F98'),
193
- syntaxNumber: rgb('#A96B32'),
194
- syntaxFunction: rgb('#3F7E8F'),
195
- syntaxType: rgb('#7A5CA8'),
197
+ syntaxNumber: rgb('#B25E1E'),
198
+ syntaxFunction: rgb('#0F7A8A'),
199
+ syntaxType: rgb('#8A3FC7'),
196
200
  syntaxVariable: rgb('#343945'),
197
201
  syntaxOperator: rgb('#5B6672'),
198
202
  syntaxPunctuation: rgb('#9AA0A8'),
199
- syntaxConstant: rgb('#B04A5A'),
203
+ syntaxConstant: rgb('#C2186B'),
200
204
  red_FOR_SUBAGENTS_ONLY: rgb('#BE5A52'),
201
205
  blue_FOR_SUBAGENTS_ONLY: rgb('#3F6CC4'),
202
206
  green_FOR_SUBAGENTS_ONLY: rgb('#4E9675'),
@@ -209,8 +213,8 @@ const lightTheme = {
209
213
  chromeYellow: rgb('#C99A3F'),
210
214
  clawd_body: rgb('#D98A63'), // Warm mascot orange
211
215
  clawd_background: rgb('#F6F3ED'),
212
- userMessageBackground: rgb('#EEE5D2'), // Surface
213
- userMessageBackgroundHover: rgb('#E4D9E5'), // Surface Alt
216
+ userMessageBackground: '', // user turn: no fill in light mode, gold text only
217
+ userMessageBackgroundHover: rgb('#DCE4FB'), // subtle blue tint on hover/expand
214
218
  messageActionsBackground: rgb('#E4D9E5'),
215
219
  selectionBg: rgb('#D5DEF2'), // Mist-blue tint on warm white
216
220
  bashMessageBackgroundColor: rgb('#EAE1D3'),
@@ -219,7 +223,7 @@ const lightTheme = {
219
223
  rate_limit_empty: rgb('#DDD5C7'),
220
224
  fastMode: rgb('#D98E4A'),
221
225
  fastModeShimmer: rgb('#E2A465'),
222
- briefLabelYou: rgb('#5E88CC'),
226
+ briefLabelYou: rgb('#A67600'),
223
227
  briefLabelClaude: rgb('#3F6CC4'),
224
228
  rainbow_red: rgb('#D98888'),
225
229
  rainbow_orange: rgb('#D9A276'),
@@ -248,6 +252,8 @@ const darkAnsiTheme = {
248
252
  autoAccept: 'ansi:magentaBright',
249
253
  bashBorder: 'ansi:magentaBright',
250
254
  claude: 'ansi:blueBright',
255
+ toolNameMutate: 'ansi:yellowBright',
256
+ toolNameExec: 'ansi:cyanBright',
251
257
  claudeShimmer: 'ansi:blueBright',
252
258
  claudeBlue_FOR_SYSTEM_SPINNER: 'ansi:blueBright',
253
259
  claudeBlueShimmer_FOR_SYSTEM_SPINNER: 'ansi:blueBright',
@@ -305,8 +311,8 @@ const darkAnsiTheme = {
305
311
  chromeYellow: 'ansi:yellowBright',
306
312
  clawd_body: 'ansi:redBright',
307
313
  clawd_background: 'ansi:black',
308
- userMessageBackground: 'ansi:blackBright',
309
- userMessageBackgroundHover: 'ansi:white',
314
+ userMessageBackground: '',
315
+ userMessageBackgroundHover: 'ansi:blue',
310
316
  messageActionsBackground: 'ansi:blackBright',
311
317
  selectionBg: 'ansi:blue',
312
318
  bashMessageBackgroundColor: 'ansi:black',
@@ -315,7 +321,7 @@ const darkAnsiTheme = {
315
321
  rate_limit_empty: 'ansi:white',
316
322
  fastMode: 'ansi:redBright',
317
323
  fastModeShimmer: 'ansi:redBright',
318
- briefLabelYou: 'ansi:blueBright',
324
+ briefLabelYou: 'ansi:yellowBright',
319
325
  briefLabelClaude: 'ansi:blueBright',
320
326
  rainbow_red: 'ansi:red',
321
327
  rainbow_orange: 'ansi:redBright',
@@ -363,6 +369,30 @@ export function getTheme(themeName) {
363
369
  * (markdown inline code) resolves user themes through getActiveTheme().
364
370
  */
365
371
  let customThemeResolver;
372
+ /**
373
+ * Whether the active theme's RESOLVED palette renders on a light background.
374
+ * Keyed off the resolved palette's IDENTITY for the built-ins (auto resolves
375
+ * to the shared light/dark instance, so this covers auto-with-light-terminal
376
+ * that theme-NAME comparisons miss) and off the ink-text luminance for
377
+ * custom themes (light palettes pair with dark ink). The palette's
378
+ * `background` field is a badge fill, not the terminal background — never a
379
+ * lightness signal. Colour-pair variants (effort ignition hues) consume this.
380
+ */
381
+ export function isLightThemeActive(themeName) {
382
+ const theme = getTheme(themeName);
383
+ if (theme === lightTheme)
384
+ return true;
385
+ if (theme === darkTheme || theme === darkAnsiTheme)
386
+ return false;
387
+ // 自定义主题:按文本墨色亮度判定——浅底配深墨(ink)、深底配亮墨。
388
+ // 调色板的 background 字段是徽标填充色而非终端背景,不能作判据。
389
+ const ink = theme.text;
390
+ const rgb = /^rgb\((\d+),(\d+),(\d+)\)$/.exec(ink);
391
+ if (rgb === null)
392
+ return false;
393
+ const [r, g, b] = [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];
394
+ return 0.299 * r + 0.587 * g + 0.114 * b < 140;
395
+ }
366
396
  /**
367
397
  * Register the custom-theme resolver. Called once by ThemeProvider; the
368
398
  * resolver must return `undefined` for names it does not know so getTheme