@deepseek-harness-tui/dsh-tui 0.7.2 → 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 (119) hide show
  1. package/README.md +3 -3
  2. package/bin/dsh-tui.js +24 -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/GoalTodoPanel.d.ts.map +1 -1
  8. package/lib/types/components/GoalTodoPanel.js +6 -1
  9. package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
  10. package/lib/types/components/HistorySearchDialog.js +5 -1
  11. package/lib/types/components/MessageList.d.ts +3 -1
  12. package/lib/types/components/MessageList.d.ts.map +1 -1
  13. package/lib/types/components/MessageList.js +9 -5
  14. package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
  15. package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
  16. package/lib/types/components/PluginSceneBoundary.js +29 -0
  17. package/lib/types/components/PromptInput.d.ts.map +1 -1
  18. package/lib/types/components/PromptInput.js +52 -74
  19. package/lib/types/components/SearchBox.d.ts +4 -0
  20. package/lib/types/components/SearchBox.d.ts.map +1 -1
  21. package/lib/types/components/SearchBox.js +101 -2
  22. package/lib/types/components/SkillsPicker.d.ts +18 -0
  23. package/lib/types/components/SkillsPicker.d.ts.map +1 -0
  24. package/lib/types/components/SkillsPicker.js +50 -0
  25. package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +4 -1
  26. package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -1
  27. package/lib/types/components/Spinner/SpinnerAnimationRow.js +10 -5
  28. package/lib/types/components/SplitDiffView.d.ts +28 -0
  29. package/lib/types/components/SplitDiffView.d.ts.map +1 -0
  30. package/lib/types/components/SplitDiffView.js +417 -0
  31. package/lib/types/components/WorkingSpinner.d.ts +3 -1
  32. package/lib/types/components/WorkingSpinner.d.ts.map +1 -1
  33. package/lib/types/components/WorkingSpinner.js +2 -2
  34. package/lib/types/components/design-system/ThemedText.d.ts +7 -1
  35. package/lib/types/components/design-system/ThemedText.d.ts.map +1 -1
  36. package/lib/types/components/design-system/ThemedText.js +2 -2
  37. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +3 -1
  38. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  39. package/lib/types/components/messages/AssistantToolUseMessage.js +17 -7
  40. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  41. package/lib/types/components/questions/AskUserQuestionPanel.js +52 -8
  42. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  43. package/lib/types/components/questions/PlanReviewPanel.js +12 -3
  44. package/lib/types/dsh-adapter/channel.d.ts +78 -0
  45. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  46. package/lib/types/dsh-adapter/channel.js +165 -14
  47. package/lib/types/dsh-adapter/contract.d.ts +1 -1
  48. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  49. package/lib/types/dsh-adapter/contract.js +1 -0
  50. package/lib/types/dsh-adapter/index.d.ts +4 -0
  51. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  52. package/lib/types/dsh-adapter/index.js +1 -0
  53. package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/packaged-presets.js +5 -2
  55. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  56. package/lib/types/dsh-adapter/plugin.js +66 -3
  57. package/lib/types/dsh-adapter/promptDebug.d.ts +10 -0
  58. package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
  59. package/lib/types/dsh-adapter/promptDebug.js +148 -0
  60. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/providerWizard.js +3 -1
  62. package/lib/types/dsh-adapter/scenes.d.ts +75 -0
  63. package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
  64. package/lib/types/dsh-adapter/scenes.js +78 -0
  65. package/lib/types/dsh-adapter/settings-sections.d.ts +116 -0
  66. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
  67. package/lib/types/dsh-adapter/settings-sections.js +63 -0
  68. package/lib/types/dsh-adapter/settingsEditor.d.ts +128 -0
  69. package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
  70. package/lib/types/dsh-adapter/settingsEditor.js +244 -0
  71. package/lib/types/i18n.d.ts +169 -18
  72. package/lib/types/i18n.d.ts.map +1 -1
  73. package/lib/types/i18n.js +45 -6
  74. package/lib/types/ink/components/Text.d.ts +9 -1
  75. package/lib/types/ink/components/Text.d.ts.map +1 -1
  76. package/lib/types/ink/components/Text.js +7 -6
  77. package/lib/types/ink/ink.d.ts.map +1 -1
  78. package/lib/types/ink/ink.js +4 -2
  79. package/lib/types/ink/log-update.d.ts.map +1 -1
  80. package/lib/types/ink/log-update.js +76 -9
  81. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  82. package/lib/types/ink/parse-keypress.js +4 -1
  83. package/lib/types/ink/terminal.d.ts +40 -4
  84. package/lib/types/ink/terminal.d.ts.map +1 -1
  85. package/lib/types/ink/terminal.js +57 -5
  86. package/lib/types/ink/termio/tokenize.d.ts +6 -0
  87. package/lib/types/ink/termio/tokenize.d.ts.map +1 -1
  88. package/lib/types/ink/termio/tokenize.js +18 -4
  89. package/lib/types/jsx-runtime.d.ts +2 -0
  90. package/lib/types/jsx-runtime.d.ts.map +1 -0
  91. package/lib/types/jsx-runtime.js +13 -0
  92. package/lib/types/modelRoute.d.ts +10 -2
  93. package/lib/types/modelRoute.d.ts.map +1 -1
  94. package/lib/types/modelRoute.js +33 -4
  95. package/lib/types/scenes.d.ts +3 -0
  96. package/lib/types/scenes.d.ts.map +1 -0
  97. package/lib/types/scenes.js +4 -0
  98. package/lib/types/screens/Chat.d.ts.map +1 -1
  99. package/lib/types/screens/Chat.js +182 -26
  100. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  101. package/lib/types/screens/SessionBrowser.js +30 -15
  102. package/lib/types/screens/Settings.d.ts +20 -0
  103. package/lib/types/screens/Settings.d.ts.map +1 -0
  104. package/lib/types/screens/Settings.js +333 -0
  105. package/lib/types/screens/StatusLine.js +1 -1
  106. package/lib/types/sessions/view.d.ts.map +1 -1
  107. package/lib/types/sessions/view.js +13 -1
  108. package/lib/types/settings-sections.d.ts +3 -0
  109. package/lib/types/settings-sections.d.ts.map +1 -0
  110. package/lib/types/settings-sections.js +4 -0
  111. package/lib/types/theme.d.ts +12 -0
  112. package/lib/types/theme.d.ts.map +1 -1
  113. package/lib/types/theme.js +36 -0
  114. package/lib/types/update.d.ts +21 -7
  115. package/lib/types/update.d.ts.map +1 -1
  116. package/lib/types/update.js +66 -16
  117. package/lib/types/utils/paths.d.ts.map +1 -1
  118. package/lib/types/utils/paths.js +4 -1
  119. package/package.json +49 -9
@@ -103,6 +103,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
103
103
  controllerRef.current = {
104
104
  hasText: () => value.length > 0,
105
105
  clear: () => {
106
+ valueRef.current = '';
107
+ cursorRef.current = 0;
106
108
  setValue('');
107
109
  setCursor(0);
108
110
  },
@@ -120,6 +122,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
120
122
  React.useEffect(() => {
121
123
  if (fillText && fillText !== lastFill.current) {
122
124
  lastFill.current = fillText;
125
+ valueRef.current = fillText;
126
+ cursorRef.current = fillText.length;
123
127
  setValue(fillText);
124
128
  setCursor(fillText.length);
125
129
  onFillConsumed?.();
@@ -132,16 +136,6 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
132
136
  const clipboardBusyRef = React.useRef(false);
133
137
  /** True while the external editor owns the terminal (Ctrl+X round-trip). */
134
138
  const editorBusyRef = React.useRef(false);
135
- /**
136
- * Latest committed input state. The Ctrl+V clipboard read resolves
137
- * asynchronously; inserting against the render that STARTED the read
138
- * would drop whatever the user typed while waiting. The continuation
139
- * inserts against this mirror instead.
140
- */
141
- const liveInputRef = React.useRef({ value: '', cursor: 0 });
142
- React.useEffect(() => {
143
- liveInputRef.current = { value, cursor };
144
- });
145
139
  /** Enter dedupe window: cmd pipelines can deliver one Enter as `\r`+`\n`. */
146
140
  const lastEnterAtRef = React.useRef(0);
147
141
  React.useEffect(() => {
@@ -193,6 +187,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
193
187
  !helpOpen &&
194
188
  !selectionActive &&
195
189
  fileEscRef.current !== mention?.start;
190
+ const setInput = (next, cursorOffset = next.length) => {
191
+ valueRef.current = next;
192
+ cursorRef.current = Math.max(0, Math.min(cursorOffset, next.length));
193
+ setValue(next);
194
+ setCursor(cursorRef.current);
195
+ };
196
196
  /**
197
197
  * Accept the selected file suggestion: replace ONLY the mention token at
198
198
  * the caret (prefix/suffix text survives), quoting whitespace paths. A
@@ -216,8 +216,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
216
216
  if (history.current.length > HISTORY_LIMIT)
217
217
  history.current.shift();
218
218
  historyIndex.current = -1;
219
- setValue('');
220
- setCursor(0);
219
+ setInput('', 0);
221
220
  setSelectedCommand(0);
222
221
  appendHistory(trimmed);
223
222
  channel.submit(trimmed);
@@ -243,8 +242,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
243
242
  if (history.current.length > HISTORY_LIMIT)
244
243
  history.current.shift();
245
244
  historyIndex.current = -1;
246
- setValue('');
247
- setCursor(0);
245
+ setInput('', 0);
248
246
  setSelectedCommand(0);
249
247
  appendHistory(trimmed);
250
248
  channel.steer(trimmed);
@@ -262,8 +260,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
262
260
  if (history.current.length > HISTORY_LIMIT)
263
261
  history.current.shift();
264
262
  historyIndex.current = -1;
265
- setValue('');
266
- setCursor(0);
263
+ setInput('', 0);
267
264
  setSelectedCommand(0);
268
265
  appendHistory(trimmed);
269
266
  channel.submit(trimmed);
@@ -282,8 +279,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
282
279
  channel.notify(t('input-cannot-retract'), { color: 'warning', timeoutMs: 2500 });
283
280
  return;
284
281
  }
285
- setValue(item.text);
286
- setCursor(item.text.length);
282
+ setInput(item.text);
287
283
  setSelectedCommand(0);
288
284
  setFileSelected(0);
289
285
  channel.notify(t('input-retracted'), { timeoutMs: 2000 });
@@ -308,8 +304,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
308
304
  if (history.current.length > HISTORY_LIMIT)
309
305
  history.current.shift();
310
306
  historyIndex.current = -1;
311
- setValue('');
312
- setCursor(0);
307
+ setInput('', 0);
313
308
  setSelectedCommand(0);
314
309
  setFileSelected(0);
315
310
  appendHistory(trimmed);
@@ -336,19 +331,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
336
331
  if (history.current.length > HISTORY_LIMIT)
337
332
  history.current.shift();
338
333
  historyIndex.current = -1;
339
- setValue('');
340
- setCursor(0);
334
+ setInput('', 0);
341
335
  setSelectedCommand(0);
342
336
  appendHistory(text.trim());
343
337
  }
344
338
  return handled;
345
339
  };
346
- const setInput = (next, cursorOffset = next.length) => {
347
- valueRef.current = next;
348
- cursorRef.current = Math.max(0, Math.min(cursorOffset, next.length));
349
- setValue(next);
350
- setCursor(cursorRef.current);
351
- };
352
340
  /** Clipboard reads are asynchronous; insert against the latest render so
353
341
  * typing while PowerShell owns the clipboard never gets overwritten. */
354
342
  const insertClipboardAtCaret = (text) => {
@@ -359,13 +347,13 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
359
347
  setFileSelected(0);
360
348
  };
361
349
  /** Line index of the cursor; -1 when the cursor is at the very end. */
362
- const cursorLine = () => {
363
- const before = value.slice(0, cursor);
350
+ const cursorLine = (text, cursorOffset) => {
351
+ const before = text.slice(0, cursorOffset);
364
352
  return before.split('\n').length - 1;
365
353
  };
366
354
  /** Column of the cursor within its line. */
367
- const cursorColumn = () => {
368
- const before = value.slice(0, cursor);
355
+ const cursorColumn = (text, cursorOffset) => {
356
+ const before = text.slice(0, cursorOffset);
369
357
  const line = before.split('\n').pop() ?? '';
370
358
  return line.length;
371
359
  };
@@ -377,13 +365,17 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
377
365
  // setValue can never overwrite fresh typing (and vice versa).
378
366
  if (editorBusyRef.current)
379
367
  return;
368
+ // App deliberately dispatches every parsed key from one stdin read in a
369
+ // single React update. Read the synchronous mirrors so each event sees
370
+ // the text/caret produced by the preceding event in that batch.
371
+ const value = valueRef.current;
372
+ const cursor = cursorRef.current;
380
373
  /** Insert text at the caret (typing, paste) and dismiss overlays. */
381
374
  const insertAtCaret = (text) => {
382
375
  if (helpOpen)
383
376
  onToggleHelp();
384
377
  const next = value.slice(0, cursor) + text + value.slice(cursor);
385
- setValue(next);
386
- setCursor(cursor + text.length);
378
+ setInput(next, cursor + text.length);
387
379
  setSelectedCommand(0);
388
380
  setFileSelected(0);
389
381
  };
@@ -441,10 +433,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
441
433
  // Insert against the LIVE input state: the read above resolved
442
434
  // asynchronously and the user may have typed while waiting.
443
435
  const text = formatClipboardInsert(content);
444
- const live = liveInputRef.current;
445
- const caret = Math.min(live.cursor, live.value.length);
446
- setValue(live.value.slice(0, caret) + text + live.value.slice(caret));
447
- setCursor(caret + text.length);
436
+ insertClipboardAtCaret(text);
448
437
  })
449
438
  .catch(() => {
450
439
  channel.notify(t('input-clipboard-read-failed'), { color: 'warning' });
@@ -469,8 +458,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
469
458
  try {
470
459
  const outcome = await editInExternalEditor(value);
471
460
  if (outcome.kind === 'edited') {
472
- setValue(outcome.text);
473
- setCursor(outcome.text.length);
461
+ setInput(outcome.text);
474
462
  setSelectedCommand(0);
475
463
  setFileSelected(0);
476
464
  }
@@ -575,8 +563,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
575
563
  // ink/terminal.ts); Option+Enter (ESC CR) is the fallback on terminals
576
564
  // that can't report shift — e.g. macOS Terminal.app (issue #110).
577
565
  const next = value.slice(0, cursor) + '\n' + value.slice(cursor);
578
- setValue(next);
579
- setCursor(cursor + 1);
566
+ setInput(next, cursor + 1);
580
567
  setSelectedCommand(0);
581
568
  return;
582
569
  }
@@ -620,13 +607,13 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
620
607
  setFileSelected(index => index <= 0 ? fileMatches.length - 1 : index - 1);
621
608
  return;
622
609
  }
623
- const line = cursorLine();
610
+ const line = cursorLine(value, cursor);
624
611
  if (line > 0) {
625
612
  // Move to the previous line, clamping to its length.
626
613
  const upToLineStart = value.lastIndexOf('\n', cursor - 1);
627
614
  const prevLineStart = upToLineStart === -1 ? 0 : value.lastIndexOf('\n', upToLineStart - 1) + 1;
628
615
  const prevLine = value.slice(prevLineStart, upToLineStart);
629
- setCursor(prevLineStart + Math.min(cursorColumn(), prevLine.length));
616
+ setInput(value, prevLineStart + Math.min(cursorColumn(value, cursor), prevLine.length));
630
617
  return;
631
618
  }
632
619
  if (overlayOpen) {
@@ -639,8 +626,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
639
626
  ? history.current.length - 1
640
627
  : Math.max(0, historyIndex.current - 1);
641
628
  const entry = history.current[historyIndex.current] ?? '';
642
- setValue(entry);
643
- setCursor(entry.length);
629
+ setInput(entry);
644
630
  return;
645
631
  }
646
632
  if (key.downArrow) {
@@ -648,13 +634,13 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
648
634
  setFileSelected(index => index >= fileMatches.length - 1 ? 0 : index + 1);
649
635
  return;
650
636
  }
651
- const line = cursorLine();
637
+ const line = cursorLine(value, cursor);
652
638
  const lines = value.split('\n');
653
639
  if (line < lines.length - 1) {
654
640
  const nextLineStart = value.indexOf('\n', cursor) + 1;
655
641
  const nextLineEnd = value.indexOf('\n', nextLineStart);
656
642
  const nextLine = value.slice(nextLineStart, nextLineEnd === -1 ? value.length : nextLineEnd);
657
- setCursor(nextLineStart + Math.min(cursorColumn(), nextLine.length));
643
+ setInput(value, nextLineStart + Math.min(cursorColumn(value, cursor), nextLine.length));
658
644
  return;
659
645
  }
660
646
  if (overlayOpen) {
@@ -667,76 +653,73 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
667
653
  const entry = historyIndex.current >= history.current.length
668
654
  ? ''
669
655
  : (history.current[historyIndex.current] ?? '');
670
- setValue(entry);
671
- setCursor(entry.length);
656
+ setInput(entry);
672
657
  return;
673
658
  }
674
659
  if (isMod(key) && key.leftArrow) {
675
660
  // Jump to the previous word boundary (readline alt+b). Must precede the
676
661
  // bare-arrow arms: Ctrl+Left arrives as leftArrow + ctrl.
677
- setCursor(previous => wordBoundaryLeft(value, previous));
662
+ setInput(value, wordBoundaryLeft(value, cursor));
678
663
  return;
679
664
  }
680
665
  if (isMod(key) && key.rightArrow) {
681
666
  // Jump to the next word boundary (readline alt+f).
682
- setCursor(previous => wordBoundaryRight(value, previous));
667
+ setInput(value, wordBoundaryRight(value, cursor));
683
668
  return;
684
669
  }
685
670
  if (key.leftArrow) {
686
- setCursor(previous => Math.max(0, previous - 1));
671
+ setInput(value, Math.max(0, cursor - 1));
687
672
  return;
688
673
  }
689
674
  if (key.rightArrow) {
690
- setCursor(previous => Math.min(value.length, previous + 1));
675
+ setInput(value, Math.min(value.length, cursor + 1));
691
676
  return;
692
677
  }
693
678
  if (key.backspace) {
694
679
  if (cursor === 0)
695
680
  return;
696
- setValue(value.slice(0, cursor - 1) + value.slice(cursor));
697
- setCursor(cursor - 1);
681
+ setInput(value.slice(0, cursor - 1) + value.slice(cursor), cursor - 1);
698
682
  return;
699
683
  }
700
684
  if (key.delete) {
701
685
  if (cursor >= value.length)
702
686
  return;
703
- setValue(value.slice(0, cursor) + value.slice(cursor + 1));
687
+ setInput(value.slice(0, cursor) + value.slice(cursor + 1), cursor);
704
688
  return;
705
689
  }
706
690
  if (key.home) {
707
691
  // Start of the current line.
708
692
  const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
709
- setCursor(lineStart);
693
+ setInput(value, lineStart);
710
694
  return;
711
695
  }
712
696
  if (key.end) {
713
697
  // End of the current line.
714
698
  const nextLine = value.indexOf('\n', cursor);
715
- setCursor(nextLine === -1 ? value.length : nextLine);
699
+ setInput(value, nextLine === -1 ? value.length : nextLine);
716
700
  return;
717
701
  }
718
702
  if (isMod(key) && input === 'a') {
719
703
  const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
720
- setCursor(lineStart);
704
+ setInput(value, lineStart);
721
705
  return;
722
706
  }
723
707
  if (isMod(key) && input === 'e') {
724
708
  const nextLine = value.indexOf('\n', cursor);
725
- setCursor(nextLine === -1 ? value.length : nextLine);
709
+ setInput(value, nextLine === -1 ? value.length : nextLine);
726
710
  return;
727
711
  }
728
712
  if (isMod(key) && input === 'u') {
729
713
  // Delete to start of line.
730
714
  const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
731
- setValue(value.slice(0, lineStart) + value.slice(cursor));
732
- setCursor(lineStart);
715
+ setInput(value.slice(0, lineStart) + value.slice(cursor), lineStart);
733
716
  return;
734
717
  }
735
718
  if (isMod(key) && input === 'k') {
736
719
  // Delete to end of line.
737
720
  const nextLine = value.indexOf('\n', cursor);
738
721
  const end = nextLine === -1 ? value.length : nextLine;
739
- setValue(value.slice(0, cursor) + value.slice(end));
722
+ setInput(value.slice(0, cursor) + value.slice(end), cursor);
740
723
  return;
741
724
  }
742
725
  if (isMod(key) && input === 'w') {
@@ -749,8 +732,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
749
732
  let start = end;
750
733
  while (start > 0 && !/\s/.test(before[start - 1]))
751
734
  start--;
752
- setValue(value.slice(0, start) + value.slice(cursor));
753
- setCursor(start);
735
+ setInput(value.slice(0, start) + value.slice(cursor), start);
754
736
  return;
755
737
  }
756
738
  if (key.escape) {
@@ -761,8 +743,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
761
743
  // A single Esc closes the open command menu first (CC/pi behavior);
762
744
  // the double-tap-clear semantics only apply to ordinary input.
763
745
  if (overlayOpen) {
764
- setValue('');
765
- setCursor(0);
746
+ setInput('', 0);
766
747
  setSelectedCommand(0);
767
748
  setFileSelected(0);
768
749
  return;
@@ -786,8 +767,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
786
767
  // A single Esc clears the current input (if any); the double-tap
787
768
  // path below handles rewind/clear on an already-empty input.
788
769
  if (value.length > 0) {
789
- setValue('');
790
- setCursor(0);
770
+ setInput('', 0);
791
771
  setSelectedCommand(0);
792
772
  setFileSelected(0);
793
773
  return;
@@ -803,8 +783,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
803
783
  onRewindRequest?.();
804
784
  }
805
785
  else {
806
- setValue('');
807
- setCursor(0);
786
+ setInput('', 0);
808
787
  }
809
788
  return;
810
789
  }
@@ -826,8 +805,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
826
805
  if (helpOpen)
827
806
  onToggleHelp();
828
807
  const next = value.slice(0, cursor) + input + value.slice(cursor);
829
- setValue(next);
830
- setCursor(cursor + input.length);
808
+ setInput(next, cursor + input.length);
831
809
  setSelectedCommand(0);
832
810
  setFileSelected(0);
833
811
  }
@@ -6,6 +6,10 @@ import React from 'react';
6
6
  * placeholder is right-aligned (dimmed) — kept off the caret's cell so the
7
7
  * terminal-painted IME preedit (pinyin) can never be overlaid on it during
8
8
  * CJK composition.
9
+ *
10
+ * The query row is strictly single-line: an overlong query is windowed
11
+ * around the caret (horizontal scroll) instead of wrapping, so the native
12
+ * cursor declaration below stays exact for any query length.
9
13
  */
10
14
  export declare function SearchBox({ query, placeholder, isFocused, isTerminalFocused, prefix, width, cursorOffset, borderless, }: {
11
15
  query: string;
@@ -1 +1 @@
1
- {"version":3,"file":"SearchBox.d.ts","sourceRoot":"","sources":["../../../src/components/SearchBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,WAAuB,EACvB,SAAS,EACT,iBAAiB,EACjB,MAAY,EACZ,KAAK,EACL,YAAY,EACZ,UAAkB,GACnB,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,GAAG,KAAK,CAAC,SAAS,CAqDlB"}
1
+ {"version":3,"file":"SearchBox.d.ts","sourceRoot":"","sources":["../../../src/components/SearchBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyD,MAAM,OAAO,CAAA;AAyD7E;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,WAAuB,EACvB,SAAS,EACT,iBAAiB,EACjB,MAAY,EACZ,KAAK,EACL,YAAY,EACZ,UAAkB,GACnB,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,GAAG,KAAK,CAAC,SAAS,CA0GlB"}
@@ -1,6 +1,54 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useLayoutEffect, useRef, useState } from 'react';
2
3
  import Box from '../ink/components/Box.js';
3
4
  import Text from '../ink/components/Text.js';
5
+ import measureElement from '../ink/measure-element.js';
6
+ import { useDeclaredCursor } from '../ink/hooks/use-declared-cursor.js';
7
+ import { useTerminalSize } from '../ink/hooks/use-terminal-size.js';
8
+ import { stringWidth } from '../ink/stringWidth.js';
9
+ /**
10
+ * Window a single-line query around the caret so the visible slice fits
11
+ * `avail` display cells (CJK display width, code-point safe — `[...s]`
12
+ * iterates code points, not UTF-16 units). The caret character itself
13
+ * always stays inside the window: leading characters are dropped until
14
+ * before-caret text + caret fit, then after-caret text fills what remains.
15
+ * `offset` arrives in UTF-16 units (callers move the caret by key count);
16
+ * a mid-surrogate offset would split an emoji, so it snaps back to the
17
+ * pair's start first.
18
+ */
19
+ function windowQuery(query, offset, avail) {
20
+ const budget = Math.max(avail, 1);
21
+ let caret = Math.max(0, Math.min(offset, query.length));
22
+ if (caret > 0 &&
23
+ caret < query.length &&
24
+ query.charCodeAt(caret - 1) >= 0xd800 &&
25
+ query.charCodeAt(caret - 1) <= 0xdbff &&
26
+ query.charCodeAt(caret) >= 0xdc00 &&
27
+ query.charCodeAt(caret) <= 0xdfff) {
28
+ caret--; // mid-surrogate: snap to the emoji's start
29
+ }
30
+ const beforeChars = [...query.slice(0, caret)];
31
+ const at = caret < query.length ? [...query.slice(caret)][0] : ' ';
32
+ const atWidth = Math.max(1, stringWidth(at));
33
+ let caretColumn = 0;
34
+ for (const ch of beforeChars)
35
+ caretColumn += stringWidth(ch);
36
+ let start = 0;
37
+ while (start < beforeChars.length && caretColumn + atWidth > budget) {
38
+ caretColumn -= stringWidth(beforeChars[start]);
39
+ start++;
40
+ }
41
+ let rest = budget - caretColumn - atWidth;
42
+ let after = '';
43
+ for (const ch of [...query.slice(caret + at.length)]) {
44
+ const w = stringWidth(ch);
45
+ if (w > rest)
46
+ break;
47
+ after += ch;
48
+ rest -= w;
49
+ }
50
+ return { before: beforeChars.slice(start).join(''), at, after, caretColumn };
51
+ }
4
52
  /**
5
53
  * A single-line search input in the round-bordered box of Claude Code's
6
54
  * SearchBox: `⌕ ` prefix, block cursor at `cursorOffset` (inverse cell).
@@ -8,6 +56,10 @@ import Text from '../ink/components/Text.js';
8
56
  * placeholder is right-aligned (dimmed) — kept off the caret's cell so the
9
57
  * terminal-painted IME preedit (pinyin) can never be overlaid on it during
10
58
  * CJK composition.
59
+ *
60
+ * The query row is strictly single-line: an overlong query is windowed
61
+ * around the caret (horizontal scroll) instead of wrapping, so the native
62
+ * cursor declaration below stays exact for any query length.
11
63
  */
12
64
  export function SearchBox({ query, placeholder = 'Search…', isFocused, isTerminalFocused, prefix = '⌕', width, cursorOffset, borderless = false, }) {
13
65
  const offset = cursorOffset ?? query.length;
@@ -17,10 +69,57 @@ export function SearchBox({ query, placeholder = 'Search…', isFocused, isTermi
17
69
  // Focused + empty + terminal focused: inline caret row (block caret at the
18
70
  // start, placeholder right-aligned) instead of the inline placeholder.
19
71
  const inlineCaret = isFocused && query === '' && isTerminalFocused;
72
+ // Content width of the box in display cells. Measured from yoga after
73
+ // layout (resize re-layouts without any prop/state change, so measure on
74
+ // every commit — the setState is a no-op when the width is unchanged);
75
+ // the terminal-columns estimate only covers the very first frame.
76
+ const { columns } = useTerminalSize();
77
+ const chrome = borderless ? 0 : 4; // 2 border cells + paddingX 2
78
+ const [measuredWidth, setMeasuredWidth] = useState(null);
79
+ const contentWidth = measuredWidth ?? Math.max(8, columns - chrome - 2);
80
+ const prefixWidth = stringWidth(`${prefix} `);
81
+ const win = windowQuery(query, offset, contentWidth - prefixWidth);
82
+ // Park the native terminal cursor at the caret so IME preedit (pinyin)
83
+ // renders inline at the input instead of the screen's bottom row (same
84
+ // mechanism as PromptInput). The declaration hangs on the outer Box —
85
+ // content stays a single Text (splitting it into flex siblings
86
+ // reorders/drops glyphs on narrow widths), so the position is computed:
87
+ // border (1) + paddingX (1) per edge when bordered, then the `prefix `
88
+ // run and the windowed before-caret text, all in display cells.
89
+ const showCaret = isFocused && isTerminalFocused;
90
+ // Clamp into the box's content area: on absurdly narrow layouts the
91
+ // prefix alone can meet or exceed the content width, and the park must
92
+ // never land outside the box's rect.
93
+ const edge = borderless ? 0 : 2;
94
+ const maxColumn = edge + Math.max(0, contentWidth - 1);
95
+ const caretColumn = Math.min(edge + prefixWidth + win.caretColumn, maxColumn);
96
+ const declarationRef = useDeclaredCursor({
97
+ line: borderless ? 0 : 1,
98
+ column: caretColumn,
99
+ active: showCaret,
100
+ });
101
+ const boxNodeRef = useRef(null);
102
+ const boxRef = useCallback((node) => {
103
+ boxNodeRef.current = node;
104
+ declarationRef(node);
105
+ }, [declarationRef]);
106
+ useLayoutEffect(() => {
107
+ const node = boxNodeRef.current;
108
+ if (!node)
109
+ return;
110
+ // Layout runs before layout effects (reconciler resetAfterCommit), so a
111
+ // zero raw width means a genuinely zero-width box — but a zero CONTENT
112
+ // width (chrome eats the whole box) is a real, must-clamp case.
113
+ const raw = measureElement(node).width;
114
+ if (raw > 0) {
115
+ const w = Math.max(0, raw - chrome);
116
+ setMeasuredWidth(prev => (prev === w ? prev : w));
117
+ }
118
+ });
20
119
  let content;
21
120
  if (isFocused) {
22
121
  if (query) {
23
- content = isTerminalFocused ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: query.slice(0, offset) }), _jsx(Text, { inverse: true, children: offset < query.length ? query[offset] : ' ' }), offset < query.length && _jsx(Text, { children: query.slice(offset + 1) })] })) : (_jsx(Text, { children: query }));
122
+ content = isTerminalFocused ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: win.before }), _jsx(Text, { inverse: true, children: win.at }), win.after !== '' && _jsx(Text, { children: win.after })] })) : (_jsx(Text, { children: query }));
24
123
  }
25
124
  else if (!isTerminalFocused) {
26
125
  content = _jsx(Text, { dimColor: true, children: placeholder });
@@ -29,5 +128,5 @@ export function SearchBox({ query, placeholder = 'Search…', isFocused, isTermi
29
128
  else {
30
129
  content = query ? _jsx(Text, { children: query }) : _jsx(Text, { children: placeholder });
31
130
  }
32
- return (_jsx(Box, { flexShrink: 0, borderStyle: borderStyle, borderColor: borderColor, borderDimColor: borderDimColor, paddingX: borderless ? 0 : 1, width: width, children: inlineCaret ? (_jsxs(Box, { flexDirection: "row", width: "100%", children: [_jsxs(Text, { children: [prefix, " "] }), _jsx(Text, { inverse: true, children: " " }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { dimColor: true, wrap: "truncate", children: placeholder })] })) : (_jsxs(Text, { dimColor: !isFocused, children: [prefix, " ", content] })) }));
131
+ return (_jsx(Box, { ref: boxRef, flexShrink: 0, borderStyle: borderStyle, borderColor: borderColor, borderDimColor: borderDimColor, paddingX: borderless ? 0 : 1, width: width, children: inlineCaret ? (_jsxs(Box, { flexDirection: "row", width: "100%", children: [_jsxs(Text, { children: [prefix, " "] }), _jsx(Text, { inverse: true, children: " " }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { dimColor: true, wrap: "truncate", children: placeholder })] })) : (_jsxs(Text, { dimColor: !isFocused, wrap: "truncate-end", children: [prefix, " ", content] })) }));
33
132
  }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { SkillInfo } from '../dsh-adapter/channel.js';
3
+ /**
4
+ * `/skills` picker (issue #204) in the ModelPicker style: a
5
+ * permission-colored Pane listing the live agent's skill catalog —
6
+ * user-invocable skills lead with `/name`(它们在 / 菜单里也是这个形态),
7
+ * 描述行是「来源 · 简述」。Enter 由 Chat 填回 `/name `,Esc 关闭。
8
+ *
9
+ * 长列表按焦点窗口化(ModelPicker 同款):picker 经 OverlayAbove 浮层挂载后
10
+ * 有 maxHeight 裁剪,全量渲染会让焦点行被裁掉(看不到焦点按 Enter)。
11
+ */
12
+ export declare function SkillsPicker({ skills, focusIndex, }: {
13
+ skills: readonly SkillInfo[];
14
+ focusIndex: number;
15
+ }): React.ReactNode;
16
+ /** `/skills` while the registry snapshot is still in flight (ModelPickerLoading 同款). */
17
+ export declare function SkillsPickerLoading(): React.ReactNode;
18
+ //# sourceMappingURL=SkillsPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillsPicker.d.ts","sourceRoot":"","sources":["../../../src/components/SkillsPicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AA2B1D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,UAAU,GACX,EAAE;IACD,MAAM,EAAE,SAAS,SAAS,EAAE,CAAA;IAC5B,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,KAAK,CAAC,SAAS,CAyClB;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,IAAI,KAAK,CAAC,SAAS,CAerD"}
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { t } from '../i18n.js';
3
+ import { Box, Text, useTerminalSize } from '../ui.js';
4
+ import { Pane } from './design-system/Pane.js';
5
+ import { ListItem } from './design-system/ListItem.js';
6
+ import { HintLine } from './design-system/HintLine.js';
7
+ import { LoadingState } from './design-system/LoadingState.js';
8
+ import { listWindow } from './listWindow.js';
9
+ /** 来源桶 → 本地化标签(未知桶原样显示,SkillSource 对自定义桶开放)。 */
10
+ function sourceLabel(source) {
11
+ switch (source) {
12
+ case 'bundled':
13
+ return t('skills-source-bundled');
14
+ case 'user-dsh':
15
+ case 'user-agents':
16
+ return t('skills-source-user');
17
+ case 'project-dsh':
18
+ case 'project-agents':
19
+ return t('skills-source-project');
20
+ case 'runtime':
21
+ return t('skills-source-runtime');
22
+ case 'custom':
23
+ return t('skills-source-custom');
24
+ default:
25
+ return source;
26
+ }
27
+ }
28
+ /**
29
+ * `/skills` picker (issue #204) in the ModelPicker style: a
30
+ * permission-colored Pane listing the live agent's skill catalog —
31
+ * user-invocable skills lead with `/name`(它们在 / 菜单里也是这个形态),
32
+ * 描述行是「来源 · 简述」。Enter 由 Chat 填回 `/name `,Esc 关闭。
33
+ *
34
+ * 长列表按焦点窗口化(ModelPicker 同款):picker 经 OverlayAbove 浮层挂载后
35
+ * 有 maxHeight 裁剪,全量渲染会让焦点行被裁掉(看不到焦点按 Enter)。
36
+ */
37
+ export function SkillsPicker({ skills, focusIndex, }) {
38
+ const { rows: terminalRows } = useTerminalSize();
39
+ // 每项恒占 2 行(正文 + 来源/简述描述行,均 truncate 成单行)。
40
+ // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 = 13(ModelPicker 同款)。
41
+ const { start, end } = listWindow(skills.map(() => 2), focusIndex, Math.max(terminalRows - 13, 2));
42
+ return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: t('picker-title-skills') }) }), skills.length === 0 ? (_jsx(Text, { dimColor: true, children: t('skills-empty') })) : (skills.slice(start, end).map((skill, index) => {
43
+ const absoluteIndex = start + index;
44
+ return (_jsx(ListItem, { isFocused: absoluteIndex === focusIndex, description: `${sourceLabel(skill.source)}${skill.description === '' ? '' : ` · ${skill.description}`}`, showScrollUp: absoluteIndex === start && start > 0, showScrollDown: absoluteIndex === end - 1 && end < skills.length, children: skill.userInvocable ? `/${skill.name}` : skill.name }, skill.name));
45
+ }))] }), _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: t('hint-fill-exit') }) })] }));
46
+ }
47
+ /** `/skills` while the registry snapshot is still in flight (ModelPickerLoading 同款). */
48
+ export function SkillsPickerLoading() {
49
+ return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: "permission", children: t('picker-title-skills') }), _jsx(LoadingState, { message: t('skills-loading'), bold: true, subtitle: t('skills-loading-subtitle') })] }) }));
50
+ }
@@ -7,6 +7,9 @@ export type SpinnerAnimationRowProps = {
7
7
  hasActiveTools: boolean;
8
8
  /** Raw response length (chars) — feeds the animated token counter. */
9
9
  responseLengthRef: React.RefObject<number>;
10
+ /** Most recent request's real upload tokens (input + cache read/write);
11
+ * 0 until the first usage event lands. */
12
+ uploadTokensRef: React.RefObject<number>;
10
13
  /** Stable within a turn. */
11
14
  message: string;
12
15
  messageColor: keyof Theme;
@@ -27,5 +30,5 @@ export type SpinnerAnimationRowProps = {
27
30
  * animation clock (frame, glimmer, token counter animation, elapsed time,
28
31
  * stalled intensity, thinking shimmer).
29
32
  */
30
- export declare function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, responseLengthRef, message, messageColor, shimmerColor, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, spinnerSuffix, verbose, columns, thinkingStatus, }: SpinnerAnimationRowProps): React.ReactNode;
33
+ export declare function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, responseLengthRef, uploadTokensRef, message, messageColor, shimmerColor, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, spinnerSuffix, verbose, columns, thinkingStatus, }: SpinnerAnimationRowProps): React.ReactNode;
31
34
  //# sourceMappingURL=SpinnerAnimationRow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpinnerAnimationRow.d.ts","sourceRoot":"","sources":["../../../../src/components/Spinner/SpinnerAnimationRow.tsx"],"names":[],"mappings":"AACA,OAAO,KAA0B,MAAM,OAAO,CAAA;AAM9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAI3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAcnD,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;IACvB,sEAAsE;IACtE,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC1C,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,KAAK,CAAA;IACzB,YAAY,EAAE,MAAM,KAAK,CAAA;IACzB,mBAAmB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACzC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,cAAc,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAA;CAC3C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,OAAO,EACP,OAAO,EACP,cAAc,GACf,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CAwM5C"}
1
+ {"version":3,"file":"SpinnerAnimationRow.d.ts","sourceRoot":"","sources":["../../../../src/components/Spinner/SpinnerAnimationRow.tsx"],"names":[],"mappings":"AACA,OAAO,KAA0B,MAAM,OAAO,CAAA;AAM9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAI3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAcnD,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;IACvB,sEAAsE;IACtE,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC1C;+CAC2C;IAC3C,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACxC,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,KAAK,CAAA;IACzB,YAAY,EAAE,MAAM,KAAK,CAAA;IACzB,mBAAmB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACzC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,cAAc,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAA;CAC3C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,OAAO,EACP,OAAO,EACP,cAAc,GACf,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CA6M5C"}