@deepseek-harness-tui/dsh-tui 0.7.1 → 0.7.2

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 (84) hide show
  1. package/README.md +6 -4
  2. package/bin/dsh-tui.js +37 -1
  3. package/cordis.patch.yml +10 -0
  4. package/lib/types/commands.js +1 -1
  5. package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
  6. package/lib/types/components/LoadedContextPanel.js +1 -1
  7. package/lib/types/components/design-system/ThemedText.d.ts +2 -2
  8. package/lib/types/components/design-system/ThemedText.d.ts.map +1 -1
  9. package/lib/types/components/design-system/ThemedText.js +1 -3
  10. package/lib/types/components/sessions/SessionListRow.d.ts +26 -0
  11. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -0
  12. package/lib/types/components/sessions/SessionListRow.js +35 -0
  13. package/lib/types/components/sessions/SessionPreview.d.ts +25 -0
  14. package/lib/types/components/sessions/SessionPreview.d.ts.map +1 -0
  15. package/lib/types/components/sessions/SessionPreview.js +60 -0
  16. package/lib/types/dsh-adapter/channel.d.ts +9 -4
  17. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  18. package/lib/types/dsh-adapter/channel.js +31 -73
  19. package/lib/types/dsh-adapter/compat/index.d.ts +1 -1
  20. package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
  21. package/lib/types/dsh-adapter/compat/index.js +1 -1
  22. package/lib/types/dsh-adapter/compat/sessionLog.d.ts +8 -0
  23. package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
  24. package/lib/types/dsh-adapter/compat/sessionLog.js +1 -1
  25. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  26. package/lib/types/dsh-adapter/index.js +10 -1
  27. package/lib/types/dsh-adapter/packaged-presets.d.ts +22 -0
  28. package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -0
  29. package/lib/types/dsh-adapter/packaged-presets.js +90 -0
  30. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  31. package/lib/types/dsh-adapter/plugin.js +46 -4
  32. package/lib/types/dsh-adapter/presets.d.ts +16 -0
  33. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  34. package/lib/types/dsh-adapter/presets.js +23 -0
  35. package/lib/types/dsh-adapter/sessions/digest.d.ts +29 -0
  36. package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -0
  37. package/lib/types/dsh-adapter/sessions/digest.js +224 -0
  38. package/lib/types/dsh-adapter/sessions/frames.d.ts +100 -0
  39. package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -0
  40. package/lib/types/dsh-adapter/sessions/frames.js +246 -0
  41. package/lib/types/dsh-adapter/sessions/header.d.ts +50 -0
  42. package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -0
  43. package/lib/types/dsh-adapter/sessions/header.js +62 -0
  44. package/lib/types/dsh-adapter/sessions/index.d.ts +17 -0
  45. package/lib/types/dsh-adapter/sessions/index.d.ts.map +1 -0
  46. package/lib/types/dsh-adapter/sessions/index.js +15 -0
  47. package/lib/types/dsh-adapter/sessions/list.d.ts +41 -0
  48. package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -0
  49. package/lib/types/dsh-adapter/sessions/list.js +213 -0
  50. package/lib/types/dsh-adapter/sessions/store.d.ts +48 -0
  51. package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -0
  52. package/lib/types/dsh-adapter/sessions/store.js +164 -0
  53. package/lib/types/dsh-adapter/sessions/types.d.ts +142 -0
  54. package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -0
  55. package/lib/types/dsh-adapter/sessions/types.js +14 -0
  56. package/lib/types/i18n.d.ts +118 -14
  57. package/lib/types/i18n.d.ts.map +1 -1
  58. package/lib/types/i18n.js +36 -6
  59. package/lib/types/screens/Chat.d.ts +3 -2
  60. package/lib/types/screens/Chat.d.ts.map +1 -1
  61. package/lib/types/screens/Chat.js +28 -144
  62. package/lib/types/screens/SessionBrowser.d.ts +37 -0
  63. package/lib/types/screens/SessionBrowser.d.ts.map +1 -0
  64. package/lib/types/screens/SessionBrowser.js +437 -0
  65. package/lib/types/sessionHistory.d.ts +0 -8
  66. package/lib/types/sessionHistory.d.ts.map +1 -1
  67. package/lib/types/sessions/format.d.ts +117 -0
  68. package/lib/types/sessions/format.d.ts.map +1 -0
  69. package/lib/types/sessions/format.js +244 -0
  70. package/lib/types/sessions/view.d.ts +125 -0
  71. package/lib/types/sessions/view.d.ts.map +1 -0
  72. package/lib/types/sessions/view.js +222 -0
  73. package/package.json +6 -2
  74. package/presets/liangshen/.dsh-tui-managed.json +5 -0
  75. package/presets/liangshen/agent.cordis.yml +389 -0
  76. package/presets/liangshen/compaction-epoch.mjs +81 -0
  77. package/presets/liangshen/custom-bash.mjs +126 -0
  78. package/presets/liangshen/instruction-hint.mjs +181 -0
  79. package/presets/liangshen/preset.yml +3 -0
  80. package/presets/liangshen/skill-search.mjs +142 -0
  81. package/presets/liangshen/tool-bootstrap.mjs +300 -0
  82. package/lib/types/components/ResumePicker.d.ts +0 -26
  83. package/lib/types/components/ResumePicker.d.ts.map +0 -1
  84. package/lib/types/components/ResumePicker.js +0 -50
@@ -5,6 +5,8 @@ import { AlternateScreen, Box, Text, useInput, ScrollBox, useTheme, useTerminalS
5
5
  import { POINTER } from '../cc/figures.js';
6
6
  import { isMod, isPlainReturnInput, modLabel } from '../utils/modifiers.js';
7
7
  import { formatTokens } from '../cc/format.js';
8
+ import { homeDir } from '../utils/paths.js';
9
+ import { sessionCwdMatches } from '../dsh-adapter/channel.js';
8
10
  import { runProviderWizard } from '../dsh-adapter/providerWizard.js';
9
11
  import { ApprovalStore } from '../dsh-adapter/approvals.js';
10
12
  import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
@@ -24,7 +26,7 @@ import { StatusLine } from './StatusLine.js';
24
26
  import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
25
27
  import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
26
28
  import { ModelPicker } from '../components/ModelPicker.js';
27
- import { ResumePicker } from '../components/ResumePicker.js';
29
+ import { SessionBrowser } from './SessionBrowser.js';
28
30
  import { WorkspacePicker } from '../components/WorkspacePicker.js';
29
31
  import { WorkspaceFlowPicker } from '../components/WorkspaceFlowPicker.js';
30
32
  import { ActivityPicker } from '../components/ActivityPicker.js';
@@ -150,12 +152,9 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
150
152
  const [modelPickerOpen, setModelPickerOpen] = React.useState(false);
151
153
  const [models, setModels] = React.useState([]);
152
154
  const [modelIndex, setModelIndex] = React.useState(0);
153
- const [resumePickerOpen, setResumePickerOpen] = React.useState(false);
154
- const [resumeSessions, setResumeSessions] = React.useState([]);
155
- const [resumeIndex, setResumeIndex] = React.useState(0);
156
- /** `/resume` session management (issue #112): plain selection, a delete
157
- * confirmation (ctrl+d), or the inline rename input (ctrl+r). */
158
- const [resumeMode, setResumeMode] = React.useState('list');
155
+ /** `/resume` opens the session browser, a screen rather than a panel. It
156
+ * owns its own selection, filters and keyboard — Chat only opens it. */
157
+ const [browserOpen, setBrowserOpen] = React.useState(false);
159
158
  const [workspacePickerOpen, setWorkspacePickerOpen] = React.useState(false);
160
159
  const [workspaceTargets, setWorkspaceTargets] = React.useState([]);
161
160
  const [workspaceIndex, setWorkspaceIndex] = React.useState(0);
@@ -165,7 +164,6 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
165
164
  const [workspaceFlowInput, setWorkspaceFlowInput] = React.useState(null);
166
165
  const workspaceFlowRequestRef = React.useRef(0);
167
166
  const workspaceFlowAbortRef = React.useRef(null);
168
- const [resumeRenameText, setResumeRenameText] = React.useState('');
169
167
  /** `/activity` indicator picker (pi extension's interactive select). */
170
168
  const [activityPickerOpen, setActivityPickerOpen] = React.useState(false);
171
169
  const [activityIndex, setActivityIndex] = React.useState(0);
@@ -663,22 +661,10 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
663
661
  }
664
662
  case 'resume': {
665
663
  setHelpOpen(false);
666
- void (async () => {
667
- const sessions = await channel.listSessions();
668
- // The current session cannot be resumed into itself (agents.resume
669
- // rejects a live session), so it is excluded from the picker —
670
- // otherwise the fresh empty session of this launch always tops the
671
- // list as an unopenable row.
672
- const pickable = sessions.filter(session => session.id !== channel.agentId);
673
- setResumeSessions(pickable);
674
- if (pickable.length === 0) {
675
- channel.notify(t('resume-none-in-cwd'));
676
- return;
677
- }
678
- setResumeMode('list');
679
- setResumePickerOpen(true);
680
- setResumeIndex(0);
681
- })();
664
+ // The browser opens immediately and loads its own list. Waiting for
665
+ // the listing here would make `/resume` feel slower the more history
666
+ // a project has, which is exactly backwards.
667
+ setBrowserOpen(true);
682
668
  return true;
683
669
  }
684
670
  case 'workspace': {
@@ -1182,6 +1168,11 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1182
1168
  // not block the event from reaching the panel.
1183
1169
  if (btw !== null)
1184
1170
  return;
1171
+ // Same for the session browser: it renders instead of the conversation,
1172
+ // so every key belongs to it — including the plain letters that drive its
1173
+ // search box, which Chat would otherwise route into the prompt.
1174
+ if (browserOpen)
1175
+ return;
1185
1176
  // The questionnaire / approval panel owns the keyboard while one is
1186
1177
  // pending (the panel's own useInput handles ↑/↓/Space/Tab/Enter/Esc;
1187
1178
  // the prompt input is unmounted, so nothing else should see these keys).
@@ -1407,123 +1398,6 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1407
1398
  }
1408
1399
  return;
1409
1400
  }
1410
- if (resumePickerOpen) {
1411
- const resumeSession = resumeSessions[resumeIndex];
1412
- // Session management modes (issue #112): the list keys stay untouched
1413
- // until ctrl+d/ctrl+r switch into a sub-mode, each with Enter/Esc.
1414
- if (resumeMode === 'confirm-delete') {
1415
- if (plainReturn) {
1416
- setResumeMode('list');
1417
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
1418
- if (resumeSession) {
1419
- const target = resumeSession;
1420
- void (async () => {
1421
- const ok = await channel.deleteSession(target.id);
1422
- if (!ok) {
1423
- channel.notify(t('resume-delete-failed', { name: target.title || target.id }), { color: 'error' });
1424
- return;
1425
- }
1426
- channel.notify(t('resume-deleted', { name: target.title || target.id }));
1427
- // Refresh right away so the row disappears in place; closing
1428
- // the picker when nothing resumable remains.
1429
- const sessions = await channel.listSessions();
1430
- const pickable = sessions.filter(session => session.id !== channel.agentId);
1431
- setResumeSessions(pickable);
1432
- if (pickable.length === 0) {
1433
- setResumePickerOpen(false);
1434
- }
1435
- else {
1436
- setResumeIndex(index => Math.min(index, pickable.length - 1));
1437
- }
1438
- })();
1439
- }
1440
- }
1441
- else if (key.escape) {
1442
- setResumeMode('list');
1443
- }
1444
- return;
1445
- }
1446
- if (resumeMode === 'rename') {
1447
- if (plainReturn) {
1448
- setResumeMode('list');
1449
- const title = resumeRenameText.trim();
1450
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
1451
- if (resumeSession && title.length > 0) {
1452
- const target = resumeSession;
1453
- void (async () => {
1454
- const ok = await channel.renameSessionTo(target.id, title);
1455
- if (!ok) {
1456
- channel.notify(t('resume-rename-failed', { name: target.title || target.id }), { color: 'error' });
1457
- return;
1458
- }
1459
- channel.notify(t('rename-done', { title }));
1460
- // Re-list so the row reflects the persisted state, but patch
1461
- // the renamed row's title explicitly: listSessions resolves
1462
- // persisted titles only within the MRU top SESSION_TITLE_DEPTH
1463
- // window, and a freshly renamed row must never snap back to
1464
- // the basename fallback in between.
1465
- const sessions = await channel.listSessions();
1466
- const next = sessions
1467
- .filter(session => session.id !== channel.agentId)
1468
- .map(session => (session.id === target.id ? { ...session, title } : session));
1469
- setResumeSessions(next);
1470
- // Re-anchor focus on the renamed row: renameSessionTo touches
1471
- // MRU, so the re-listed order shifts — a kept index would
1472
- // silently point at a DIFFERENT session, and a following
1473
- // Enter/ctrl+d would act on the wrong one (review leftover).
1474
- const anchored = next.findIndex(session => session.id === target.id);
1475
- if (anchored >= 0)
1476
- setResumeIndex(anchored);
1477
- })();
1478
- }
1479
- }
1480
- else if (key.escape) {
1481
- setResumeMode('list');
1482
- }
1483
- else if (key.backspace) {
1484
- setResumeRenameText(text => text.slice(0, -1));
1485
- }
1486
- else if (!key.ctrl && !key.meta && !key.super && input) {
1487
- // Single-line title: pasted newlines collapse to spaces.
1488
- setResumeRenameText(text => text + input.replace(/[\r\n]+/g, ' '));
1489
- }
1490
- return;
1491
- }
1492
- if (key.upArrow) {
1493
- setResumeIndex(index => (index <= 0 ? resumeSessions.length - 1 : index - 1));
1494
- }
1495
- else if (key.downArrow) {
1496
- setResumeIndex(index => (index >= resumeSessions.length - 1 ? 0 : index + 1));
1497
- }
1498
- else if (plainReturn) {
1499
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
1500
- if (resumeSession) {
1501
- // Enter switches the live agent to the persisted session right
1502
- // away (the history replays into the transcript); the resume.txt
1503
- // launcher marker is refreshed by resumeTo so `--resume` on the
1504
- // next launch opens the same session.
1505
- setResumePickerOpen(false);
1506
- void channel.resumeTo(resumeSession.id).then((ok) => {
1507
- if (ok)
1508
- channel.notify(t('resume-resumed'));
1509
- });
1510
- }
1511
- else {
1512
- setResumePickerOpen(false);
1513
- }
1514
- }
1515
- else if (key.escape) {
1516
- setResumePickerOpen(false);
1517
- }
1518
- else if (isMod(key) && input === 'd' && resumeSession) {
1519
- setResumeMode('confirm-delete');
1520
- }
1521
- else if (isMod(key) && input === 'r' && resumeSession) {
1522
- setResumeRenameText(resumeSession.title || '');
1523
- setResumeMode('rename');
1524
- }
1525
- return;
1526
- }
1527
1401
  if (modelPickerOpen) {
1528
1402
  if (key.upArrow) {
1529
1403
  setModelIndex(index => (index <= 0 ? models.length - 1 : index - 1));
@@ -1815,8 +1689,18 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1815
1689
  // Working-activity line (spinner slot): context-pressure prefix shares the
1816
1690
  // StatusLine thresholds (amber ≥ 80, red ≥ 95).
1817
1691
  const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
1692
+ // The browser is a screen, not an overlay: it REPLACES the conversation
1693
+ // rather than floating above it. Rendering it as an early return (after
1694
+ // every hook above has run) is what makes that literal — there is no
1695
+ // transcript underneath to be repainted, scrolled, or bled through.
1696
+ if (browserOpen) {
1697
+ const browser = (_jsx(SessionBrowser, { channel: channel, home: homeDir(), sameProject: sessionCwdMatches, onClose: () => setBrowserOpen(false) }));
1698
+ // Inline hosts enter the alternate screen for the duration; full-screen
1699
+ // hosts are already in it and must not nest a second one.
1700
+ return fullscreen ? browser : _jsx(AlternateScreen, { children: browser });
1701
+ }
1818
1702
  /** Prompt input is inert while a modal dialog owns the keyboard. */
1819
- const promptSelectionActive = selectionActive || modelPickerOpen || resumePickerOpen || workspacePickerOpen || workspaceFlow !== null || activityPickerOpen ||
1703
+ const promptSelectionActive = selectionActive || modelPickerOpen || workspacePickerOpen || workspaceFlow !== null || activityPickerOpen ||
1820
1704
  effortSliderOpen || presetPickerOpen || themePickerOpen || thinkingOpen || historyOpen || rewindOpen || searchOpen ||
1821
1705
  btw !== null;
1822
1706
  // The trajectory scene replaces the conversation for as long as it is open.
@@ -1837,7 +1721,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1837
1721
  // 常驻、只移除其普通子节点,blit 解毒不触发,被覆盖的转录行会在
1838
1722
  // blit-skip 后留空(Esc 关 picker 一片空白的根因)。
1839
1723
  const dialogOverlayOpen = thinkingOpen || (workspacePickerOpen && workspaceTargets.length > 0) || workspaceFlow !== null ||
1840
- (resumePickerOpen && resumeSessions.length > 0) || modelPickerOpen ||
1724
+ modelPickerOpen ||
1841
1725
  activityPickerOpen || (effortSliderOpen && effortOptions.length > 1) ||
1842
1726
  (presetPickerOpen && presetOptions.length > 0) || themePickerOpen || historyOpen ||
1843
1727
  rewindOpen || searchOpen;
@@ -1862,7 +1746,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
1862
1746
  band: wakeBand,
1863
1747
  hint: trajectorySeen ? undefined : `${modLabel}t`,
1864
1748
  tick: Math.floor(wakeTime / 120),
1865
- } }), 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 }) })), resumePickerOpen && resumeSessions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ResumePicker, { sessions: resumeSessions, focusIndex: resumeIndex, currentSessionId: channel.agentId, mode: resumeMode, renameText: resumeRenameText }) })), 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 })] }))] })] }));
1749
+ } }), 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 })] }))] })] }));
1866
1750
  }
1867
1751
  /**
1868
1752
  * The pinned prompt header shown above the ScrollBox while the user has
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import type { Channel } from '../dsh-adapter/channel.js';
3
+ /**
4
+ * The session browser — `/resume` as a screen of its own.
5
+ *
6
+ * The old picker was a panel of eight titles and a timestamp, and the reason
7
+ * it could not be more than that was never layout: the data behind it was five
8
+ * fields wide. With every session now arriving classified, sized, dated and
9
+ * attributed, the surface that shows them can do the job a person actually
10
+ * came for — find one conversation among many.
11
+ *
12
+ * What that means concretely:
13
+ *
14
+ * - Search is always live. There is no mode to enter; typing filters, because
15
+ * the list is the search results.
16
+ * - Delegated sub-agent runs are folded away by default and revealed under
17
+ * their parents on demand. They are not noise to be deleted — they are the
18
+ * model's own work, and it is worth being able to open one — but they are
19
+ * not what "resume a conversation" means, and there are five of them for
20
+ * every conversation.
21
+ * - Sessions that hold no conversation are never listed, only counted, with
22
+ * one action to clear them.
23
+ * - The preview shows the end of a session, so "is this the one I was in the
24
+ * middle of" is answerable without resuming it.
25
+ *
26
+ * Every one of those reads bounded data, so the screen behaves the same on a
27
+ * fifty-session history as on a five-session one.
28
+ */
29
+ export declare function SessionBrowser({ channel, home, sameProject, onClose, }: {
30
+ channel: Channel;
31
+ /** Home directory, for collapsing project paths to `~`. */
32
+ home: string;
33
+ /** Whether a stored cwd belongs to the same project as the live session. */
34
+ sameProject: (a: string, b: string) => boolean;
35
+ onClose: () => void;
36
+ }): React.ReactNode;
37
+ //# sourceMappingURL=SessionBrowser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionBrowser.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAsBzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAoDxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,WAAW,EACX,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAydlB"}