@deepseek-harness-tui/dsh-tui 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +2 -2
  2. package/bin/dsh-tui.js +19 -3
  3. package/cordis.patch.yml +24 -11
  4. package/cordis.yml +9 -1
  5. package/lib/types/commands.d.ts.map +1 -1
  6. package/lib/types/commands.js +2 -1
  7. package/lib/types/components/MessageList.d.ts +3 -1
  8. package/lib/types/components/MessageList.d.ts.map +1 -1
  9. package/lib/types/components/MessageList.js +4 -4
  10. package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
  11. package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
  12. package/lib/types/components/PluginSceneBoundary.js +29 -0
  13. package/lib/types/components/SkillsPicker.d.ts +18 -0
  14. package/lib/types/components/SkillsPicker.d.ts.map +1 -0
  15. package/lib/types/components/SkillsPicker.js +50 -0
  16. package/lib/types/components/SplitDiffView.d.ts +28 -0
  17. package/lib/types/components/SplitDiffView.d.ts.map +1 -0
  18. package/lib/types/components/SplitDiffView.js +417 -0
  19. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +3 -1
  20. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  21. package/lib/types/components/messages/AssistantToolUseMessage.js +17 -7
  22. package/lib/types/dsh-adapter/channel.d.ts +68 -0
  23. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  24. package/lib/types/dsh-adapter/channel.js +120 -5
  25. package/lib/types/dsh-adapter/contract.d.ts +1 -1
  26. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  27. package/lib/types/dsh-adapter/contract.js +1 -0
  28. package/lib/types/dsh-adapter/index.d.ts +4 -0
  29. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  30. package/lib/types/dsh-adapter/index.js +1 -0
  31. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  32. package/lib/types/dsh-adapter/plugin.js +59 -0
  33. package/lib/types/dsh-adapter/promptDebug.d.ts +10 -0
  34. package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
  35. package/lib/types/dsh-adapter/promptDebug.js +148 -0
  36. package/lib/types/dsh-adapter/scenes.d.ts +75 -0
  37. package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
  38. package/lib/types/dsh-adapter/scenes.js +78 -0
  39. package/lib/types/dsh-adapter/settings-sections.d.ts +116 -0
  40. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
  41. package/lib/types/dsh-adapter/settings-sections.js +63 -0
  42. package/lib/types/dsh-adapter/settingsEditor.d.ts +128 -0
  43. package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
  44. package/lib/types/dsh-adapter/settingsEditor.js +244 -0
  45. package/lib/types/i18n.d.ts +137 -10
  46. package/lib/types/i18n.d.ts.map +1 -1
  47. package/lib/types/i18n.js +36 -3
  48. package/lib/types/jsx-runtime.d.ts +2 -0
  49. package/lib/types/jsx-runtime.d.ts.map +1 -0
  50. package/lib/types/jsx-runtime.js +13 -0
  51. package/lib/types/modelRoute.d.ts +10 -2
  52. package/lib/types/modelRoute.d.ts.map +1 -1
  53. package/lib/types/modelRoute.js +33 -4
  54. package/lib/types/scenes.d.ts +3 -0
  55. package/lib/types/scenes.d.ts.map +1 -0
  56. package/lib/types/scenes.js +4 -0
  57. package/lib/types/screens/Chat.d.ts.map +1 -1
  58. package/lib/types/screens/Chat.js +107 -11
  59. package/lib/types/screens/Settings.d.ts +20 -0
  60. package/lib/types/screens/Settings.d.ts.map +1 -0
  61. package/lib/types/screens/Settings.js +333 -0
  62. package/lib/types/screens/StatusLine.js +1 -1
  63. package/lib/types/settings-sections.d.ts +3 -0
  64. package/lib/types/settings-sections.d.ts.map +1 -0
  65. package/lib/types/settings-sections.js +4 -0
  66. package/lib/types/theme.d.ts +12 -0
  67. package/lib/types/theme.d.ts.map +1 -1
  68. package/lib/types/theme.js +36 -0
  69. package/package.json +17 -1
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { t, getLang, setLang, isLang, writeLangPref, subscribeLang } from '../i18n.js';
4
4
  import { AlternateScreen, Box, Text, useInput, ScrollBox, useTheme, useTerminalSize } from '../ui.js';
5
+ import * as tuiKit from '../ui.js';
5
6
  import { POINTER } from '../cc/figures.js';
6
7
  import { isMod, isPlainReturnInput, modLabel } from '../utils/modifiers.js';
7
8
  import { formatTokens } from '../cc/format.js';
@@ -26,7 +27,10 @@ import { StatusLine } from './StatusLine.js';
26
27
  import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
27
28
  import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
28
29
  import { ModelPicker } from '../components/ModelPicker.js';
30
+ import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
31
+ import { SkillsPicker, SkillsPickerLoading } from '../components/SkillsPicker.js';
29
32
  import { SessionBrowser } from './SessionBrowser.js';
33
+ import { Settings } from './Settings.js';
30
34
  import { WorkspacePicker } from '../components/WorkspacePicker.js';
31
35
  import { WorkspaceFlowPicker } from '../components/WorkspaceFlowPicker.js';
32
36
  import { ActivityPicker } from '../components/ActivityPicker.js';
@@ -152,9 +156,17 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
152
156
  const [modelPickerOpen, setModelPickerOpen] = React.useState(false);
153
157
  const [models, setModels] = React.useState([]);
154
158
  const [modelIndex, setModelIndex] = React.useState(0);
159
+ /** `/skills` 技能目录(issue #204):null = 注册表快照在途。 */
160
+ const [skillsPickerOpen, setSkillsPickerOpen] = React.useState(false);
161
+ const [skillsList, setSkillsList] = React.useState(null);
162
+ const [skillsIndex, setSkillsIndex] = React.useState(0);
155
163
  /** `/resume` opens the session browser, a screen rather than a panel. It
156
164
  * owns its own selection, filters and keyboard — Chat only opens it. */
157
165
  const [browserOpen, setBrowserOpen] = React.useState(false);
166
+ /** `/settings` opens the plugin settings screen (issue #165) — like the
167
+ * browser, a screen rather than a panel: it owns its own focus, staged
168
+ * drafts and keyboard; Chat only opens it. */
169
+ const [settingsOpen, setSettingsOpen] = React.useState(false);
158
170
  const [workspacePickerOpen, setWorkspacePickerOpen] = React.useState(false);
159
171
  const [workspaceTargets, setWorkspaceTargets] = React.useState([]);
160
172
  const [workspaceIndex, setWorkspaceIndex] = React.useState(0);
@@ -635,6 +647,23 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
635
647
  setModelIndex(index >= 0 ? index : 0);
636
648
  });
637
649
  return true;
650
+ case 'skills':
651
+ // issue #204: 列出当前 agent 的完整技能目录(名称 + 来源 + 简述),
652
+ // Enter 把可直调技能以 `/name ` 填回输入行(completion-only 分发的
653
+ // 同一路径)。注册表读取走 channel(快照 scoped 到 live agent)。
654
+ setHelpOpen(false);
655
+ setSkillsList(null);
656
+ setSkillsIndex(0);
657
+ setSkillsPickerOpen(true);
658
+ void channel.listSkills().then((list) => {
659
+ if (list === undefined) {
660
+ setSkillsPickerOpen(false);
661
+ channel.notify(t('skills-load-failed'), { color: 'error' });
662
+ return;
663
+ }
664
+ setSkillsList(list);
665
+ });
666
+ return true;
638
667
  case 'provider': {
639
668
  // Interactive add-provider wizard (/provider): drives the shared
640
669
  // question panel, persists profile + key via the channel's settings/
@@ -788,6 +817,13 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
788
817
  channel.pushLocal('/cost', lines);
789
818
  return true;
790
819
  }
820
+ case 'settings': {
821
+ // Plugin settings screen (issue #165): opens immediately; the screen
822
+ // reads sections + namespaces from the channel itself.
823
+ setHelpOpen(false);
824
+ setSettingsOpen(true);
825
+ return true;
826
+ }
791
827
  case 'config': {
792
828
  const userHome = process.env.USERPROFILE ?? '';
793
829
  const lines = [
@@ -888,13 +924,6 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
888
924
  setHelpOpen(false);
889
925
  channel.pushLocal('/mcp', channel.mcpStatus());
890
926
  return true;
891
- case 'memory':
892
- setHelpOpen(false);
893
- channel.pushLocal('/memory', [
894
- t('memory-none'),
895
- t('memory-hint'),
896
- ]);
897
- return true;
898
927
  case 'update':
899
928
  setHelpOpen(false);
900
929
  if (onUpdate === undefined) {
@@ -1202,6 +1231,16 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1202
1231
  // search box, which Chat would otherwise route into the prompt.
1203
1232
  if (browserOpen)
1204
1233
  return;
1234
+ // Same for the settings screen: plain letters (s save / d discard) and
1235
+ // the field draft editor belong to it alone.
1236
+ if (settingsOpen)
1237
+ return;
1238
+ // A plugin scene (dsh-tui-scenes) or the trajectory scene owns the whole
1239
+ // screen while open: every key belongs to it. Unguarded, an Esc meant to
1240
+ // CLOSE the scene also reached the chat:cancel branch below whenever a
1241
+ // turn was in flight — closing the view and killing the turn in one key.
1242
+ if (sceneOpen || channel.pluginScene !== undefined)
1243
+ return;
1205
1244
  // The questionnaire / approval panel owns the keyboard while one is
1206
1245
  // pending (the panel's own useInput handles ↑/↓/Space/Tab/Enter/Esc;
1207
1246
  // the prompt input is unmounted, so nothing else should see these keys).
@@ -1457,6 +1496,30 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1457
1496
  }
1458
1497
  return;
1459
1498
  }
1499
+ if (skillsPickerOpen) {
1500
+ const list = skillsList ?? [];
1501
+ if (key.upArrow) {
1502
+ if (list.length > 0)
1503
+ setSkillsIndex(index => (index <= 0 ? list.length - 1 : index - 1));
1504
+ }
1505
+ else if (key.downArrow) {
1506
+ if (list.length > 0)
1507
+ setSkillsIndex(index => (index >= list.length - 1 ? 0 : index + 1));
1508
+ }
1509
+ else if (plainReturn) {
1510
+ const skill = list[skillsIndex];
1511
+ setSkillsPickerOpen(false);
1512
+ // 可直调技能 Enter 填入 `/name `——与 / 菜单选中技能同一条
1513
+ // completion-only 分发路径;模型专用技能(userInvocable=false)只关闭。
1514
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
1515
+ if (skill?.userInvocable)
1516
+ setHistoryFill(`/${skill.name} `);
1517
+ }
1518
+ else if (key.escape) {
1519
+ setSkillsPickerOpen(false);
1520
+ }
1521
+ return;
1522
+ }
1460
1523
  if (activityPickerOpen) {
1461
1524
  if (key.upArrow) {
1462
1525
  setActivityIndex(index => (index <= 0 ? PRESET_NAMES.length - 1 : index - 1));
@@ -1744,6 +1807,32 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1744
1807
  // Working-activity line (spinner slot): context-pressure prefix shares the
1745
1808
  // StatusLine thresholds (amber ≥ 80, red ≥ 95).
1746
1809
  const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
1810
+ // A plugin scene (dsh-tui-scenes) takes the whole terminal the same way
1811
+ // the trajectory scene does, and sits at the TOP of this return chain:
1812
+ // an open() landing while the session browser or the trajectory scene is
1813
+ // up must still take the screen (and the keyboard, via the useInput guard
1814
+ // above), not queue silently behind them. Closing the plugin scene lands
1815
+ // back on whatever screen was up before, so these early returns read as a
1816
+ // stack. The component comes from the registry, so its identity is stable
1817
+ // across renders and its hook state survives re-renders; it receives the
1818
+ // TUI's own React + ui kit because a plugin importing its own React copy
1819
+ // would die on the first hook call under this reconciler.
1820
+ // The scene is third-party code, so it renders inside a boundary: a render
1821
+ // crash reports to the transcript and closes the scene instead of taking
1822
+ // the whole TUI down through ink's app-level boundary.
1823
+ const pluginScene = channel.pluginScene;
1824
+ if (pluginScene !== undefined) {
1825
+ const node = (_jsx(PluginSceneBoundary, { id: pluginScene.id, onError: (id, error) => {
1826
+ channel.notify(t('plugin-scene-crashed', { id, err: error.message }), { color: 'error' });
1827
+ channel.closePluginScene();
1828
+ }, children: React.createElement(pluginScene.component, {
1829
+ React,
1830
+ ui: tuiKit,
1831
+ channel,
1832
+ close: () => channel.closePluginScene(),
1833
+ }) }));
1834
+ return fullscreen ? node : _jsx(AlternateScreen, { children: node });
1835
+ }
1747
1836
  // The browser is a screen, not an overlay: it REPLACES the conversation
1748
1837
  // rather than floating above it. Rendering it as an early return (after
1749
1838
  // every hook above has run) is what makes that literal — there is no
@@ -1754,8 +1843,15 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1754
1843
  // hosts are already in it and must not nest a second one.
1755
1844
  return fullscreen ? browser : _jsx(AlternateScreen, { children: browser });
1756
1845
  }
1846
+ // The settings screen follows the browser's rule exactly: it REPLACES the
1847
+ // conversation (an early return after every hook above has run), so there
1848
+ // is no transcript underneath to be repainted or bled through.
1849
+ if (settingsOpen) {
1850
+ const screen = _jsx(Settings, { channel: channel, onClose: () => setSettingsOpen(false) });
1851
+ return fullscreen ? screen : _jsx(AlternateScreen, { children: screen });
1852
+ }
1757
1853
  /** Prompt input is inert while a modal dialog owns the keyboard. */
1758
- const promptSelectionActive = selectionActive || modelPickerOpen || workspacePickerOpen || workspaceFlow !== null || activityPickerOpen ||
1854
+ const promptSelectionActive = selectionActive || modelPickerOpen || skillsPickerOpen || workspacePickerOpen || workspaceFlow !== null || activityPickerOpen ||
1759
1855
  effortSliderOpen || presetPickerOpen || themePickerOpen || thinkingOpen || historyOpen || rewindOpen || searchOpen ||
1760
1856
  btw !== null;
1761
1857
  // The trajectory scene replaces the conversation for as long as it is open.
@@ -1776,7 +1872,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1776
1872
  // 常驻、只移除其普通子节点,blit 解毒不触发,被覆盖的转录行会在
1777
1873
  // blit-skip 后留空(Esc 关 picker 一片空白的根因)。
1778
1874
  const dialogOverlayOpen = thinkingOpen || (workspacePickerOpen && workspaceTargets.length > 0) || workspaceFlow !== null ||
1779
- modelPickerOpen ||
1875
+ modelPickerOpen || skillsPickerOpen ||
1780
1876
  activityPickerOpen || (effortSliderOpen && effortOptions.length > 1) ||
1781
1877
  (presetPickerOpen && presetOptions.length > 0) || themePickerOpen || historyOpen ||
1782
1878
  rewindOpen || searchOpen;
@@ -1787,7 +1883,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1787
1883
  seekRow(lastUser.id);
1788
1884
  else
1789
1885
  handle?.scrollToBottom();
1790
- } })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd }), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: () => { setLoadedContextOpen(previous => !previous); } })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, model: channel.model, showAll: showAllMessages, thinkingVisible: thinkingVisible, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
1886
+ } })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd }), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: () => { setLoadedContextOpen(previous => !previous); } })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, model: channel.model, diffLayout: channel.diffLayout, showAll: showAllMessages, thinkingVisible: thinkingVisible, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
1791
1887
  (channel.activityEnabled &&
1792
1888
  channel.workingActivity !== undefined &&
1793
1889
  channel.workingActivity.line !== '' &&
@@ -1806,7 +1902,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1806
1902
  band: wakeBand,
1807
1903
  hint: trajectorySeen ? undefined : `${modLabel}t`,
1808
1904
  tick: Math.floor(wakeTime / 120),
1809
- } }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 8), children: [thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), workspacePickerOpen && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: workspaceIndex, currentCwd: channel.cwd }) })), workspaceFlow !== null && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: workspaceFlow.title, choices: workspaceFlow.choices, focusIndex: workspaceFlowIndex, busy: workspaceFlowBusy, input: workspaceFlowInput }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), effortSliderOpen && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: effortIndex, currentId: channel.reasoningEffort }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }));
1905
+ } }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 8), children: [thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), workspacePickerOpen && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: workspaceIndex, currentCwd: channel.cwd }) })), workspaceFlow !== null && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: workspaceFlow.title, choices: workspaceFlow.choices, focusIndex: workspaceFlowIndex, busy: workspaceFlowBusy, input: workspaceFlowInput }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), skillsPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: skillsIndex })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), effortSliderOpen && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: effortIndex, currentId: channel.reasoningEffort }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }));
1810
1906
  }
1811
1907
  /**
1812
1908
  * The pinned prompt header shown above the ScrollBox while the user has
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import type { Channel } from '../dsh-adapter/channel.js';
3
+ /**
4
+ * The settings screen — `/settings` as a screen of its own (issue #165).
5
+ *
6
+ * The TUI owns only presentation here: plugin-declared sections from the
7
+ * `tuiSettingsSections` seam render as editable forms; every write goes back
8
+ * through the dsh settings service (revision-fenced `mutate` path ops) or the
9
+ * credentials seam (secret fields, blank-until-typed). Namespaces no plugin
10
+ * declared a section for stay read-only with a YAML hint — the same fallback
11
+ * the web front door gives namespaces without a card.
12
+ *
13
+ * Edits are staged, never settled live: typing changes a draft, and only the
14
+ * explicit save writes the durable document (see settingsEditor.ts for why).
15
+ */
16
+ export declare function Settings({ channel, onClose, }: {
17
+ channel: Channel;
18
+ onClose: () => void;
19
+ }): React.ReactNode;
20
+ //# sourceMappingURL=Settings.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,333 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text, useInput, useTerminalSize } from '../ui.js';
4
+ import { Divider } from '../components/design-system/Divider.js';
5
+ import { HintLine } from '../components/design-system/HintLine.js';
6
+ import { isMod, isPlainReturn } from '../utils/modifiers.js';
7
+ import { truncateWidth } from '../sessions/format.js';
8
+ import { getLang, t } from '../i18n.js';
9
+ import { SettingsForm } from '../dsh-adapter/settingsEditor.js';
10
+ /** Pick the provider-owned translation for the active language. */
11
+ function pick(text, descriptions) {
12
+ return descriptions?.[getLang()] ?? text;
13
+ }
14
+ /** Compact one-line preview of a read-only namespace's resolved value. */
15
+ function valuePreview(value, budget) {
16
+ let raw;
17
+ try {
18
+ raw = JSON.stringify(value) ?? 'undefined';
19
+ }
20
+ catch {
21
+ raw = String(value);
22
+ }
23
+ return truncateWidth(raw, Math.max(8, budget));
24
+ }
25
+ /**
26
+ * The settings screen — `/settings` as a screen of its own (issue #165).
27
+ *
28
+ * The TUI owns only presentation here: plugin-declared sections from the
29
+ * `tuiSettingsSections` seam render as editable forms; every write goes back
30
+ * through the dsh settings service (revision-fenced `mutate` path ops) or the
31
+ * credentials seam (secret fields, blank-until-typed). Namespaces no plugin
32
+ * declared a section for stay read-only with a YAML hint — the same fallback
33
+ * the web front door gives namespaces without a card.
34
+ *
35
+ * Edits are staged, never settled live: typing changes a draft, and only the
36
+ * explicit save writes the durable document (see settingsEditor.ts for why).
37
+ */
38
+ export function Settings({ channel, onClose, }) {
39
+ // Explicit terminal size (not flexGrow) — the same rule SessionBrowser's
40
+ // root follows inside the alternate screen's fixed-height box.
41
+ const { columns, rows } = useTerminalSize();
42
+ // channel caches the host — a fresh object per call would re-fire the
43
+ // host-keyed effects below on every render (an endless render loop).
44
+ const host = channel.settingsHost();
45
+ const [namespaces, setNamespaces] = React.useState(() => host?.listNamespaces() ?? []);
46
+ const [sections, setSections] = React.useState(() => channel.settingsSections());
47
+ const [mode, setMode] = React.useState('list');
48
+ const [editing, setEditing] = React.useState(null);
49
+ const [focusIndex, setFocusIndex] = React.useState(0);
50
+ const [notice, setNotice] = React.useState(undefined);
51
+ /** Configured status of every secret field's credential ref. */
52
+ const [secrets, setSecrets] = React.useState(new Map());
53
+ /** First line of the windowed entry list (focus-follow scrolling). */
54
+ const [windowStart, setWindowStart] = React.useState(0);
55
+ /** Repaint after a form mutation — staged drafts live in the (React-free)
56
+ * SettingsForm, so editing one changes no React state by itself. */
57
+ const [, bump] = React.useReducer((count) => count + 1, 0);
58
+ /** Async save completions must not touch state after the screen closes. */
59
+ const mountedRef = React.useRef(true);
60
+ React.useEffect(() => () => {
61
+ mountedRef.current = false;
62
+ }, []);
63
+ React.useEffect(() => channel.subscribeSettingsSections(() => {
64
+ setSections(channel.settingsSections());
65
+ }), [channel]);
66
+ // One form per section namespace. A fresh namespace view replaces the form
67
+ // only while it holds no edits — replacing a dirty form would discard the
68
+ // drafts the user is still typing.
69
+ const formsRef = React.useRef(new Map());
70
+ const forms = new Map();
71
+ if (host !== undefined) {
72
+ for (const section of sections) {
73
+ const view = namespaces.find(entry => entry.ns === section.ns);
74
+ const kept = formsRef.current.get(section.ns);
75
+ const reuse = kept !== undefined && (kept.namespace === view || kept.shell().dirty);
76
+ const form = reuse ? kept : new SettingsForm(host, view, section.fields);
77
+ forms.set(section.ns, form);
78
+ }
79
+ }
80
+ formsRef.current = forms;
81
+ const refresh = () => {
82
+ setNamespaces(host?.listNamespaces() ?? []);
83
+ };
84
+ // Secret fields report only configured/unconfigured; resolve each ref once
85
+ // per section-list change (and after every save, which may have set one).
86
+ const [secretProbe, setSecretProbe] = React.useState(0);
87
+ React.useEffect(() => {
88
+ if (host === undefined)
89
+ return;
90
+ let stale = false;
91
+ const pending = sections.flatMap(section => section.fields
92
+ .filter((field) => field.secret !== undefined)
93
+ .map(async (field) => [`${section.ns}:${field.path.join('.')}`, await host.credentialConfigured(field.secret.ref)]));
94
+ void Promise.all(pending).then(entries => {
95
+ if (!stale && mountedRef.current)
96
+ setSecrets(new Map(entries));
97
+ });
98
+ return () => {
99
+ stale = true;
100
+ };
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, [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 })));
105
+ const effFocus = Math.min(focusIndex, Math.max(0, focusable.length - 1));
106
+ const focused = focusable.length === 0 ? undefined : focusable[effFocus];
107
+ const focusedForm = focused === undefined ? undefined : forms.get(focused.ns);
108
+ const commitSave = (ns) => {
109
+ const form = forms.get(ns);
110
+ if (form === undefined || form.saving || !form.shell().dirty)
111
+ return;
112
+ void form.save().then(ok => {
113
+ if (!mountedRef.current)
114
+ return;
115
+ if (ok) {
116
+ setNotice({ text: t('settings-saved', { ns }), tone: 'success' });
117
+ refresh();
118
+ setSecretProbe(count => count + 1);
119
+ }
120
+ else {
121
+ setNotice({ text: t('settings-save-failed', { ns }), tone: 'error' });
122
+ }
123
+ });
124
+ };
125
+ /** Stage the next choice for a boolean/select field (Enter cycles). */
126
+ const cycleField = (ns, field) => {
127
+ const form = forms.get(ns);
128
+ if (form === undefined || !form.available || form.saving)
129
+ return;
130
+ const current = form.field(field).text;
131
+ if (field.kind === 'boolean') {
132
+ form.edit(field, current === 'true' ? 'false' : 'true');
133
+ }
134
+ else {
135
+ const options = field.options ?? [];
136
+ if (options.length === 0)
137
+ return;
138
+ const index = options.findIndex(option => option.value === current);
139
+ form.edit(field, options[(index + 1) % options.length]?.value ?? options[0]?.value ?? '');
140
+ }
141
+ setNotice(undefined);
142
+ bump();
143
+ };
144
+ useInput((input, key) => {
145
+ if (mode === 'edit' && editing !== null) {
146
+ if (isPlainReturn(key)) {
147
+ const form = forms.get(editing.ns);
148
+ form?.edit(editing.field, editing.draft);
149
+ bump();
150
+ setMode('list');
151
+ setEditing(null);
152
+ }
153
+ else if (key.escape) {
154
+ setMode('list');
155
+ setEditing(null);
156
+ }
157
+ else if (key.backspace || key.delete) {
158
+ setEditing(state => state === null ? null : { ...state, draft: state.draft.slice(0, -1) });
159
+ }
160
+ else if (!isMod(key) && !key.meta && !key.super && input && !key.return) {
161
+ // Only real characters reach the draft (see SessionBrowser's query).
162
+ const typed = input.replace(/\p{Cc}/gu, '');
163
+ if (typed.length > 0) {
164
+ setEditing(state => state === null ? null : { ...state, draft: state.draft + typed });
165
+ }
166
+ }
167
+ return;
168
+ }
169
+ if (key.upArrow) {
170
+ setFocusIndex(Math.max(0, effFocus - 1));
171
+ }
172
+ else if (key.downArrow) {
173
+ setFocusIndex(Math.min(Math.max(0, focusable.length - 1), effFocus + 1));
174
+ }
175
+ else if (isPlainReturn(key) && focused !== undefined) {
176
+ // A save in flight owns the section's drafts; starting an edit mid-write
177
+ // is exactly the lost-draft race the staged model exists to prevent.
178
+ if (focusedForm === undefined || !focusedForm.available || focusedForm.saving)
179
+ return;
180
+ if (focused.field.kind === 'boolean' || focused.field.kind === 'select') {
181
+ cycleField(focused.ns, focused.field);
182
+ }
183
+ else {
184
+ setEditing({ ns: focused.ns, field: focused.field, draft: focusedForm.field(focused.field).text });
185
+ setMode('edit');
186
+ }
187
+ }
188
+ else if (input === 's' && focused !== undefined) {
189
+ commitSave(focused.ns);
190
+ }
191
+ else if (input === 'd' && focused !== undefined) {
192
+ if (focusedForm?.saving)
193
+ return;
194
+ focusedForm?.discard();
195
+ setNotice(undefined);
196
+ bump();
197
+ }
198
+ 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.
204
+ const dirty = [...forms.values()].filter(form => form.shell().dirty);
205
+ if (dirty.length > 0) {
206
+ if (dirty.some(form => form.saving))
207
+ return;
208
+ for (const form of dirty)
209
+ form.discard();
210
+ setNotice({ text: t('settings-discarded'), tone: 'success' });
211
+ bump();
212
+ }
213
+ else {
214
+ onClose();
215
+ }
216
+ }
217
+ });
218
+ const renderField = (section, field) => {
219
+ const ns = section.ns;
220
+ const form = forms.get(ns);
221
+ const state = form?.field(field) ?? { text: '', overridden: false, invalid: false };
222
+ const isFocused = focused !== undefined && focused.ns === ns && focused.field === field;
223
+ const isEditing = isFocused && mode === 'edit' && editing !== null;
224
+ const label = pick(field.label, field.descriptions);
225
+ const hint = field.hint !== undefined ? pick(field.hint, field.hintDescriptions) : undefined;
226
+ let value;
227
+ if (field.secret !== undefined) {
228
+ const configured = secrets.get(`${ns}:${field.path.join('.')}`) === true;
229
+ if (isEditing) {
230
+ value = '•'.repeat(editing?.draft.length ?? 0) + '▌';
231
+ }
232
+ else if (state.text !== '') {
233
+ value = `${'•'.repeat(state.text.length)} ${t('settings-secret-staged')}`;
234
+ }
235
+ else {
236
+ value = configured ? t('settings-secret-set') : t('settings-secret-unset');
237
+ }
238
+ }
239
+ else if (isEditing) {
240
+ value = `${editing?.draft ?? ''}▌`;
241
+ }
242
+ else if (state.text === '') {
243
+ value = t('settings-field-empty');
244
+ }
245
+ else {
246
+ value = state.text;
247
+ }
248
+ 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
+ };
250
+ // ── 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. ────
252
+ const entries = [];
253
+ 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();
258
+ 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'), "]"] })] })] })),
262
+ });
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;
268
+ entries.push({
269
+ key: `field:${section.ns}:${field.path.join('.')}`,
270
+ lines: hasHint ? 2 : 1,
271
+ focus: index,
272
+ node: renderField(section, field),
273
+ });
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') })] })),
283
+ });
284
+ for (const entry of readonlyNamespaces) {
285
+ entries.push({
286
+ key: `readonly:${entry.ns}`,
287
+ 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)] })] })),
289
+ });
290
+ }
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
+ }
304
+ // Focus-follow window: keep the focused entry fully inside the viewport.
305
+ let totalLines = 0;
306
+ let focusedOffset = 0;
307
+ let focusedLines = 1;
308
+ for (const entry of entries) {
309
+ if (entry.focus === effFocus) {
310
+ focusedOffset = totalLines;
311
+ focusedLines = entry.lines;
312
+ }
313
+ totalLines += entry.lines;
314
+ }
315
+ // Chrome: title row, top rule, bottom rule, hint row, plus the notice.
316
+ const viewport = Math.max(1, rows - 4 - (notice === undefined ? 0 : 1));
317
+ React.useEffect(() => {
318
+ setWindowStart(start => {
319
+ if (focusedOffset < start)
320
+ return focusedOffset;
321
+ if (focusedOffset + focusedLines > start + viewport)
322
+ return focusedOffset + focusedLines - viewport;
323
+ return start;
324
+ });
325
+ }, [focusedOffset, focusedLines, viewport]);
326
+ let entryOffset = 0;
327
+ const visible = entries.filter(entry => {
328
+ const start = entryOffset;
329
+ entryOffset += entry.lines;
330
+ return start >= windowStart && start + entry.lines <= windowStart + viewport;
331
+ });
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') }) })] }));
333
+ }
@@ -105,7 +105,7 @@ export function StatusLine({ channel, selectionActive = false, helpOpen = false,
105
105
  if (channel.contextBarEnabled && barWidth >= 14 && channel.contextWindow !== undefined) {
106
106
  bar = renderContextBar(channel.contextSegments, usage !== undefined ? usage.input + usage.cacheRead + usage.cacheWrite : 0, channel.contextWindow, barWidth, barColors);
107
107
  }
108
- return (_jsx(Box, { paddingX: 2, 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] })] }) }));
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] })] }) }));
109
109
  }
110
110
  function basename(path) {
111
111
  const parts = path.split(/[\\/]/);
@@ -0,0 +1,3 @@
1
+ export * from './dsh-adapter/settings-sections.js';
2
+ export { default } from './dsh-adapter/settings-sections.js';
3
+ //# sourceMappingURL=settings-sections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-sections.d.ts","sourceRoot":"","sources":["../../src/settings-sections.ts"],"names":[],"mappings":"AAEA,cAAc,oCAAoC,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAA"}
@@ -0,0 +1,4 @@
1
+ // Re-export shim: the Cordis-backed implementation lives behind the adapter
2
+ // boundary so UI consumers never import official @deepseek-ai/* packages.
3
+ export * from './dsh-adapter/settings-sections.js';
4
+ export { default } from './dsh-adapter/settings-sections.js';
@@ -47,6 +47,18 @@ export type Theme = {
47
47
  diffRemovedDimmed: string;
48
48
  diffAddedWord: string;
49
49
  diffRemovedWord: string;
50
+ toolCardBackground: string;
51
+ toolCardBackgroundDim: string;
52
+ syntaxKeyword: string;
53
+ syntaxString: string;
54
+ syntaxComment: string;
55
+ syntaxNumber: string;
56
+ syntaxFunction: string;
57
+ syntaxType: string;
58
+ syntaxVariable: string;
59
+ syntaxOperator: string;
60
+ syntaxPunctuation: string;
61
+ syntaxConstant: string;
50
62
  red_FOR_SUBAGENTS_ONLY: string;
51
63
  blue_FOR_SUBAGENTS_ONLY: string;
52
64
  green_FOR_SUBAGENTS_ONLY: string;
@@ -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":"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"}