@shareai-lab/kode 1.0.71 → 1.0.75
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.
- package/README.md +160 -1
- package/README.zh-CN.md +65 -1
- package/cli.js +5 -10
- package/package.json +6 -2
- package/src/ProjectOnboarding.tsx +47 -29
- package/src/Tool.ts +33 -4
- package/src/commands/agents.tsx +3401 -0
- package/src/commands/help.tsx +2 -2
- package/src/commands/resume.tsx +2 -1
- package/src/commands/terminalSetup.ts +4 -4
- package/src/commands.ts +3 -0
- package/src/components/ApproveApiKey.tsx +1 -1
- package/src/components/Config.tsx +10 -6
- package/src/components/ConsoleOAuthFlow.tsx +5 -4
- package/src/components/CustomSelect/select-option.tsx +28 -2
- package/src/components/CustomSelect/select.tsx +14 -5
- package/src/components/CustomSelect/theme.ts +45 -0
- package/src/components/Help.tsx +4 -4
- package/src/components/InvalidConfigDialog.tsx +1 -1
- package/src/components/LogSelector.tsx +1 -1
- package/src/components/MCPServerApprovalDialog.tsx +1 -1
- package/src/components/Message.tsx +2 -0
- package/src/components/ModelListManager.tsx +10 -6
- package/src/components/ModelSelector.tsx +201 -23
- package/src/components/ModelStatusDisplay.tsx +7 -5
- package/src/components/PromptInput.tsx +146 -96
- package/src/components/SentryErrorBoundary.ts +9 -3
- package/src/components/StickerRequestForm.tsx +16 -0
- package/src/components/StructuredDiff.tsx +36 -29
- package/src/components/TextInput.tsx +13 -0
- package/src/components/TodoItem.tsx +47 -0
- package/src/components/TrustDialog.tsx +1 -1
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +5 -1
- package/src/components/messages/AssistantToolUseMessage.tsx +14 -4
- package/src/components/messages/TaskProgressMessage.tsx +32 -0
- package/src/components/messages/TaskToolMessage.tsx +58 -0
- package/src/components/permissions/FallbackPermissionRequest.tsx +2 -4
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +1 -1
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +5 -3
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +1 -1
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +5 -3
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +2 -4
- package/src/components/permissions/PermissionRequest.tsx +3 -5
- package/src/constants/macros.ts +2 -0
- package/src/constants/modelCapabilities.ts +179 -0
- package/src/constants/models.ts +90 -0
- package/src/constants/product.ts +1 -1
- package/src/context.ts +7 -7
- package/src/entrypoints/cli.tsx +23 -3
- package/src/entrypoints/mcp.ts +10 -10
- package/src/hooks/useCanUseTool.ts +1 -1
- package/src/hooks/useTextInput.ts +5 -2
- package/src/hooks/useUnifiedCompletion.ts +1405 -0
- package/src/messages.ts +1 -0
- package/src/query.ts +3 -0
- package/src/screens/ConfigureNpmPrefix.tsx +1 -1
- package/src/screens/Doctor.tsx +1 -1
- package/src/screens/REPL.tsx +11 -12
- package/src/services/adapters/base.ts +38 -0
- package/src/services/adapters/chatCompletions.ts +90 -0
- package/src/services/adapters/responsesAPI.ts +170 -0
- package/src/services/claude.ts +198 -62
- package/src/services/customCommands.ts +43 -22
- package/src/services/gpt5ConnectionTest.ts +340 -0
- package/src/services/mcpClient.ts +1 -1
- package/src/services/mentionProcessor.ts +273 -0
- package/src/services/modelAdapterFactory.ts +69 -0
- package/src/services/openai.ts +534 -14
- package/src/services/responseStateManager.ts +90 -0
- package/src/services/systemReminder.ts +113 -12
- package/src/test/testAdapters.ts +96 -0
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +120 -56
- package/src/tools/BashTool/BashTool.tsx +4 -31
- package/src/tools/BashTool/BashToolResultMessage.tsx +1 -1
- package/src/tools/BashTool/OutputLine.tsx +1 -0
- package/src/tools/FileEditTool/FileEditTool.tsx +4 -5
- package/src/tools/FileReadTool/FileReadTool.tsx +43 -10
- package/src/tools/MCPTool/MCPTool.tsx +2 -1
- package/src/tools/MultiEditTool/MultiEditTool.tsx +2 -2
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +15 -23
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +1 -1
- package/src/tools/TaskTool/TaskTool.tsx +170 -86
- package/src/tools/TaskTool/prompt.ts +61 -25
- package/src/tools/ThinkTool/ThinkTool.tsx +1 -3
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +65 -41
- package/src/tools/lsTool/lsTool.tsx +5 -2
- package/src/tools.ts +16 -16
- package/src/types/conversation.ts +51 -0
- package/src/types/logs.ts +58 -0
- package/src/types/modelCapabilities.ts +64 -0
- package/src/types/notebook.ts +87 -0
- package/src/utils/advancedFuzzyMatcher.ts +290 -0
- package/src/utils/agentLoader.ts +284 -0
- package/src/utils/ask.tsx +1 -0
- package/src/utils/commands.ts +1 -1
- package/src/utils/commonUnixCommands.ts +161 -0
- package/src/utils/config.ts +173 -2
- package/src/utils/conversationRecovery.ts +1 -0
- package/src/utils/debugLogger.ts +13 -13
- package/src/utils/exampleCommands.ts +1 -0
- package/src/utils/fuzzyMatcher.ts +328 -0
- package/src/utils/messages.tsx +6 -5
- package/src/utils/model.ts +120 -42
- package/src/utils/responseState.ts +23 -0
- package/src/utils/secureFile.ts +559 -0
- package/src/utils/terminal.ts +1 -0
- package/src/utils/theme.ts +11 -0
- 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
|
-
<
|
|
199
|
-
{
|
|
200
|
-
|
|
201
|
-
|
|
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 {
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} =
|
|
175
|
-
|
|
171
|
+
selectedIndex,
|
|
172
|
+
isActive: completionActive,
|
|
173
|
+
emptyDirMessage,
|
|
174
|
+
} = useUnifiedCompletion({
|
|
175
|
+
input,
|
|
176
|
+
cursorOffset,
|
|
176
177
|
onInputChange,
|
|
177
|
-
onSubmit,
|
|
178
178
|
setCursorOffset,
|
|
179
|
-
|
|
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,26 +222,27 @@ function PromptInput({
|
|
|
189
222
|
onModeChange('koding')
|
|
190
223
|
return
|
|
191
224
|
}
|
|
192
|
-
updateSuggestions(value)
|
|
193
225
|
onInputChange(value)
|
|
194
226
|
},
|
|
195
|
-
[onModeChange, onInputChange
|
|
227
|
+
[onModeChange, onInputChange],
|
|
196
228
|
)
|
|
197
229
|
|
|
198
|
-
// Handle
|
|
230
|
+
// Handle Shift+M model switching with enhanced debugging
|
|
199
231
|
const handleQuickModelSwitch = useCallback(async () => {
|
|
200
232
|
const modelManager = getModelManager()
|
|
201
233
|
const currentTokens = countTokens(messages)
|
|
202
234
|
|
|
235
|
+
// Get debug info for better error reporting
|
|
236
|
+
const debugInfo = modelManager.getModelSwitchingDebugInfo()
|
|
237
|
+
|
|
203
238
|
const switchResult = modelManager.switchToNextModel(currentTokens)
|
|
204
239
|
|
|
205
240
|
if (switchResult.success && switchResult.modelName) {
|
|
206
|
-
// Successful switch
|
|
241
|
+
// Successful switch - use enhanced message from model manager
|
|
207
242
|
onSubmitCountChange(prev => prev + 1)
|
|
208
|
-
const newModel = modelManager.getModel('main')
|
|
209
243
|
setModelSwitchMessage({
|
|
210
244
|
show: true,
|
|
211
|
-
text: `✅ Switched to ${switchResult.modelName}
|
|
245
|
+
text: switchResult.message || `✅ Switched to ${switchResult.modelName}`,
|
|
212
246
|
})
|
|
213
247
|
setTimeout(() => setModelSwitchMessage({ show: false }), 3000)
|
|
214
248
|
} else if (switchResult.blocked && switchResult.message) {
|
|
@@ -219,14 +253,28 @@ function PromptInput({
|
|
|
219
253
|
})
|
|
220
254
|
setTimeout(() => setModelSwitchMessage({ show: false }), 5000)
|
|
221
255
|
} else {
|
|
222
|
-
//
|
|
256
|
+
// Enhanced error reporting with debug info
|
|
257
|
+
let errorMessage = switchResult.message
|
|
258
|
+
|
|
259
|
+
if (!errorMessage) {
|
|
260
|
+
if (debugInfo.totalModels === 0) {
|
|
261
|
+
errorMessage = '❌ No models configured. Use /model to add models.'
|
|
262
|
+
} else if (debugInfo.activeModels === 0) {
|
|
263
|
+
errorMessage = `❌ No active models (${debugInfo.totalModels} total, all inactive). Use /model to activate models.`
|
|
264
|
+
} else if (debugInfo.activeModels === 1) {
|
|
265
|
+
// Show ALL models including inactive ones for debugging
|
|
266
|
+
const allModelNames = debugInfo.availableModels.map(m => `${m.name}${m.isActive ? '' : ' (inactive)'}`).join(', ')
|
|
267
|
+
errorMessage = `⚠️ Only 1 active model out of ${debugInfo.totalModels} total models: ${allModelNames}. ALL configured models will be activated for switching.`
|
|
268
|
+
} else {
|
|
269
|
+
errorMessage = `❌ Model switching failed (${debugInfo.activeModels} active, ${debugInfo.totalModels} total models available)`
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
223
273
|
setModelSwitchMessage({
|
|
224
274
|
show: true,
|
|
225
|
-
text:
|
|
226
|
-
switchResult.message ||
|
|
227
|
-
'⚠️ No other models configured. Use /model to add more models',
|
|
275
|
+
text: errorMessage,
|
|
228
276
|
})
|
|
229
|
-
setTimeout(() => setModelSwitchMessage({ show: false }),
|
|
277
|
+
setTimeout(() => setModelSwitchMessage({ show: false }), 6000)
|
|
230
278
|
}
|
|
231
279
|
}, [onSubmitCountChange, messages])
|
|
232
280
|
|
|
@@ -238,15 +286,15 @@ function PromptInput({
|
|
|
238
286
|
input,
|
|
239
287
|
)
|
|
240
288
|
|
|
241
|
-
// Only use history navigation when there are
|
|
289
|
+
// Only use history navigation when there are no suggestions
|
|
242
290
|
const handleHistoryUp = () => {
|
|
243
|
-
if (
|
|
291
|
+
if (!completionActive) {
|
|
244
292
|
onHistoryUp()
|
|
245
293
|
}
|
|
246
294
|
}
|
|
247
295
|
|
|
248
296
|
const handleHistoryDown = () => {
|
|
249
|
-
if (
|
|
297
|
+
if (!completionActive) {
|
|
250
298
|
onHistoryDown()
|
|
251
299
|
}
|
|
252
300
|
}
|
|
@@ -270,7 +318,7 @@ function PromptInput({
|
|
|
270
318
|
|
|
271
319
|
// Create additional context to inform Claude this is for KODING.md
|
|
272
320
|
const kodingContext =
|
|
273
|
-
'The user is using Koding mode. Format your response as a comprehensive, well-structured document suitable for adding to
|
|
321
|
+
'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
322
|
|
|
275
323
|
// Switch to prompt mode but tag the submission for later capture
|
|
276
324
|
onModeChange('prompt')
|
|
@@ -326,7 +374,7 @@ function PromptInput({
|
|
|
326
374
|
}
|
|
327
375
|
}
|
|
328
376
|
|
|
329
|
-
// If in koding mode or input starts with '#', interpret it using AI before appending to
|
|
377
|
+
// If in koding mode or input starts with '#', interpret it using AI before appending to AGENTS.md
|
|
330
378
|
else if (mode === 'koding' || input.startsWith('#')) {
|
|
331
379
|
try {
|
|
332
380
|
// Strip the # if we're in koding mode and the user didn't type it (since it's implied)
|
|
@@ -354,7 +402,12 @@ function PromptInput({
|
|
|
354
402
|
if (isLoading) {
|
|
355
403
|
return
|
|
356
404
|
}
|
|
357
|
-
|
|
405
|
+
|
|
406
|
+
// Handle Enter key when completions are active
|
|
407
|
+
// If there are suggestions showing, Enter should complete the selection, not send the message
|
|
408
|
+
if (suggestions.length > 0 && completionActive) {
|
|
409
|
+
// The completion is handled by useUnifiedCompletion hook
|
|
410
|
+
// Just return to prevent message sending
|
|
358
411
|
return
|
|
359
412
|
}
|
|
360
413
|
|
|
@@ -373,7 +426,7 @@ function PromptInput({
|
|
|
373
426
|
}
|
|
374
427
|
onInputChange('')
|
|
375
428
|
onModeChange('prompt')
|
|
376
|
-
|
|
429
|
+
// Suggestions are now handled by unified completion
|
|
377
430
|
setPastedImage(null)
|
|
378
431
|
setPastedText(null)
|
|
379
432
|
onSubmitCountChange(_ => _ + 1)
|
|
@@ -446,8 +499,29 @@ function PromptInput({
|
|
|
446
499
|
setPastedText(text)
|
|
447
500
|
}
|
|
448
501
|
|
|
449
|
-
useInput((
|
|
450
|
-
|
|
502
|
+
useInput((inputChar, key) => {
|
|
503
|
+
// For bash mode, only exit when deleting the last character (which would be the '!' character)
|
|
504
|
+
if (mode === 'bash' && (key.backspace || key.delete)) {
|
|
505
|
+
// Check the current input state, not the inputChar parameter
|
|
506
|
+
// If current input is empty, we're about to delete the '!' character, so exit bash mode
|
|
507
|
+
if (input === '') {
|
|
508
|
+
onModeChange('prompt')
|
|
509
|
+
}
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// For koding mode, only exit when deleting the last character (which would be the '#' character)
|
|
514
|
+
if (mode === 'koding' && (key.backspace || key.delete)) {
|
|
515
|
+
// Check the current input state, not the inputChar parameter
|
|
516
|
+
// If current input is empty, we're about to delete the '#' character, so exit koding mode
|
|
517
|
+
if (input === '') {
|
|
518
|
+
onModeChange('prompt')
|
|
519
|
+
}
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// For other modes, keep the original behavior
|
|
524
|
+
if (inputChar === '' && (key.escape || key.backspace || key.delete)) {
|
|
451
525
|
onModeChange('prompt')
|
|
452
526
|
}
|
|
453
527
|
// esc is a little overloaded:
|
|
@@ -464,22 +538,26 @@ function PromptInput({
|
|
|
464
538
|
return true // Explicitly handled
|
|
465
539
|
}
|
|
466
540
|
|
|
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
541
|
return false // Not handled, allow other hooks
|
|
474
542
|
})
|
|
475
543
|
|
|
544
|
+
// Handle special key combinations before character input
|
|
545
|
+
const handleSpecialKey = useCallback((inputChar: string, key: any): boolean => {
|
|
546
|
+
// Shift+M for model switching - intercept before character input
|
|
547
|
+
if (key.shift && (inputChar === 'M' || inputChar === 'm')) {
|
|
548
|
+
handleQuickModelSwitch()
|
|
549
|
+
return true // Prevent character from being input
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return false // Not handled, allow normal processing
|
|
553
|
+
}, [handleQuickModelSwitch])
|
|
554
|
+
|
|
476
555
|
const textInputColumns = useTerminalSize().columns - 6
|
|
477
556
|
const tokenUsage = useMemo(() => countTokens(messages), [messages])
|
|
478
|
-
const theme = getTheme()
|
|
479
557
|
|
|
480
558
|
// 🔧 Fix: Track model ID changes to detect external config updates
|
|
481
559
|
const modelManager = getModelManager()
|
|
482
|
-
const currentModelId = modelManager.getModel('main')?.id || null
|
|
560
|
+
const currentModelId = (modelManager.getModel('main') as any)?.id || null
|
|
483
561
|
|
|
484
562
|
const modelInfo = useMemo(() => {
|
|
485
563
|
// Force fresh ModelManager instance to detect config changes
|
|
@@ -491,7 +569,7 @@ function PromptInput({
|
|
|
491
569
|
|
|
492
570
|
return {
|
|
493
571
|
name: currentModel.modelName, // 🔧 Fix: Use actual model name, not display name
|
|
494
|
-
id: currentModel.id, // 添加模型ID用于调试
|
|
572
|
+
id: (currentModel as any).id, // 添加模型ID用于调试
|
|
495
573
|
provider: currentModel.provider, // 添加提供商信息
|
|
496
574
|
contextLength: currentModel.contextLength,
|
|
497
575
|
currentTokens: tokenUsage,
|
|
@@ -559,14 +637,15 @@ function PromptInput({
|
|
|
559
637
|
onImagePaste={onImagePaste}
|
|
560
638
|
columns={textInputColumns}
|
|
561
639
|
isDimmed={isDisabled || isLoading}
|
|
562
|
-
disableCursorMovementForUpDownKeys={
|
|
640
|
+
disableCursorMovementForUpDownKeys={completionActive}
|
|
563
641
|
cursorOffset={cursorOffset}
|
|
564
642
|
onChangeCursorOffset={setCursorOffset}
|
|
565
643
|
onPaste={onTextPaste}
|
|
644
|
+
onSpecialKey={handleSpecialKey}
|
|
566
645
|
/>
|
|
567
646
|
</Box>
|
|
568
647
|
</Box>
|
|
569
|
-
{suggestions.length === 0 && (
|
|
648
|
+
{!completionActive && suggestions.length === 0 && (
|
|
570
649
|
<Box
|
|
571
650
|
flexDirection="row"
|
|
572
651
|
justifyContent="space-between"
|
|
@@ -592,15 +671,15 @@ function PromptInput({
|
|
|
592
671
|
color={mode === 'koding' ? theme.koding : undefined}
|
|
593
672
|
dimColor={mode !== 'koding'}
|
|
594
673
|
>
|
|
595
|
-
· # for
|
|
674
|
+
· # for AGENTS.md
|
|
596
675
|
</Text>
|
|
597
676
|
<Text dimColor>
|
|
598
|
-
· / for commands ·
|
|
677
|
+
· / for commands · shift+m to switch model · esc to undo
|
|
599
678
|
</Text>
|
|
600
679
|
</>
|
|
601
680
|
)}
|
|
602
681
|
</Box>
|
|
603
|
-
<SentryErrorBoundary
|
|
682
|
+
<SentryErrorBoundary children={
|
|
604
683
|
<Box justifyContent="flex-end" gap={1}>
|
|
605
684
|
{!autoUpdaterResult &&
|
|
606
685
|
!isAutoUpdating &&
|
|
@@ -622,9 +701,10 @@ function PromptInput({
|
|
|
622
701
|
onChangeIsUpdating={setIsAutoUpdating}
|
|
623
702
|
/> */}
|
|
624
703
|
</Box>
|
|
625
|
-
|
|
704
|
+
} />
|
|
626
705
|
</Box>
|
|
627
706
|
)}
|
|
707
|
+
{/* Unified completion suggestions - optimized rendering */}
|
|
628
708
|
{suggestions.length > 0 && (
|
|
629
709
|
<Box
|
|
630
710
|
flexDirection="row"
|
|
@@ -633,58 +713,28 @@ function PromptInput({
|
|
|
633
713
|
paddingY={0}
|
|
634
714
|
>
|
|
635
715
|
<Box flexDirection="column">
|
|
636
|
-
{
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
-
})}
|
|
716
|
+
{renderedSuggestions}
|
|
717
|
+
|
|
718
|
+
{/* 简洁操作提示框 */}
|
|
719
|
+
<Box marginTop={1} paddingX={3} borderStyle="round" borderColor="gray">
|
|
720
|
+
<Text dimColor={!emptyDirMessage} color={emptyDirMessage ? "yellow" : undefined}>
|
|
721
|
+
{emptyDirMessage || (() => {
|
|
722
|
+
const selected = suggestions[selectedIndex]
|
|
723
|
+
if (!selected) {
|
|
724
|
+
return '↑↓ navigate • → accept • Tab cycle • Esc close'
|
|
725
|
+
}
|
|
726
|
+
if (selected?.value.endsWith('/')) {
|
|
727
|
+
return '→ enter directory • ↑↓ navigate • Tab cycle • Esc close'
|
|
728
|
+
} else if (selected?.type === 'agent') {
|
|
729
|
+
return '→ select agent • ↑↓ navigate • Tab cycle • Esc close'
|
|
730
|
+
} else {
|
|
731
|
+
return '→ insert reference • ↑↓ navigate • Tab cycle • Esc close'
|
|
732
|
+
}
|
|
733
|
+
})()}
|
|
734
|
+
</Text>
|
|
735
|
+
</Box>
|
|
686
736
|
</Box>
|
|
687
|
-
<SentryErrorBoundary
|
|
737
|
+
<SentryErrorBoundary children={
|
|
688
738
|
<Box justifyContent="flex-end" gap={1}>
|
|
689
739
|
<TokenWarning tokenUsage={countTokens(messages)} />
|
|
690
740
|
<AutoUpdater
|
|
@@ -695,7 +745,7 @@ function PromptInput({
|
|
|
695
745
|
onChangeIsUpdating={setIsAutoUpdating}
|
|
696
746
|
/>
|
|
697
747
|
</Box>
|
|
698
|
-
|
|
748
|
+
} />
|
|
699
749
|
</Box>
|
|
700
750
|
)}
|
|
701
751
|
</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 {
|
|
@@ -20,14 +20,20 @@ export class SentryErrorBoundary extends React.Component<Props, State> {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
componentDidCatch(error: Error): void {
|
|
23
|
+
// Don't report user-initiated cancellations to Sentry
|
|
24
|
+
if (error.name === 'AbortError' ||
|
|
25
|
+
error.message?.includes('abort') ||
|
|
26
|
+
error.message?.includes('The operation was aborted')) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
23
29
|
captureException(error)
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
render(): React.ReactNode {
|
|
27
|
-
if (this.state.hasError) {
|
|
33
|
+
if ((this as any).state.hasError) {
|
|
28
34
|
return null
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
return this.props.children
|
|
37
|
+
return (this as any).props.children
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -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
|
-
<
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
</
|
|
86
|
+
</React.Fragment>
|
|
84
87
|
)
|
|
85
88
|
case 'remove':
|
|
86
89
|
return (
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
</
|
|
106
|
+
</React.Fragment>
|
|
102
107
|
)
|
|
103
108
|
case 'nochange':
|
|
104
109
|
return (
|
|
105
|
-
<
|
|
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,47 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Box, Text } from 'ink'
|
|
3
|
+
import type { TodoItem as TodoItemType } from '../utils/todoStorage'
|
|
4
|
+
|
|
5
|
+
export interface TodoItemProps {
|
|
6
|
+
todo: TodoItemType
|
|
7
|
+
children?: React.ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const TodoItem: React.FC<TodoItemProps> = ({ todo, children }) => {
|
|
11
|
+
const statusIconMap = {
|
|
12
|
+
completed: '✅',
|
|
13
|
+
in_progress: '🔄',
|
|
14
|
+
pending: '⏸️',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const statusColorMap = {
|
|
18
|
+
completed: '#008000',
|
|
19
|
+
in_progress: '#FFA500',
|
|
20
|
+
pending: '#FFD700',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const priorityIconMap = {
|
|
24
|
+
high: '🔴',
|
|
25
|
+
medium: '🟡',
|
|
26
|
+
low: '🟢',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const icon = statusIconMap[todo.status]
|
|
30
|
+
const color = statusColorMap[todo.status]
|
|
31
|
+
const priorityIcon = todo.priority ? priorityIconMap[todo.priority] : ''
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Box flexDirection="row" gap={1}>
|
|
35
|
+
<Text color={color}>{icon}</Text>
|
|
36
|
+
{priorityIcon && <Text>{priorityIcon}</Text>}
|
|
37
|
+
<Text
|
|
38
|
+
color={color}
|
|
39
|
+
strikethrough={todo.status === 'completed'}
|
|
40
|
+
bold={todo.status === 'in_progress'}
|
|
41
|
+
>
|
|
42
|
+
{todo.content}
|
|
43
|
+
</Text>
|
|
44
|
+
{children}
|
|
45
|
+
</Box>
|
|
46
|
+
)
|
|
47
|
+
}
|