@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
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Input Method Inspired Fuzzy Matching Algorithm
3
+ *
4
+ * Multi-algorithm weighted scoring system inspired by:
5
+ * - Sogou/Baidu Pinyin input method algorithms
6
+ * - Double-pinyin abbreviation matching
7
+ * - Terminal completion best practices (fzf, zsh, fish)
8
+ *
9
+ * Designed specifically for command/terminal completion scenarios
10
+ * where users type abbreviations like "nde" expecting "node"
11
+ */
12
+
13
+ export interface MatchResult {
14
+ score: number
15
+ algorithm: string // Which algorithm contributed most to the score
16
+ confidence: number // 0-1 confidence level
17
+ }
18
+
19
+ export interface FuzzyMatcherConfig {
20
+ // Algorithm weights (must sum to 1.0)
21
+ weights: {
22
+ prefix: number // Direct prefix matching ("nod" → "node")
23
+ substring: number // Substring matching ("ode" → "node")
24
+ abbreviation: number // Key chars matching ("nde" → "node")
25
+ editDistance: number // Typo tolerance ("noda" → "node")
26
+ popularity: number // Common command boost
27
+ }
28
+
29
+ // Scoring parameters
30
+ minScore: number // Minimum score threshold
31
+ maxEditDistance: number // Maximum edits allowed
32
+ popularCommands: string[] // Commands to boost
33
+ }
34
+
35
+ const DEFAULT_CONFIG: FuzzyMatcherConfig = {
36
+ weights: {
37
+ prefix: 0.35, // Strong weight for prefix matching
38
+ substring: 0.20, // Good for partial matches
39
+ abbreviation: 0.30, // Key for "nde"→"node" cases
40
+ editDistance: 0.10, // Typo tolerance
41
+ popularity: 0.05 // Slight bias for common commands
42
+ },
43
+ minScore: 10, // Lower threshold for better matching
44
+ maxEditDistance: 2,
45
+ popularCommands: [
46
+ 'node', 'npm', 'git', 'ls', 'cd', 'cat', 'grep', 'find', 'cp', 'mv',
47
+ 'python', 'java', 'docker', 'curl', 'wget', 'vim', 'nano'
48
+ ]
49
+ }
50
+
51
+ export class FuzzyMatcher {
52
+ private config: FuzzyMatcherConfig
53
+
54
+ constructor(config: Partial<FuzzyMatcherConfig> = {}) {
55
+ this.config = { ...DEFAULT_CONFIG, ...config }
56
+
57
+ // Normalize weights to sum to 1.0
58
+ const weightSum = Object.values(this.config.weights).reduce((a, b) => a + b, 0)
59
+ if (Math.abs(weightSum - 1.0) > 0.01) {
60
+ Object.keys(this.config.weights).forEach(key => {
61
+ this.config.weights[key as keyof typeof this.config.weights] /= weightSum
62
+ })
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Calculate fuzzy match score for a candidate against a query
68
+ */
69
+ match(candidate: string, query: string): MatchResult {
70
+ const text = candidate.toLowerCase()
71
+ const pattern = query.toLowerCase()
72
+
73
+ // Quick perfect match exits
74
+ if (text === pattern) {
75
+ return { score: 1000, algorithm: 'exact', confidence: 1.0 }
76
+ }
77
+ if (text.startsWith(pattern)) {
78
+ return {
79
+ score: 900 + (10 - pattern.length),
80
+ algorithm: 'prefix-exact',
81
+ confidence: 0.95
82
+ }
83
+ }
84
+
85
+ // Run all algorithms
86
+ const scores = {
87
+ prefix: this.prefixScore(text, pattern),
88
+ substring: this.substringScore(text, pattern),
89
+ abbreviation: this.abbreviationScore(text, pattern),
90
+ editDistance: this.editDistanceScore(text, pattern),
91
+ popularity: this.popularityScore(text)
92
+ }
93
+
94
+ // Weighted combination
95
+ const rawScore = Object.entries(scores).reduce((total, [algorithm, score]) => {
96
+ const weight = this.config.weights[algorithm as keyof typeof this.config.weights]
97
+ return total + (score * weight)
98
+ }, 0)
99
+
100
+ // Length penalty (prefer shorter commands)
101
+ const lengthPenalty = Math.max(0, text.length - 6) * 1.5
102
+ const finalScore = Math.max(0, rawScore - lengthPenalty)
103
+
104
+ // Determine primary algorithm and confidence
105
+ const maxAlgorithm = Object.entries(scores).reduce((max, [alg, score]) =>
106
+ score > max.score ? { algorithm: alg, score } : max,
107
+ { algorithm: 'none', score: 0 }
108
+ )
109
+
110
+ const confidence = Math.min(1.0, finalScore / 100)
111
+
112
+ return {
113
+ score: finalScore,
114
+ algorithm: maxAlgorithm.algorithm,
115
+ confidence
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Algorithm 1: Prefix Matching (like pinyin prefix)
121
+ * Handles cases like "nod" → "node"
122
+ */
123
+ private prefixScore(text: string, pattern: string): number {
124
+ if (!text.startsWith(pattern)) return 0
125
+
126
+ // Score based on prefix length vs total length
127
+ const coverage = pattern.length / text.length
128
+ return 100 * coverage
129
+ }
130
+
131
+ /**
132
+ * Algorithm 2: Substring Matching (like pinyin contains)
133
+ * Handles cases like "ode" → "node", "py3" → "python3"
134
+ */
135
+ private substringScore(text: string, pattern: string): number {
136
+ // Direct substring match
137
+ const index = text.indexOf(pattern)
138
+ if (index !== -1) {
139
+ // Earlier position and better coverage = higher score
140
+ const positionFactor = Math.max(0, 10 - index) / 10
141
+ const coverageFactor = pattern.length / text.length
142
+ return 80 * positionFactor * coverageFactor
143
+ }
144
+
145
+ // Special handling for numeric suffixes (py3 → python3)
146
+ // Check if pattern ends with a number and try prefix match + number
147
+ const numMatch = pattern.match(/^(.+?)(\d+)$/)
148
+ if (numMatch) {
149
+ const [, prefix, num] = numMatch
150
+ // Check if text starts with prefix and ends with the same number
151
+ if (text.startsWith(prefix) && text.endsWith(num)) {
152
+ // Good match for patterns like "py3" → "python3"
153
+ const coverageFactor = pattern.length / text.length
154
+ return 70 * coverageFactor + 20 // Bonus for numeric suffix match
155
+ }
156
+ }
157
+
158
+ return 0
159
+ }
160
+
161
+ /**
162
+ * Algorithm 3: Abbreviation Matching (key innovation)
163
+ * Handles cases like "nde" → "node", "pyt3" → "python3", "gp5" → "gpt-5"
164
+ */
165
+ private abbreviationScore(text: string, pattern: string): number {
166
+ let score = 0
167
+ let textPos = 0
168
+ let perfectStart = false
169
+ let consecutiveMatches = 0
170
+ let wordBoundaryMatches = 0
171
+
172
+ // Split text by hyphens to handle word boundaries better
173
+ const textWords = text.split('-')
174
+ const textClean = text.replace(/-/g, '').toLowerCase()
175
+
176
+ for (let i = 0; i < pattern.length; i++) {
177
+ const char = pattern[i]
178
+ let charFound = false
179
+
180
+ // Try to find in clean text (no hyphens)
181
+ for (let j = textPos; j < textClean.length; j++) {
182
+ if (textClean[j] === char) {
183
+ charFound = true
184
+
185
+ // Check if this character is at a word boundary in original text
186
+ let originalPos = 0
187
+ let cleanPos = 0
188
+ for (let k = 0; k < text.length; k++) {
189
+ if (text[k] === '-') continue
190
+ if (cleanPos === j) {
191
+ originalPos = k
192
+ break
193
+ }
194
+ cleanPos++
195
+ }
196
+
197
+ // Consecutive character bonus
198
+ if (j === textPos) {
199
+ consecutiveMatches++
200
+ } else {
201
+ consecutiveMatches = 1
202
+ }
203
+
204
+ // Position-sensitive scoring
205
+ if (i === 0 && j === 0) {
206
+ score += 50 // Perfect first character
207
+ perfectStart = true
208
+ } else if (originalPos === 0 || text[originalPos - 1] === '-') {
209
+ score += 35 // Word boundary match
210
+ wordBoundaryMatches++
211
+ } else if (j <= 2) {
212
+ score += 20 // Early position
213
+ } else if (j <= 6) {
214
+ score += 10 // Mid position
215
+ } else {
216
+ score += 5 // Late position
217
+ }
218
+
219
+ // Consecutive character bonus
220
+ if (consecutiveMatches > 1) {
221
+ score += consecutiveMatches * 5
222
+ }
223
+
224
+ textPos = j + 1
225
+ break
226
+ }
227
+ }
228
+
229
+ if (!charFound) return 0 // Invalid abbreviation
230
+ }
231
+
232
+ // Critical bonuses
233
+ if (perfectStart) score += 30
234
+ if (wordBoundaryMatches >= 2) score += 25 // Multiple word boundaries
235
+ if (textPos <= textClean.length * 0.8) score += 15 // Compact abbreviation
236
+
237
+ // Special bonus for number matching at end
238
+ const lastPatternChar = pattern[pattern.length - 1]
239
+ const lastTextChar = text[text.length - 1]
240
+ if (/\d/.test(lastPatternChar) && lastPatternChar === lastTextChar) {
241
+ score += 25
242
+ }
243
+
244
+ return score
245
+ }
246
+
247
+ /**
248
+ * Algorithm 4: Edit Distance (typo tolerance)
249
+ * Handles cases like "noda" → "node"
250
+ */
251
+ private editDistanceScore(text: string, pattern: string): number {
252
+ if (pattern.length > text.length + this.config.maxEditDistance) return 0
253
+
254
+ // Simplified Levenshtein distance
255
+ const dp: number[][] = []
256
+ const m = pattern.length
257
+ const n = text.length
258
+
259
+ // Initialize DP table
260
+ for (let i = 0; i <= m; i++) {
261
+ dp[i] = []
262
+ for (let j = 0; j <= n; j++) {
263
+ if (i === 0) dp[i][j] = j
264
+ else if (j === 0) dp[i][j] = i
265
+ else {
266
+ const cost = pattern[i-1] === text[j-1] ? 0 : 1
267
+ dp[i][j] = Math.min(
268
+ dp[i-1][j] + 1, // deletion
269
+ dp[i][j-1] + 1, // insertion
270
+ dp[i-1][j-1] + cost // substitution
271
+ )
272
+ }
273
+ }
274
+ }
275
+
276
+ const distance = dp[m][n]
277
+ if (distance > this.config.maxEditDistance) return 0
278
+
279
+ return Math.max(0, 30 - distance * 10)
280
+ }
281
+
282
+ /**
283
+ * Algorithm 5: Command Popularity (like frequency in input method)
284
+ * Boost common commands that users frequently type
285
+ */
286
+ private popularityScore(text: string): number {
287
+ if (this.config.popularCommands.includes(text)) {
288
+ return 40
289
+ }
290
+
291
+ // Short commands are often more commonly used
292
+ if (text.length <= 5) return 10
293
+
294
+ return 0
295
+ }
296
+
297
+ /**
298
+ * Batch match multiple candidates and return sorted results
299
+ */
300
+ matchMany(candidates: string[], query: string): Array<{candidate: string, result: MatchResult}> {
301
+ return candidates
302
+ .map(candidate => ({
303
+ candidate,
304
+ result: this.match(candidate, query)
305
+ }))
306
+ .filter(item => item.result.score >= this.config.minScore)
307
+ .sort((a, b) => b.result.score - a.result.score)
308
+ }
309
+ }
310
+
311
+ // Export convenience functions
312
+ export const defaultMatcher = new FuzzyMatcher()
313
+
314
+ export function matchCommand(command: string, query: string): MatchResult {
315
+ return defaultMatcher.match(command, query)
316
+ }
317
+
318
+ // Import the advanced matcher
319
+ import { matchManyAdvanced } from './advancedFuzzyMatcher'
320
+
321
+ export function matchCommands(commands: string[], query: string): Array<{command: string, score: number}> {
322
+ // Use the advanced matcher for better results
323
+ return matchManyAdvanced(commands, query, 5) // Lower threshold for better matching
324
+ .map(item => ({
325
+ command: item.candidate,
326
+ score: item.score
327
+ }))
328
+ }
@@ -355,7 +355,7 @@ export async function processUserInput(
355
355
  if (input.includes('!`') || input.includes('@')) {
356
356
  try {
357
357
  // Import functions from customCommands service to avoid code duplication
358
- const { executeBashCommands, resolveFileReferences } = await import(
358
+ const { executeBashCommands } = await import(
359
359
  '../services/customCommands'
360
360
  )
361
361
 
@@ -366,11 +366,12 @@ export async function processUserInput(
366
366
  processedInput = await executeBashCommands(processedInput)
367
367
  }
368
368
 
369
- // Resolve file references if present
369
+ // Process mentions for system reminder integration
370
+ // Note: We don't call resolveFileReferences here anymore -
371
+ // @file mentions should trigger Read tool usage via reminders, not embed content
370
372
  if (input.includes('@')) {
371
- // Note: This function is not exported from customCommands.ts, so we need to expose it
372
- // For now, we'll keep the local implementation until we refactor the service
373
- processedInput = await resolveFileReferences(processedInput)
373
+ const { processMentions } = await import('../services/mentionProcessor')
374
+ await processMentions(input)
374
375
  }
375
376
  } catch (error) {
376
377
  console.warn('Dynamic content processing failed:', error)
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Response state management for Responses API
3
+ * Tracks previous_response_id for conversation chaining
4
+ */
5
+
6
+ // Store the last response ID for each conversation
7
+ const responseIdCache = new Map<string, string>()
8
+
9
+ export function getLastResponseId(conversationId: string): string | undefined {
10
+ return responseIdCache.get(conversationId)
11
+ }
12
+
13
+ export function setLastResponseId(conversationId: string, responseId: string): void {
14
+ responseIdCache.set(conversationId, responseId)
15
+ }
16
+
17
+ export function clearResponseId(conversationId: string): void {
18
+ responseIdCache.delete(conversationId)
19
+ }
20
+
21
+ export function clearAllResponseIds(): void {
22
+ responseIdCache.clear()
23
+ }