@shareai-lab/kode 1.0.71 → 1.0.73

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 (106) hide show
  1. package/README.md +142 -1
  2. package/README.zh-CN.md +47 -1
  3. package/package.json +5 -1
  4. package/src/ProjectOnboarding.tsx +47 -29
  5. package/src/Tool.ts +33 -4
  6. package/src/commands/agents.tsx +3401 -0
  7. package/src/commands/help.tsx +2 -2
  8. package/src/commands/resume.tsx +2 -1
  9. package/src/commands/terminalSetup.ts +4 -4
  10. package/src/commands.ts +3 -0
  11. package/src/components/ApproveApiKey.tsx +1 -1
  12. package/src/components/Config.tsx +10 -6
  13. package/src/components/ConsoleOAuthFlow.tsx +5 -4
  14. package/src/components/CustomSelect/select-option.tsx +28 -2
  15. package/src/components/CustomSelect/select.tsx +14 -5
  16. package/src/components/CustomSelect/theme.ts +45 -0
  17. package/src/components/Help.tsx +4 -4
  18. package/src/components/InvalidConfigDialog.tsx +1 -1
  19. package/src/components/LogSelector.tsx +1 -1
  20. package/src/components/MCPServerApprovalDialog.tsx +1 -1
  21. package/src/components/Message.tsx +2 -0
  22. package/src/components/ModelListManager.tsx +10 -6
  23. package/src/components/ModelSelector.tsx +201 -23
  24. package/src/components/ModelStatusDisplay.tsx +7 -5
  25. package/src/components/PromptInput.tsx +117 -87
  26. package/src/components/SentryErrorBoundary.ts +3 -3
  27. package/src/components/StickerRequestForm.tsx +16 -0
  28. package/src/components/StructuredDiff.tsx +36 -29
  29. package/src/components/TextInput.tsx +13 -0
  30. package/src/components/TodoItem.tsx +11 -0
  31. package/src/components/TrustDialog.tsx +1 -1
  32. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +5 -1
  33. package/src/components/messages/AssistantToolUseMessage.tsx +14 -4
  34. package/src/components/messages/TaskProgressMessage.tsx +32 -0
  35. package/src/components/messages/TaskToolMessage.tsx +58 -0
  36. package/src/components/permissions/FallbackPermissionRequest.tsx +2 -4
  37. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +1 -1
  38. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +5 -3
  39. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +1 -1
  40. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +5 -3
  41. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +2 -4
  42. package/src/components/permissions/PermissionRequest.tsx +3 -5
  43. package/src/constants/macros.ts +2 -0
  44. package/src/constants/modelCapabilities.ts +179 -0
  45. package/src/constants/models.ts +90 -0
  46. package/src/constants/product.ts +1 -1
  47. package/src/context.ts +7 -7
  48. package/src/entrypoints/cli.tsx +23 -3
  49. package/src/entrypoints/mcp.ts +10 -10
  50. package/src/hooks/useCanUseTool.ts +1 -1
  51. package/src/hooks/useTextInput.ts +5 -2
  52. package/src/hooks/useUnifiedCompletion.ts +1404 -0
  53. package/src/messages.ts +1 -0
  54. package/src/query.ts +3 -0
  55. package/src/screens/ConfigureNpmPrefix.tsx +1 -1
  56. package/src/screens/Doctor.tsx +1 -1
  57. package/src/screens/REPL.tsx +15 -9
  58. package/src/services/adapters/base.ts +38 -0
  59. package/src/services/adapters/chatCompletions.ts +90 -0
  60. package/src/services/adapters/responsesAPI.ts +170 -0
  61. package/src/services/claude.ts +198 -62
  62. package/src/services/customCommands.ts +43 -22
  63. package/src/services/gpt5ConnectionTest.ts +340 -0
  64. package/src/services/mcpClient.ts +1 -1
  65. package/src/services/mentionProcessor.ts +273 -0
  66. package/src/services/modelAdapterFactory.ts +69 -0
  67. package/src/services/openai.ts +521 -12
  68. package/src/services/responseStateManager.ts +90 -0
  69. package/src/services/systemReminder.ts +113 -12
  70. package/src/test/testAdapters.ts +96 -0
  71. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +120 -56
  72. package/src/tools/BashTool/BashTool.tsx +4 -31
  73. package/src/tools/BashTool/BashToolResultMessage.tsx +1 -1
  74. package/src/tools/BashTool/OutputLine.tsx +1 -0
  75. package/src/tools/FileEditTool/FileEditTool.tsx +4 -5
  76. package/src/tools/FileReadTool/FileReadTool.tsx +43 -10
  77. package/src/tools/MCPTool/MCPTool.tsx +2 -1
  78. package/src/tools/MultiEditTool/MultiEditTool.tsx +2 -2
  79. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +15 -23
  80. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +1 -1
  81. package/src/tools/TaskTool/TaskTool.tsx +170 -86
  82. package/src/tools/TaskTool/prompt.ts +61 -25
  83. package/src/tools/ThinkTool/ThinkTool.tsx +1 -3
  84. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +11 -10
  85. package/src/tools/lsTool/lsTool.tsx +5 -2
  86. package/src/tools.ts +16 -16
  87. package/src/types/conversation.ts +51 -0
  88. package/src/types/logs.ts +58 -0
  89. package/src/types/modelCapabilities.ts +64 -0
  90. package/src/types/notebook.ts +87 -0
  91. package/src/utils/advancedFuzzyMatcher.ts +290 -0
  92. package/src/utils/agentLoader.ts +284 -0
  93. package/src/utils/ask.tsx +1 -0
  94. package/src/utils/commands.ts +1 -1
  95. package/src/utils/commonUnixCommands.ts +161 -0
  96. package/src/utils/config.ts +173 -2
  97. package/src/utils/conversationRecovery.ts +1 -0
  98. package/src/utils/debugLogger.ts +13 -13
  99. package/src/utils/exampleCommands.ts +1 -0
  100. package/src/utils/fuzzyMatcher.ts +328 -0
  101. package/src/utils/messages.tsx +6 -5
  102. package/src/utils/responseState.ts +23 -0
  103. package/src/utils/secureFile.ts +559 -0
  104. package/src/utils/terminal.ts +1 -0
  105. package/src/utils/theme.ts +11 -0
  106. package/src/hooks/useSlashCommandTypeahead.ts +0 -137
@@ -185,7 +185,7 @@ export function ModelStatusDisplay({ onClose }: Props): React.ReactNode {
185
185
  </Text>
186
186
  <Text color={theme.secondaryText}>
187
187
  {' '}
188
- DefaultModelId: {config.defaultModelId || 'not set'}
188
+ DefaultModelId: {(config as any).defaultModelId || 'not set'}
189
189
  </Text>
190
190
  {config.modelPointers && (
191
191
  <>
@@ -195,10 +195,12 @@ export function ModelStatusDisplay({ onClose }: Props): React.ReactNode {
195
195
  {Object.keys(config.modelPointers).length > 0 ? 'Yes' : 'No'}
196
196
  </Text>
197
197
  {Object.entries(config.modelPointers).map(([pointer, modelId]) => (
198
- <Text key={pointer} color={theme.secondaryText}>
199
- {' '}
200
- {pointer}: {modelId || 'not set'}
201
- </Text>
198
+ <React.Fragment key={pointer}>
199
+ <Text color={theme.secondaryText}>
200
+ {' '}
201
+ {pointer}: {modelId || 'not set'}
202
+ </Text>
203
+ </React.Fragment>
202
204
  ))}
203
205
  </>
204
206
  )}
@@ -5,7 +5,7 @@ import * as React from 'react'
5
5
  import { type Message } from '../query'
6
6
  import { processUserInput } from '../utils/messages'
7
7
  import { useArrowKeyHistory } from '../hooks/useArrowKeyHistory'
8
- import { useSlashCommandTypeahead } from '../hooks/useSlashCommandTypeahead'
8
+ import { useUnifiedCompletion } from '../hooks/useUnifiedCompletion'
9
9
  import { addToHistory } from '../history'
10
10
  import TextInput from './TextInput'
11
11
  import { memo, useCallback, useEffect, useMemo, useState } from 'react'
@@ -96,7 +96,6 @@ type Props = {
96
96
  ) => void
97
97
  readFileTimestamps: { [filename: string]: number }
98
98
  abortController: AbortController | null
99
- setAbortController: (abortController: AbortController | null) => void
100
99
  onModelChange?: () => void
101
100
  }
102
101
 
@@ -166,19 +165,53 @@ function PromptInput({
166
165
  [commands],
167
166
  )
168
167
 
168
+ // Unified completion system - one hook to rule them all (now with terminal behavior)
169
169
  const {
170
170
  suggestions,
171
- selectedSuggestion,
172
- updateSuggestions,
173
- clearSuggestions,
174
- } = useSlashCommandTypeahead({
175
- commands,
171
+ selectedIndex,
172
+ isActive: completionActive,
173
+ emptyDirMessage,
174
+ } = useUnifiedCompletion({
175
+ input,
176
+ cursorOffset,
176
177
  onInputChange,
177
- onSubmit,
178
178
  setCursorOffset,
179
- currentInput: input,
179
+ commands,
180
+ onSubmit,
180
181
  })
181
182
 
183
+ // Get theme early for memoized rendering
184
+ const theme = getTheme()
185
+
186
+ // Memoized completion suggestions rendering - after useUnifiedCompletion
187
+ const renderedSuggestions = useMemo(() => {
188
+ if (suggestions.length === 0) return null
189
+
190
+ return suggestions.map((suggestion, index) => {
191
+ const isSelected = index === selectedIndex
192
+ const isAgent = suggestion.type === 'agent'
193
+
194
+ // Simple color logic without complex lookups
195
+ const displayColor = isSelected
196
+ ? theme.suggestion
197
+ : (isAgent && suggestion.metadata?.color)
198
+ ? suggestion.metadata.color
199
+ : undefined
200
+
201
+ return (
202
+ <Box key={`${suggestion.type}-${suggestion.value}-${index}`} flexDirection="row">
203
+ <Text
204
+ color={displayColor}
205
+ dimColor={!isSelected && !displayColor}
206
+ >
207
+ {isSelected ? '◆ ' : ' '}
208
+ {suggestion.displayValue}
209
+ </Text>
210
+ </Box>
211
+ )
212
+ })
213
+ }, [suggestions, selectedIndex, theme.suggestion])
214
+
182
215
  const onChange = useCallback(
183
216
  (value: string) => {
184
217
  if (value.startsWith('!')) {
@@ -189,10 +222,9 @@ function PromptInput({
189
222
  onModeChange('koding')
190
223
  return
191
224
  }
192
- updateSuggestions(value)
193
225
  onInputChange(value)
194
226
  },
195
- [onModeChange, onInputChange, updateSuggestions],
227
+ [onModeChange, onInputChange],
196
228
  )
197
229
 
198
230
  // Handle Tab key model switching with simple context check
@@ -238,15 +270,15 @@ function PromptInput({
238
270
  input,
239
271
  )
240
272
 
241
- // Only use history navigation when there are 0 or 1 slash command suggestions
273
+ // Only use history navigation when there are no suggestions
242
274
  const handleHistoryUp = () => {
243
- if (suggestions.length <= 1) {
275
+ if (!completionActive) {
244
276
  onHistoryUp()
245
277
  }
246
278
  }
247
279
 
248
280
  const handleHistoryDown = () => {
249
- if (suggestions.length <= 1) {
281
+ if (!completionActive) {
250
282
  onHistoryDown()
251
283
  }
252
284
  }
@@ -270,7 +302,7 @@ function PromptInput({
270
302
 
271
303
  // Create additional context to inform Claude this is for KODING.md
272
304
  const kodingContext =
273
- 'The user is using Koding mode. Format your response as a comprehensive, well-structured document suitable for adding to KODE.md. Use proper markdown formatting with headings, lists, code blocks, etc. The response should be complete and ready to add to KODE.md documentation.'
305
+ 'The user is using Koding mode. Format your response as a comprehensive, well-structured document suitable for adding to AGENTS.md. Use proper markdown formatting with headings, lists, code blocks, etc. The response should be complete and ready to add to AGENTS.md documentation.'
274
306
 
275
307
  // Switch to prompt mode but tag the submission for later capture
276
308
  onModeChange('prompt')
@@ -326,7 +358,7 @@ function PromptInput({
326
358
  }
327
359
  }
328
360
 
329
- // If in koding mode or input starts with '#', interpret it using AI before appending to KODE.md
361
+ // If in koding mode or input starts with '#', interpret it using AI before appending to AGENTS.md
330
362
  else if (mode === 'koding' || input.startsWith('#')) {
331
363
  try {
332
364
  // Strip the # if we're in koding mode and the user didn't type it (since it's implied)
@@ -354,7 +386,12 @@ function PromptInput({
354
386
  if (isLoading) {
355
387
  return
356
388
  }
357
- if (suggestions.length > 0 && !isSubmittingSlashCommand) {
389
+
390
+ // Handle Enter key when completions are active
391
+ // If there are suggestions showing, Enter should complete the selection, not send the message
392
+ if (suggestions.length > 0 && completionActive) {
393
+ // The completion is handled by useUnifiedCompletion hook
394
+ // Just return to prevent message sending
358
395
  return
359
396
  }
360
397
 
@@ -373,7 +410,7 @@ function PromptInput({
373
410
  }
374
411
  onInputChange('')
375
412
  onModeChange('prompt')
376
- clearSuggestions()
413
+ // Suggestions are now handled by unified completion
377
414
  setPastedImage(null)
378
415
  setPastedText(null)
379
416
  onSubmitCountChange(_ => _ + 1)
@@ -446,8 +483,29 @@ function PromptInput({
446
483
  setPastedText(text)
447
484
  }
448
485
 
449
- useInput((input, key) => {
450
- if (input === '' && (key.escape || key.backspace || key.delete)) {
486
+ useInput((inputChar, key) => {
487
+ // For bash mode, only exit when deleting the last character (which would be the '!' character)
488
+ if (mode === 'bash' && (key.backspace || key.delete)) {
489
+ // Check the current input state, not the inputChar parameter
490
+ // If current input is empty, we're about to delete the '!' character, so exit bash mode
491
+ if (input === '') {
492
+ onModeChange('prompt')
493
+ }
494
+ return
495
+ }
496
+
497
+ // For koding mode, only exit when deleting the last character (which would be the '#' character)
498
+ if (mode === 'koding' && (key.backspace || key.delete)) {
499
+ // Check the current input state, not the inputChar parameter
500
+ // If current input is empty, we're about to delete the '#' character, so exit koding mode
501
+ if (input === '') {
502
+ onModeChange('prompt')
503
+ }
504
+ return
505
+ }
506
+
507
+ // For other modes, keep the original behavior
508
+ if (inputChar === '' && (key.escape || key.backspace || key.delete)) {
451
509
  onModeChange('prompt')
452
510
  }
453
511
  // esc is a little overloaded:
@@ -464,22 +522,15 @@ function PromptInput({
464
522
  return true // Explicitly handled
465
523
  }
466
524
 
467
- // Tab key for model switching (simple and non-conflicting)
468
- if (key.tab && !key.shift) {
469
- handleQuickModelSwitch()
470
- return true // Explicitly handled
471
- }
472
-
473
525
  return false // Not handled, allow other hooks
474
526
  })
475
527
 
476
528
  const textInputColumns = useTerminalSize().columns - 6
477
529
  const tokenUsage = useMemo(() => countTokens(messages), [messages])
478
- const theme = getTheme()
479
530
 
480
531
  // 🔧 Fix: Track model ID changes to detect external config updates
481
532
  const modelManager = getModelManager()
482
- const currentModelId = modelManager.getModel('main')?.id || null
533
+ const currentModelId = (modelManager.getModel('main') as any)?.id || null
483
534
 
484
535
  const modelInfo = useMemo(() => {
485
536
  // Force fresh ModelManager instance to detect config changes
@@ -491,7 +542,7 @@ function PromptInput({
491
542
 
492
543
  return {
493
544
  name: currentModel.modelName, // 🔧 Fix: Use actual model name, not display name
494
- id: currentModel.id, // 添加模型ID用于调试
545
+ id: (currentModel as any).id, // 添加模型ID用于调试
495
546
  provider: currentModel.provider, // 添加提供商信息
496
547
  contextLength: currentModel.contextLength,
497
548
  currentTokens: tokenUsage,
@@ -559,14 +610,22 @@ function PromptInput({
559
610
  onImagePaste={onImagePaste}
560
611
  columns={textInputColumns}
561
612
  isDimmed={isDisabled || isLoading}
562
- disableCursorMovementForUpDownKeys={suggestions.length > 0}
613
+ disableCursorMovementForUpDownKeys={completionActive}
563
614
  cursorOffset={cursorOffset}
564
615
  onChangeCursorOffset={setCursorOffset}
565
616
  onPaste={onTextPaste}
617
+ onSpecialKey={(input, key) => {
618
+ // Handle Shift+M for model switching
619
+ if (key.shift && (input === 'M' || input === 'm')) {
620
+ handleQuickModelSwitch()
621
+ return true // Prevent the 'M' from being typed
622
+ }
623
+ return false
624
+ }}
566
625
  />
567
626
  </Box>
568
627
  </Box>
569
- {suggestions.length === 0 && (
628
+ {!completionActive && suggestions.length === 0 && (
570
629
  <Box
571
630
  flexDirection="row"
572
631
  justifyContent="space-between"
@@ -592,15 +651,15 @@ function PromptInput({
592
651
  color={mode === 'koding' ? theme.koding : undefined}
593
652
  dimColor={mode !== 'koding'}
594
653
  >
595
- · # for KODE.md
654
+ · # for AGENTS.md
596
655
  </Text>
597
656
  <Text dimColor>
598
- · / for commands · tab to switch model · esc to undo
657
+ · / for commands · shift+m to switch model · esc to undo
599
658
  </Text>
600
659
  </>
601
660
  )}
602
661
  </Box>
603
- <SentryErrorBoundary>
662
+ <SentryErrorBoundary children={
604
663
  <Box justifyContent="flex-end" gap={1}>
605
664
  {!autoUpdaterResult &&
606
665
  !isAutoUpdating &&
@@ -622,9 +681,10 @@ function PromptInput({
622
681
  onChangeIsUpdating={setIsAutoUpdating}
623
682
  /> */}
624
683
  </Box>
625
- </SentryErrorBoundary>
684
+ } />
626
685
  </Box>
627
686
  )}
687
+ {/* Unified completion suggestions - optimized rendering */}
628
688
  {suggestions.length > 0 && (
629
689
  <Box
630
690
  flexDirection="row"
@@ -633,58 +693,28 @@ function PromptInput({
633
693
  paddingY={0}
634
694
  >
635
695
  <Box flexDirection="column">
636
- {suggestions.map((suggestion, index) => {
637
- const command = commands.find(
638
- cmd => cmd.userFacingName() === suggestion.replace('/', ''),
639
- )
640
- return (
641
- <Box
642
- key={suggestion}
643
- flexDirection={columns < 80 ? 'column' : 'row'}
644
- >
645
- <Box width={columns < 80 ? undefined : commandWidth}>
646
- <Text
647
- color={
648
- index === selectedSuggestion
649
- ? theme.suggestion
650
- : undefined
651
- }
652
- dimColor={index !== selectedSuggestion}
653
- >
654
- /{suggestion}
655
- {command?.aliases && command.aliases.length > 0 && (
656
- <Text dimColor> ({command.aliases.join(', ')})</Text>
657
- )}
658
- </Text>
659
- </Box>
660
- {command && (
661
- <Box
662
- width={columns - (columns < 80 ? 4 : commandWidth + 4)}
663
- paddingLeft={columns < 80 ? 4 : 0}
664
- >
665
- <Text
666
- color={
667
- index === selectedSuggestion
668
- ? theme.suggestion
669
- : undefined
670
- }
671
- dimColor={index !== selectedSuggestion}
672
- wrap="wrap"
673
- >
674
- <Text dimColor={index !== selectedSuggestion}>
675
- {command.description}
676
- {command.type === 'prompt' && command.argNames?.length
677
- ? ` (arguments: ${command.argNames.join(', ')})`
678
- : null}
679
- </Text>
680
- </Text>
681
- </Box>
682
- )}
683
- </Box>
684
- )
685
- })}
696
+ {renderedSuggestions}
697
+
698
+ {/* 简洁操作提示框 */}
699
+ <Box marginTop={1} paddingX={3} borderStyle="round" borderColor="gray">
700
+ <Text dimColor={!emptyDirMessage} color={emptyDirMessage ? "yellow" : undefined}>
701
+ {emptyDirMessage || (() => {
702
+ const selected = suggestions[selectedIndex]
703
+ if (!selected) {
704
+ return '↑↓ navigate • → accept • Tab cycle • Esc close'
705
+ }
706
+ if (selected?.value.endsWith('/')) {
707
+ return '→ enter directory • ↑↓ navigate • Tab cycle • Esc close'
708
+ } else if (selected?.type === 'agent') {
709
+ return '→ select agent • ↑↓ navigate • Tab cycle • Esc close'
710
+ } else {
711
+ return '→ insert reference • ↑↓ navigate • Tab cycle • Esc close'
712
+ }
713
+ })()}
714
+ </Text>
715
+ </Box>
686
716
  </Box>
687
- <SentryErrorBoundary>
717
+ <SentryErrorBoundary children={
688
718
  <Box justifyContent="flex-end" gap={1}>
689
719
  <TokenWarning tokenUsage={countTokens(messages)} />
690
720
  <AutoUpdater
@@ -695,7 +725,7 @@ function PromptInput({
695
725
  onChangeIsUpdating={setIsAutoUpdating}
696
726
  />
697
727
  </Box>
698
- </SentryErrorBoundary>
728
+ } />
699
729
  </Box>
700
730
  )}
701
731
  </Box>
@@ -12,7 +12,7 @@ interface State {
12
12
  export class SentryErrorBoundary extends React.Component<Props, State> {
13
13
  constructor(props: Props) {
14
14
  super(props)
15
- this.state = { hasError: false }
15
+ ;(this as any).state = { hasError: false }
16
16
  }
17
17
 
18
18
  static getDerivedStateFromError(): State {
@@ -24,10 +24,10 @@ export class SentryErrorBoundary extends React.Component<Props, State> {
24
24
  }
25
25
 
26
26
  render(): React.ReactNode {
27
- if (this.state.hasError) {
27
+ if ((this as any).state.hasError) {
28
28
  return null
29
29
  }
30
30
 
31
- return this.props.children
31
+ return (this as any).props.children
32
32
  }
33
33
  }
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+
3
+ export interface FormData {
4
+ // Define form data structure as needed
5
+ [key: string]: any
6
+ }
7
+
8
+ export interface StickerRequestFormProps {
9
+ // Define props as needed
10
+ onSubmit?: (data: FormData) => void
11
+ }
12
+
13
+ export const StickerRequestForm: React.FC<StickerRequestFormProps> = () => {
14
+ // Minimal component implementation
15
+ return null
16
+ }
@@ -10,6 +10,7 @@ type Props = {
10
10
  dim: boolean
11
11
  width: number
12
12
  overrideTheme?: ThemeNames // custom theme for previews
13
+ key?: React.Key
13
14
  }
14
15
 
15
16
  export function StructuredDiff({
@@ -66,43 +67,48 @@ function formatDiff(
66
67
  switch (type) {
67
68
  case 'add':
68
69
  return (
69
- <Text key={key}>
70
- <LineNumber
71
- i={lineIndex === 0 ? i : undefined}
72
- width={maxWidth}
73
- />
74
- <Text
75
- color={overrideTheme ? theme.text : undefined}
76
- backgroundColor={
77
- dim ? theme.diff.addedDimmed : theme.diff.added
78
- }
79
- dimColor={dim}
80
- >
81
- {line}
70
+ <React.Fragment key={key}>
71
+ <Text>
72
+ <LineNumber
73
+ i={lineIndex === 0 ? i : undefined}
74
+ width={maxWidth}
75
+ />
76
+ <Text
77
+ color={overrideTheme ? theme.text : undefined}
78
+ backgroundColor={
79
+ dim ? theme.diff.addedDimmed : theme.diff.added
80
+ }
81
+ dimColor={dim}
82
+ >
83
+ {line}
84
+ </Text>
82
85
  </Text>
83
- </Text>
86
+ </React.Fragment>
84
87
  )
85
88
  case 'remove':
86
89
  return (
87
- <Text key={key}>
88
- <LineNumber
89
- i={lineIndex === 0 ? i : undefined}
90
- width={maxWidth}
91
- />
92
- <Text
93
- color={overrideTheme ? theme.text : undefined}
94
- backgroundColor={
95
- dim ? theme.diff.removedDimmed : theme.diff.removed
96
- }
97
- dimColor={dim}
98
- >
99
- {line}
90
+ <React.Fragment key={key}>
91
+ <Text>
92
+ <LineNumber
93
+ i={lineIndex === 0 ? i : undefined}
94
+ width={maxWidth}
95
+ />
96
+ <Text
97
+ color={overrideTheme ? theme.text : undefined}
98
+ backgroundColor={
99
+ dim ? theme.diff.removedDimmed : theme.diff.removed
100
+ }
101
+ dimColor={dim}
102
+ >
103
+ {line}
104
+ </Text>
100
105
  </Text>
101
- </Text>
106
+ </React.Fragment>
102
107
  )
103
108
  case 'nochange':
104
109
  return (
105
- <Text key={key}>
110
+ <React.Fragment key={key}>
111
+ <Text>
106
112
  <LineNumber
107
113
  i={lineIndex === 0 ? i : undefined}
108
114
  width={maxWidth}
@@ -114,6 +120,7 @@ function formatDiff(
114
120
  {line}
115
121
  </Text>
116
122
  </Text>
123
+ </React.Fragment>
117
124
  )
118
125
  }
119
126
  })
@@ -106,6 +106,12 @@ export type Props = {
106
106
  * Whether to disable cursor movement for up/down arrow keys
107
107
  */
108
108
  readonly disableCursorMovementForUpDownKeys?: boolean
109
+
110
+ /**
111
+ * Optional callback to handle special key combinations before input processing
112
+ * Return true to prevent default handling
113
+ */
114
+ readonly onSpecialKey?: (input: string, key: Key) => boolean
109
115
 
110
116
  readonly cursorOffset: number
111
117
 
@@ -136,6 +142,7 @@ export default function TextInput({
136
142
  onPaste,
137
143
  isDimmed = false,
138
144
  disableCursorMovementForUpDownKeys = false,
145
+ onSpecialKey,
139
146
  cursorOffset,
140
147
  onChangeCursorOffset,
141
148
  }: Props) {
@@ -186,6 +193,12 @@ export default function TextInput({
186
193
  }
187
194
 
188
195
  const wrappedOnInput = (input: string, key: Key): void => {
196
+ // Check for special key combinations first
197
+ if (onSpecialKey && onSpecialKey(input, key)) {
198
+ // Special key was handled, don't process further
199
+ return
200
+ }
201
+
189
202
  // Special handling for backspace or delete
190
203
  if (
191
204
  key.backspace ||
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+
3
+ export interface TodoItemProps {
4
+ // Define props as needed
5
+ children?: React.ReactNode
6
+ }
7
+
8
+ export const TodoItem: React.FC<TodoItemProps> = ({ children }) => {
9
+ // Minimal component implementation
10
+ return <>{children}</>
11
+ }
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
3
  import { getTheme } from '../utils/theme'
4
- import { Select } from '@inkjs/ui'
4
+ import { Select } from './CustomSelect/select'
5
5
  import {
6
6
  saveCurrentProjectConfig,
7
7
  getCurrentProjectConfig,
@@ -20,7 +20,11 @@ export function AssistantLocalCommandOutputMessage({
20
20
  ].filter(Boolean)
21
21
 
22
22
  if (insides.length === 0) {
23
- insides = [<Text key="0">(No output)</Text>]
23
+ insides = [
24
+ <React.Fragment key="0">
25
+ <Text>(No output)</Text>
26
+ </React.Fragment>
27
+ ]
24
28
  }
25
29
 
26
30
  return [
@@ -9,6 +9,7 @@ import { getTheme } from '../../utils/theme'
9
9
  import { BLACK_CIRCLE } from '../../constants/figures'
10
10
  import { ThinkTool } from '../../tools/ThinkTool/ThinkTool'
11
11
  import { AssistantThinkingMessage } from './AssistantThinkingMessage'
12
+ import { TaskToolMessage } from './TaskToolMessage'
12
13
 
13
14
  type Props = {
14
15
  param: ToolUseBlockParam
@@ -61,7 +62,7 @@ export function AssistantToolUseMessage({
61
62
  )
62
63
  }
63
64
 
64
- const userFacingToolName = tool.userFacingName(param.input as never)
65
+ const userFacingToolName = tool.userFacingName ? tool.userFacingName(param.input) : tool.name
65
66
  return (
66
67
  <Box
67
68
  flexDirection="row"
@@ -86,9 +87,18 @@ export function AssistantToolUseMessage({
86
87
  isError={erroredToolUseIDs.has(param.id)}
87
88
  />
88
89
  ))}
89
- <Text color={color} bold={!isQueued}>
90
- {userFacingToolName}
91
- </Text>
90
+ {tool.name === 'Task' && param.input ? (
91
+ <TaskToolMessage
92
+ agentType={(param.input as any).subagent_type || 'general-purpose'}
93
+ bold={!isQueued}
94
+ >
95
+ {userFacingToolName}
96
+ </TaskToolMessage>
97
+ ) : (
98
+ <Text color={color} bold={!isQueued}>
99
+ {userFacingToolName}
100
+ </Text>
101
+ )}
92
102
  </Box>
93
103
  <Box flexWrap="nowrap">
94
104
  {Object.keys(param.input as { [key: string]: unknown }).length > 0 &&
@@ -0,0 +1,32 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { getTheme } from '../../utils/theme'
4
+
5
+ interface Props {
6
+ agentType: string
7
+ status: string
8
+ toolCount?: number
9
+ }
10
+
11
+ export function TaskProgressMessage({ agentType, status, toolCount }: Props) {
12
+ const theme = getTheme()
13
+
14
+ return (
15
+ <Box flexDirection="column" marginTop={1}>
16
+ <Box flexDirection="row">
17
+ <Text color={theme.claude}>⎯ </Text>
18
+ <Text color={theme.text} bold>
19
+ [{agentType}]
20
+ </Text>
21
+ <Text color={theme.secondaryText}> {status}</Text>
22
+ </Box>
23
+ {toolCount && toolCount > 0 && (
24
+ <Box marginLeft={3}>
25
+ <Text color={theme.secondaryText}>
26
+ Tools used: {toolCount}
27
+ </Text>
28
+ </Box>
29
+ )}
30
+ </Box>
31
+ )
32
+ }