@shareai-lab/kode 1.0.70 → 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 (278) hide show
  1. package/README.md +342 -75
  2. package/README.zh-CN.md +292 -0
  3. package/cli.js +62 -0
  4. package/package.json +49 -25
  5. package/scripts/postinstall.js +56 -0
  6. package/src/ProjectOnboarding.tsx +198 -0
  7. package/src/Tool.ts +82 -0
  8. package/src/commands/agents.tsx +3401 -0
  9. package/src/commands/approvedTools.ts +53 -0
  10. package/src/commands/bug.tsx +20 -0
  11. package/src/commands/clear.ts +43 -0
  12. package/src/commands/compact.ts +120 -0
  13. package/src/commands/config.tsx +19 -0
  14. package/src/commands/cost.ts +18 -0
  15. package/src/commands/ctx_viz.ts +209 -0
  16. package/src/commands/doctor.ts +24 -0
  17. package/src/commands/help.tsx +19 -0
  18. package/src/commands/init.ts +37 -0
  19. package/src/commands/listen.ts +42 -0
  20. package/src/commands/login.tsx +51 -0
  21. package/src/commands/logout.tsx +40 -0
  22. package/src/commands/mcp.ts +41 -0
  23. package/src/commands/model.tsx +40 -0
  24. package/src/commands/modelstatus.tsx +20 -0
  25. package/src/commands/onboarding.tsx +34 -0
  26. package/src/commands/pr_comments.ts +59 -0
  27. package/src/commands/refreshCommands.ts +54 -0
  28. package/src/commands/release-notes.ts +34 -0
  29. package/src/commands/resume.tsx +31 -0
  30. package/src/commands/review.ts +49 -0
  31. package/src/commands/terminalSetup.ts +221 -0
  32. package/src/commands.ts +139 -0
  33. package/src/components/ApproveApiKey.tsx +93 -0
  34. package/src/components/AsciiLogo.tsx +13 -0
  35. package/src/components/AutoUpdater.tsx +148 -0
  36. package/src/components/Bug.tsx +367 -0
  37. package/src/components/Config.tsx +293 -0
  38. package/src/components/ConsoleOAuthFlow.tsx +327 -0
  39. package/src/components/Cost.tsx +23 -0
  40. package/src/components/CostThresholdDialog.tsx +46 -0
  41. package/src/components/CustomSelect/option-map.ts +42 -0
  42. package/src/components/CustomSelect/select-option.tsx +78 -0
  43. package/src/components/CustomSelect/select.tsx +152 -0
  44. package/src/components/CustomSelect/theme.ts +45 -0
  45. package/src/components/CustomSelect/use-select-state.ts +414 -0
  46. package/src/components/CustomSelect/use-select.ts +35 -0
  47. package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
  48. package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
  49. package/src/components/Help.tsx +215 -0
  50. package/src/components/HighlightedCode.tsx +33 -0
  51. package/src/components/InvalidConfigDialog.tsx +113 -0
  52. package/src/components/Link.tsx +32 -0
  53. package/src/components/LogSelector.tsx +86 -0
  54. package/src/components/Logo.tsx +145 -0
  55. package/src/components/MCPServerApprovalDialog.tsx +100 -0
  56. package/src/components/MCPServerDialogCopy.tsx +25 -0
  57. package/src/components/MCPServerMultiselectDialog.tsx +109 -0
  58. package/src/components/Message.tsx +221 -0
  59. package/src/components/MessageResponse.tsx +15 -0
  60. package/src/components/MessageSelector.tsx +211 -0
  61. package/src/components/ModeIndicator.tsx +88 -0
  62. package/src/components/ModelConfig.tsx +301 -0
  63. package/src/components/ModelListManager.tsx +227 -0
  64. package/src/components/ModelSelector.tsx +3386 -0
  65. package/src/components/ModelStatusDisplay.tsx +230 -0
  66. package/src/components/Onboarding.tsx +274 -0
  67. package/src/components/PressEnterToContinue.tsx +11 -0
  68. package/src/components/PromptInput.tsx +740 -0
  69. package/src/components/SentryErrorBoundary.ts +33 -0
  70. package/src/components/Spinner.tsx +129 -0
  71. package/src/components/StickerRequestForm.tsx +16 -0
  72. package/src/components/StructuredDiff.tsx +191 -0
  73. package/src/components/TextInput.tsx +259 -0
  74. package/src/components/TodoItem.tsx +11 -0
  75. package/src/components/TokenWarning.tsx +31 -0
  76. package/src/components/ToolUseLoader.tsx +40 -0
  77. package/src/components/TrustDialog.tsx +106 -0
  78. package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
  79. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
  80. package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
  81. package/src/components/binary-feedback/utils.ts +220 -0
  82. package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
  83. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +49 -0
  84. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
  85. package/src/components/messages/AssistantTextMessage.tsx +144 -0
  86. package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
  87. package/src/components/messages/AssistantToolUseMessage.tsx +133 -0
  88. package/src/components/messages/TaskProgressMessage.tsx +32 -0
  89. package/src/components/messages/TaskToolMessage.tsx +58 -0
  90. package/src/components/messages/UserBashInputMessage.tsx +28 -0
  91. package/src/components/messages/UserCommandMessage.tsx +30 -0
  92. package/src/components/messages/UserKodingInputMessage.tsx +28 -0
  93. package/src/components/messages/UserPromptMessage.tsx +35 -0
  94. package/src/components/messages/UserTextMessage.tsx +39 -0
  95. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
  96. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
  97. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
  98. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
  99. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
  100. package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
  101. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
  102. package/src/components/permissions/FallbackPermissionRequest.tsx +153 -0
  103. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
  104. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +77 -0
  105. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
  106. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +83 -0
  107. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +240 -0
  108. package/src/components/permissions/PermissionRequest.tsx +101 -0
  109. package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
  110. package/src/components/permissions/hooks.ts +44 -0
  111. package/src/components/permissions/toolUseOptions.ts +59 -0
  112. package/src/components/permissions/utils.ts +23 -0
  113. package/src/constants/betas.ts +5 -0
  114. package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
  115. package/src/constants/figures.ts +4 -0
  116. package/src/constants/keys.ts +3 -0
  117. package/src/constants/macros.ts +8 -0
  118. package/src/constants/modelCapabilities.ts +179 -0
  119. package/src/constants/models.ts +1025 -0
  120. package/src/constants/oauth.ts +18 -0
  121. package/src/constants/product.ts +17 -0
  122. package/src/constants/prompts.ts +177 -0
  123. package/src/constants/releaseNotes.ts +7 -0
  124. package/src/context/PermissionContext.tsx +149 -0
  125. package/src/context.ts +278 -0
  126. package/src/cost-tracker.ts +84 -0
  127. package/src/entrypoints/cli.tsx +1518 -0
  128. package/src/entrypoints/mcp.ts +176 -0
  129. package/src/history.ts +25 -0
  130. package/src/hooks/useApiKeyVerification.ts +59 -0
  131. package/src/hooks/useArrowKeyHistory.ts +55 -0
  132. package/src/hooks/useCanUseTool.ts +138 -0
  133. package/src/hooks/useCancelRequest.ts +39 -0
  134. package/src/hooks/useDoublePress.ts +42 -0
  135. package/src/hooks/useExitOnCtrlCD.ts +31 -0
  136. package/src/hooks/useInterval.ts +25 -0
  137. package/src/hooks/useLogMessages.ts +16 -0
  138. package/src/hooks/useLogStartupTime.ts +12 -0
  139. package/src/hooks/useNotifyAfterTimeout.ts +65 -0
  140. package/src/hooks/usePermissionRequestLogging.ts +44 -0
  141. package/src/hooks/useTerminalSize.ts +49 -0
  142. package/src/hooks/useTextInput.ts +318 -0
  143. package/src/hooks/useUnifiedCompletion.ts +1404 -0
  144. package/src/messages.ts +38 -0
  145. package/src/permissions.ts +268 -0
  146. package/src/query.ts +707 -0
  147. package/src/screens/ConfigureNpmPrefix.tsx +197 -0
  148. package/src/screens/Doctor.tsx +219 -0
  149. package/src/screens/LogList.tsx +68 -0
  150. package/src/screens/REPL.tsx +798 -0
  151. package/src/screens/ResumeConversation.tsx +68 -0
  152. package/src/services/adapters/base.ts +38 -0
  153. package/src/services/adapters/chatCompletions.ts +90 -0
  154. package/src/services/adapters/responsesAPI.ts +170 -0
  155. package/src/services/browserMocks.ts +66 -0
  156. package/src/services/claude.ts +2083 -0
  157. package/src/services/customCommands.ts +704 -0
  158. package/src/services/fileFreshness.ts +377 -0
  159. package/src/services/gpt5ConnectionTest.ts +340 -0
  160. package/src/services/mcpClient.ts +564 -0
  161. package/src/services/mcpServerApproval.tsx +50 -0
  162. package/src/services/mentionProcessor.ts +273 -0
  163. package/src/services/modelAdapterFactory.ts +69 -0
  164. package/src/services/notifier.ts +40 -0
  165. package/src/services/oauth.ts +357 -0
  166. package/src/services/openai.ts +1305 -0
  167. package/src/services/responseStateManager.ts +90 -0
  168. package/src/services/sentry.ts +3 -0
  169. package/src/services/statsig.ts +171 -0
  170. package/src/services/statsigStorage.ts +86 -0
  171. package/src/services/systemReminder.ts +507 -0
  172. package/src/services/vcr.ts +161 -0
  173. package/src/test/testAdapters.ts +96 -0
  174. package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
  175. package/src/tools/ArchitectTool/prompt.ts +15 -0
  176. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +569 -0
  177. package/src/tools/BashTool/BashTool.tsx +243 -0
  178. package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
  179. package/src/tools/BashTool/OutputLine.tsx +49 -0
  180. package/src/tools/BashTool/prompt.ts +174 -0
  181. package/src/tools/BashTool/utils.ts +56 -0
  182. package/src/tools/FileEditTool/FileEditTool.tsx +315 -0
  183. package/src/tools/FileEditTool/prompt.ts +51 -0
  184. package/src/tools/FileEditTool/utils.ts +58 -0
  185. package/src/tools/FileReadTool/FileReadTool.tsx +404 -0
  186. package/src/tools/FileReadTool/prompt.ts +7 -0
  187. package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
  188. package/src/tools/FileWriteTool/prompt.ts +10 -0
  189. package/src/tools/GlobTool/GlobTool.tsx +119 -0
  190. package/src/tools/GlobTool/prompt.ts +8 -0
  191. package/src/tools/GrepTool/GrepTool.tsx +147 -0
  192. package/src/tools/GrepTool/prompt.ts +11 -0
  193. package/src/tools/MCPTool/MCPTool.tsx +107 -0
  194. package/src/tools/MCPTool/prompt.ts +3 -0
  195. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
  196. package/src/tools/MemoryReadTool/prompt.ts +3 -0
  197. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
  198. package/src/tools/MemoryWriteTool/prompt.ts +3 -0
  199. package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
  200. package/src/tools/MultiEditTool/prompt.ts +45 -0
  201. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
  202. package/src/tools/NotebookEditTool/prompt.ts +3 -0
  203. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +258 -0
  204. package/src/tools/NotebookReadTool/prompt.ts +3 -0
  205. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
  206. package/src/tools/StickerRequestTool/prompt.ts +19 -0
  207. package/src/tools/TaskTool/TaskTool.tsx +466 -0
  208. package/src/tools/TaskTool/constants.ts +1 -0
  209. package/src/tools/TaskTool/prompt.ts +92 -0
  210. package/src/tools/ThinkTool/ThinkTool.tsx +54 -0
  211. package/src/tools/ThinkTool/prompt.ts +12 -0
  212. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +290 -0
  213. package/src/tools/TodoWriteTool/prompt.ts +63 -0
  214. package/src/tools/lsTool/lsTool.tsx +272 -0
  215. package/src/tools/lsTool/prompt.ts +2 -0
  216. package/src/tools.ts +63 -0
  217. package/src/types/PermissionMode.ts +120 -0
  218. package/src/types/RequestContext.ts +72 -0
  219. package/src/types/conversation.ts +51 -0
  220. package/src/types/logs.ts +58 -0
  221. package/src/types/modelCapabilities.ts +64 -0
  222. package/src/types/notebook.ts +87 -0
  223. package/src/utils/Cursor.ts +436 -0
  224. package/src/utils/PersistentShell.ts +373 -0
  225. package/src/utils/advancedFuzzyMatcher.ts +290 -0
  226. package/src/utils/agentLoader.ts +284 -0
  227. package/src/utils/agentStorage.ts +97 -0
  228. package/src/utils/array.ts +3 -0
  229. package/src/utils/ask.tsx +99 -0
  230. package/src/utils/auth.ts +13 -0
  231. package/src/utils/autoCompactCore.ts +223 -0
  232. package/src/utils/autoUpdater.ts +318 -0
  233. package/src/utils/betas.ts +20 -0
  234. package/src/utils/browser.ts +14 -0
  235. package/src/utils/cleanup.ts +72 -0
  236. package/src/utils/commands.ts +261 -0
  237. package/src/utils/commonUnixCommands.ts +161 -0
  238. package/src/utils/config.ts +942 -0
  239. package/src/utils/conversationRecovery.ts +55 -0
  240. package/src/utils/debugLogger.ts +1123 -0
  241. package/src/utils/diff.ts +42 -0
  242. package/src/utils/env.ts +57 -0
  243. package/src/utils/errors.ts +21 -0
  244. package/src/utils/exampleCommands.ts +109 -0
  245. package/src/utils/execFileNoThrow.ts +51 -0
  246. package/src/utils/expertChatStorage.ts +136 -0
  247. package/src/utils/file.ts +402 -0
  248. package/src/utils/fileRecoveryCore.ts +71 -0
  249. package/src/utils/format.tsx +44 -0
  250. package/src/utils/fuzzyMatcher.ts +328 -0
  251. package/src/utils/generators.ts +62 -0
  252. package/src/utils/git.ts +92 -0
  253. package/src/utils/globalLogger.ts +77 -0
  254. package/src/utils/http.ts +10 -0
  255. package/src/utils/imagePaste.ts +38 -0
  256. package/src/utils/json.ts +13 -0
  257. package/src/utils/log.ts +382 -0
  258. package/src/utils/markdown.ts +213 -0
  259. package/src/utils/messageContextManager.ts +289 -0
  260. package/src/utils/messages.tsx +939 -0
  261. package/src/utils/model.ts +836 -0
  262. package/src/utils/permissions/filesystem.ts +118 -0
  263. package/src/utils/responseState.ts +23 -0
  264. package/src/utils/ripgrep.ts +167 -0
  265. package/src/utils/secureFile.ts +559 -0
  266. package/src/utils/sessionState.ts +49 -0
  267. package/src/utils/state.ts +25 -0
  268. package/src/utils/style.ts +29 -0
  269. package/src/utils/terminal.ts +50 -0
  270. package/src/utils/theme.ts +133 -0
  271. package/src/utils/thinking.ts +144 -0
  272. package/src/utils/todoStorage.ts +431 -0
  273. package/src/utils/tokens.ts +43 -0
  274. package/src/utils/toolExecutionController.ts +163 -0
  275. package/src/utils/unaryLogging.ts +26 -0
  276. package/src/utils/user.ts +37 -0
  277. package/src/utils/validate.ts +165 -0
  278. package/cli.mjs +0 -1803
@@ -0,0 +1,798 @@
1
+ import { ToolUseBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
2
+ import { Box, Newline, Static, Text } from 'ink'
3
+ import ProjectOnboarding, {
4
+ markProjectOnboardingComplete,
5
+ } from '../ProjectOnboarding.js'
6
+ import { CostThresholdDialog } from '../components/CostThresholdDialog'
7
+ import * as React from 'react'
8
+ import { useEffect, useMemo, useRef, useState, useCallback } from 'react'
9
+ import { Command } from '../commands'
10
+ import { Logo } from '../components/Logo'
11
+ import { Message } from '../components/Message'
12
+ import { MessageResponse } from '../components/MessageResponse'
13
+ import { MessageSelector } from '../components/MessageSelector'
14
+ import {
15
+ PermissionRequest,
16
+ type ToolUseConfirm,
17
+ } from '../components/permissions/PermissionRequest.js'
18
+ import PromptInput from '../components/PromptInput'
19
+ import { Spinner } from '../components/Spinner'
20
+ import { getSystemPrompt } from '../constants/prompts'
21
+ import { getContext } from '../context'
22
+ import { getTotalCost, useCostSummary } from '../cost-tracker'
23
+ import { useLogStartupTime } from '../hooks/useLogStartupTime'
24
+ import { addToHistory } from '../history'
25
+ import { useApiKeyVerification } from '../hooks/useApiKeyVerification'
26
+ import { useCancelRequest } from '../hooks/useCancelRequest'
27
+ import useCanUseTool from '../hooks/useCanUseTool'
28
+ import { useLogMessages } from '../hooks/useLogMessages'
29
+ import { PermissionProvider } from '../context/PermissionContext'
30
+ import { ModeIndicator } from '../components/ModeIndicator'
31
+ import {
32
+ setMessagesGetter,
33
+ setMessagesSetter,
34
+ setModelConfigChangeHandler,
35
+ } from '../messages'
36
+ import {
37
+ type AssistantMessage,
38
+ type BinaryFeedbackResult,
39
+ type Message as MessageType,
40
+ type ProgressMessage,
41
+ query,
42
+ } from '../query.js'
43
+ import type { WrappedClient } from '../services/mcpClient'
44
+ import type { Tool } from '../Tool'
45
+ import { AutoUpdaterResult } from '../utils/autoUpdater'
46
+ import { getGlobalConfig, saveGlobalConfig } from '../utils/config'
47
+ import { logEvent } from '../services/statsig'
48
+ import { getNextAvailableLogForkNumber } from '../utils/log'
49
+ import {
50
+ getErroredToolUseMessages,
51
+ getInProgressToolUseIDs,
52
+ getLastAssistantMessageId,
53
+ getToolUseID,
54
+ getUnresolvedToolUseIDs,
55
+ INTERRUPT_MESSAGE,
56
+ isNotEmptyMessage,
57
+ type NormalizedMessage,
58
+ normalizeMessages,
59
+ normalizeMessagesForAPI,
60
+ processUserInput,
61
+ reorderMessages,
62
+ extractTag,
63
+ createAssistantMessage,
64
+ } from '../utils/messages.js'
65
+ import { getModelManager, ModelManager } from '../utils/model'
66
+ import { clearTerminal, updateTerminalTitle } from '../utils/terminal'
67
+ import { BinaryFeedback } from '../components/binary-feedback/BinaryFeedback'
68
+ import { getMaxThinkingTokens } from '../utils/thinking'
69
+ import { getOriginalCwd } from '../utils/state'
70
+ import { handleHashCommand } from '../commands/terminalSetup'
71
+ import { debug as debugLogger } from '../utils/debugLogger'
72
+
73
+ type Props = {
74
+ commands: Command[]
75
+ safeMode?: boolean
76
+ debug?: boolean
77
+ initialForkNumber?: number | undefined
78
+ initialPrompt: string | undefined
79
+ // A unique name for the message log file, used to identify the fork
80
+ messageLogName: string
81
+ shouldShowPromptInput: boolean
82
+ tools: Tool[]
83
+ verbose: boolean | undefined
84
+ // Initial messages to populate the REPL with
85
+ initialMessages?: MessageType[]
86
+ // MCP clients
87
+ mcpClients?: WrappedClient[]
88
+ // Flag to indicate if current model is default
89
+ isDefaultModel?: boolean
90
+ }
91
+
92
+ export type BinaryFeedbackContext = {
93
+ m1: AssistantMessage
94
+ m2: AssistantMessage
95
+ resolve: (result: BinaryFeedbackResult) => void
96
+ }
97
+
98
+ export function REPL({
99
+ commands,
100
+ safeMode,
101
+ debug = false,
102
+ initialForkNumber = 0,
103
+ initialPrompt,
104
+ messageLogName,
105
+ shouldShowPromptInput,
106
+ tools,
107
+ verbose: verboseFromCLI,
108
+ initialMessages,
109
+ mcpClients = [],
110
+ isDefaultModel = true,
111
+ }: Props): React.ReactNode {
112
+ // TODO: probably shouldn't re-read config from file synchronously on every keystroke
113
+ const verbose = verboseFromCLI ?? getGlobalConfig().verbose
114
+
115
+ // Used to force the logo to re-render and conversation log to use a new file
116
+ const [forkNumber, setForkNumber] = useState(
117
+ getNextAvailableLogForkNumber(messageLogName, initialForkNumber, 0),
118
+ )
119
+
120
+ const [
121
+ forkConvoWithMessagesOnTheNextRender,
122
+ setForkConvoWithMessagesOnTheNextRender,
123
+ ] = useState<MessageType[] | null>(null)
124
+
125
+ // 🔧 Simplified AbortController management - inspired by reference system
126
+ const [abortController, setAbortController] = useState<AbortController | null>(null)
127
+ const [isLoading, setIsLoading] = useState(false)
128
+ const [autoUpdaterResult, setAutoUpdaterResult] =
129
+ useState<AutoUpdaterResult | null>(null)
130
+ const [toolJSX, setToolJSX] = useState<{
131
+ jsx: React.ReactNode | null
132
+ shouldHidePromptInput: boolean
133
+ } | null>(null)
134
+ const [toolUseConfirm, setToolUseConfirm] = useState<ToolUseConfirm | null>(
135
+ null,
136
+ )
137
+ const [messages, setMessages] = useState<MessageType[]>(initialMessages ?? [])
138
+ const [inputValue, setInputValue] = useState('')
139
+ const [inputMode, setInputMode] = useState<'bash' | 'prompt' | 'koding'>(
140
+ 'prompt',
141
+ )
142
+ const [submitCount, setSubmitCount] = useState(0)
143
+ const [isMessageSelectorVisible, setIsMessageSelectorVisible] =
144
+ useState(false)
145
+ const [showCostDialog, setShowCostDialog] = useState(false)
146
+ const [haveShownCostDialog, setHaveShownCostDialog] = useState(
147
+ getGlobalConfig().hasAcknowledgedCostThreshold,
148
+ )
149
+
150
+ const [binaryFeedbackContext, setBinaryFeedbackContext] =
151
+ useState<BinaryFeedbackContext | null>(null)
152
+
153
+ const getBinaryFeedbackResponse = useCallback(
154
+ (
155
+ m1: AssistantMessage,
156
+ m2: AssistantMessage,
157
+ ): Promise<BinaryFeedbackResult> => {
158
+ return new Promise<BinaryFeedbackResult>(resolvePromise => {
159
+ setBinaryFeedbackContext({
160
+ m1,
161
+ m2,
162
+ resolve: resolvePromise,
163
+ })
164
+ })
165
+ },
166
+ [],
167
+ )
168
+
169
+ const readFileTimestamps = useRef<{
170
+ [filename: string]: number
171
+ }>({})
172
+
173
+ const { status: apiKeyStatus, reverify } = useApiKeyVerification()
174
+ // 🔧 FIXED: Simple cancellation logic matching original claude-code
175
+ function onCancel() {
176
+ if (!isLoading) {
177
+ return
178
+ }
179
+ setIsLoading(false)
180
+ if (toolUseConfirm) {
181
+ // Tool use confirm handles the abort signal itself
182
+ toolUseConfirm.onAbort()
183
+ } else {
184
+ // Wrap abort in try-catch to prevent error display on user interrupt
185
+ try {
186
+ abortController?.abort()
187
+ } catch (e) {
188
+ // Silently handle abort errors - this is expected behavior
189
+ }
190
+ }
191
+ }
192
+
193
+ useCancelRequest(
194
+ setToolJSX,
195
+ setToolUseConfirm,
196
+ setBinaryFeedbackContext,
197
+ onCancel,
198
+ isLoading,
199
+ isMessageSelectorVisible,
200
+ abortController?.signal,
201
+ )
202
+
203
+ useEffect(() => {
204
+ if (forkConvoWithMessagesOnTheNextRender) {
205
+ setForkNumber(_ => _ + 1)
206
+ setForkConvoWithMessagesOnTheNextRender(null)
207
+ setMessages(forkConvoWithMessagesOnTheNextRender)
208
+ }
209
+ }, [forkConvoWithMessagesOnTheNextRender])
210
+
211
+ useEffect(() => {
212
+ const totalCost = getTotalCost()
213
+ if (totalCost >= 5 /* $5 */ && !showCostDialog && !haveShownCostDialog) {
214
+ logEvent('tengu_cost_threshold_reached', {})
215
+ setShowCostDialog(true)
216
+ }
217
+ }, [messages, showCostDialog, haveShownCostDialog])
218
+
219
+ const canUseTool = useCanUseTool(setToolUseConfirm)
220
+
221
+ async function onInit() {
222
+ reverify()
223
+
224
+ if (!initialPrompt) {
225
+ return
226
+ }
227
+
228
+ setIsLoading(true)
229
+
230
+ const newAbortController = new AbortController()
231
+ setAbortController(newAbortController)
232
+
233
+ // 🔧 Force fresh config read to ensure model switching works
234
+ const model = new ModelManager(getGlobalConfig()).getModelName('main')
235
+ const newMessages = await processUserInput(
236
+ initialPrompt,
237
+ 'prompt',
238
+ setToolJSX,
239
+ {
240
+ abortController: newAbortController,
241
+ options: {
242
+ commands,
243
+ forkNumber,
244
+ messageLogName,
245
+ tools,
246
+ verbose,
247
+ maxThinkingTokens: 0,
248
+ },
249
+ messageId: getLastAssistantMessageId(messages),
250
+ setForkConvoWithMessagesOnTheNextRender,
251
+ readFileTimestamps: readFileTimestamps.current,
252
+ },
253
+ null,
254
+ )
255
+
256
+ if (newMessages.length) {
257
+ for (const message of newMessages) {
258
+ if (message.type === 'user') {
259
+ addToHistory(initialPrompt)
260
+ // TODO: setHistoryIndex
261
+ }
262
+ }
263
+ setMessages(_ => [..._, ...newMessages])
264
+
265
+ // The last message is an assistant message if the user input was a bash command,
266
+ // or if the user input was an invalid slash command.
267
+ const lastMessage = newMessages[newMessages.length - 1]!
268
+ if (lastMessage.type === 'assistant') {
269
+ setAbortController(null)
270
+ setIsLoading(false)
271
+ return
272
+ }
273
+
274
+ const [systemPrompt, context, model, maxThinkingTokens] =
275
+ await Promise.all([
276
+ getSystemPrompt(),
277
+ getContext(),
278
+ new ModelManager(getGlobalConfig()).getModelName('main'),
279
+ getMaxThinkingTokens([...messages, ...newMessages]),
280
+ ])
281
+
282
+ for await (const message of query(
283
+ [...messages, ...newMessages],
284
+ systemPrompt,
285
+ context,
286
+ canUseTool,
287
+ {
288
+ options: {
289
+ commands,
290
+ forkNumber,
291
+ messageLogName,
292
+ tools,
293
+ verbose,
294
+ safeMode,
295
+ maxThinkingTokens,
296
+ },
297
+ messageId: getLastAssistantMessageId([...messages, ...newMessages]),
298
+ readFileTimestamps: readFileTimestamps.current,
299
+ abortController: newAbortController,
300
+ setToolJSX,
301
+ },
302
+ getBinaryFeedbackResponse,
303
+ )) {
304
+ setMessages(oldMessages => [...oldMessages, message])
305
+ }
306
+ } else {
307
+ addToHistory(initialPrompt)
308
+ // TODO: setHistoryIndex
309
+ }
310
+
311
+ setHaveShownCostDialog(
312
+ getGlobalConfig().hasAcknowledgedCostThreshold || false,
313
+ )
314
+
315
+ // 🔧 Fix: Clean up state after onInit completion
316
+ setIsLoading(false)
317
+ setAbortController(null)
318
+ }
319
+
320
+ async function onQuery(newMessages: MessageType[], passedAbortController?: AbortController) {
321
+ // Use passed AbortController or create new one
322
+ const controllerToUse = passedAbortController || new AbortController()
323
+ if (!passedAbortController) {
324
+ setAbortController(controllerToUse)
325
+ }
326
+
327
+ // Check if this is a Koding request based on last message's options
328
+ const isKodingRequest =
329
+ newMessages.length > 0 &&
330
+ newMessages[0].type === 'user' &&
331
+ 'options' in newMessages[0] &&
332
+ newMessages[0].options?.isKodingRequest === true
333
+
334
+ setMessages(oldMessages => [...oldMessages, ...newMessages])
335
+
336
+ // Mark onboarding as complete when any user message is sent to Claude
337
+ markProjectOnboardingComplete()
338
+
339
+ // The last message is an assistant message if the user input was a bash command,
340
+ // or if the user input was an invalid slash command.
341
+ const lastMessage = newMessages[newMessages.length - 1]!
342
+
343
+ // Update terminal title based on user message
344
+ if (
345
+ lastMessage.type === 'user' &&
346
+ typeof lastMessage.message.content === 'string'
347
+ ) {
348
+ // updateTerminalTitle(lastMessage.message.content)
349
+ }
350
+ if (lastMessage.type === 'assistant') {
351
+ setAbortController(null)
352
+ setIsLoading(false)
353
+ return
354
+ }
355
+
356
+ const [systemPrompt, context, model, maxThinkingTokens] =
357
+ await Promise.all([
358
+ getSystemPrompt(),
359
+ getContext(),
360
+ new ModelManager(getGlobalConfig()).getModelName('main'),
361
+ getMaxThinkingTokens([...messages, lastMessage]),
362
+ ])
363
+
364
+ let lastAssistantMessage: MessageType | null = null
365
+
366
+ // query the API
367
+ for await (const message of query(
368
+ [...messages, lastMessage],
369
+ systemPrompt,
370
+ context,
371
+ canUseTool,
372
+ {
373
+ options: {
374
+ commands,
375
+ forkNumber,
376
+ messageLogName,
377
+ tools,
378
+ verbose,
379
+ safeMode,
380
+ maxThinkingTokens,
381
+ // If this came from Koding mode, pass that along
382
+ isKodingRequest: isKodingRequest || undefined,
383
+ },
384
+ messageId: getLastAssistantMessageId([...messages, lastMessage]),
385
+ readFileTimestamps: readFileTimestamps.current,
386
+ abortController: controllerToUse,
387
+ setToolJSX,
388
+ },
389
+ getBinaryFeedbackResponse,
390
+ )) {
391
+ setMessages(oldMessages => [...oldMessages, message])
392
+
393
+ // Keep track of the last assistant message for Koding mode
394
+ if (message.type === 'assistant') {
395
+ lastAssistantMessage = message
396
+ }
397
+ }
398
+
399
+ // If this was a Koding request and we got an assistant message back,
400
+ // save it to AGENTS.md (and CLAUDE.md if exists)
401
+ if (
402
+ isKodingRequest &&
403
+ lastAssistantMessage &&
404
+ lastAssistantMessage.type === 'assistant'
405
+ ) {
406
+ try {
407
+ const content =
408
+ typeof lastAssistantMessage.message.content === 'string'
409
+ ? lastAssistantMessage.message.content
410
+ : lastAssistantMessage.message.content
411
+ .filter(block => block.type === 'text')
412
+ .map(block => (block.type === 'text' ? block.text : ''))
413
+ .join('\n')
414
+
415
+ // Add the content to AGENTS.md (and CLAUDE.md if exists)
416
+ if (content && content.trim().length > 0) {
417
+ handleHashCommand(content)
418
+ }
419
+ } catch (error) {
420
+ console.error('Error saving response to project docs:', error)
421
+ }
422
+ }
423
+
424
+ setIsLoading(false)
425
+ }
426
+
427
+ // Register cost summary tracker
428
+ useCostSummary()
429
+
430
+ // Register messages getter and setter
431
+ useEffect(() => {
432
+ const getMessages = () => messages
433
+ setMessagesGetter(getMessages)
434
+ setMessagesSetter(setMessages)
435
+ }, [messages])
436
+
437
+ // Register model config change handler for UI refresh
438
+ useEffect(() => {
439
+ setModelConfigChangeHandler(() => {
440
+ setForkNumber(prev => prev + 1)
441
+ })
442
+ }, [])
443
+
444
+ // Record transcripts locally, for debugging and conversation recovery
445
+ useLogMessages(messages, messageLogName, forkNumber)
446
+
447
+ // Log startup time
448
+ useLogStartupTime()
449
+
450
+ // Initial load
451
+ useEffect(() => {
452
+ onInit()
453
+ // TODO: fix this
454
+ // eslint-disable-next-line react-hooks/exhaustive-deps
455
+ }, [])
456
+
457
+ const normalizedMessages = useMemo(
458
+ () => normalizeMessages(messages).filter(isNotEmptyMessage),
459
+ [messages],
460
+ )
461
+
462
+ const unresolvedToolUseIDs = useMemo(
463
+ () => getUnresolvedToolUseIDs(normalizedMessages),
464
+ [normalizedMessages],
465
+ )
466
+
467
+ const inProgressToolUseIDs = useMemo(
468
+ () => getInProgressToolUseIDs(normalizedMessages),
469
+ [normalizedMessages],
470
+ )
471
+
472
+ const erroredToolUseIDs = useMemo(
473
+ () =>
474
+ new Set(
475
+ getErroredToolUseMessages(normalizedMessages).map(
476
+ _ => (_.message.content[0]! as ToolUseBlockParam).id,
477
+ ),
478
+ ),
479
+ [normalizedMessages],
480
+ )
481
+
482
+ const messagesJSX = useMemo(() => {
483
+ return [
484
+ {
485
+ type: 'static',
486
+ jsx: (
487
+ <Box flexDirection="column" key={`logo${forkNumber}`}>
488
+ <Logo mcpClients={mcpClients} isDefaultModel={isDefaultModel} />
489
+ <ProjectOnboarding workspaceDir={getOriginalCwd()} />
490
+ </Box>
491
+ ),
492
+ },
493
+ ...reorderMessages(normalizedMessages).map(_ => {
494
+ const toolUseID = getToolUseID(_)
495
+ const message =
496
+ _.type === 'progress' ? (
497
+ _.content.message.content[0]?.type === 'text' &&
498
+ // Hack: TaskTool interrupts use Progress messages, so don't
499
+ // need an extra ⎿ because <Message /> already adds one.
500
+ // TODO: Find a cleaner way to do this.
501
+ _.content.message.content[0].text === INTERRUPT_MESSAGE ? (
502
+ <Message
503
+ message={_.content}
504
+ messages={_.normalizedMessages}
505
+ addMargin={false}
506
+ tools={_.tools}
507
+ verbose={verbose ?? false}
508
+ debug={debug}
509
+ erroredToolUseIDs={new Set()}
510
+ inProgressToolUseIDs={new Set()}
511
+ unresolvedToolUseIDs={new Set()}
512
+ shouldAnimate={false}
513
+ shouldShowDot={false}
514
+ />
515
+ ) : (
516
+ <MessageResponse>
517
+ <Message
518
+ message={_.content}
519
+ messages={_.normalizedMessages}
520
+ addMargin={false}
521
+ tools={_.tools}
522
+ verbose={verbose ?? false}
523
+ debug={debug}
524
+ erroredToolUseIDs={new Set()}
525
+ inProgressToolUseIDs={new Set()}
526
+ unresolvedToolUseIDs={
527
+ new Set([
528
+ (_.content.message.content[0]! as ToolUseBlockParam).id,
529
+ ])
530
+ }
531
+ shouldAnimate={false}
532
+ shouldShowDot={false}
533
+ />
534
+ </MessageResponse>
535
+ )
536
+ ) : (
537
+ <Message
538
+ message={_}
539
+ messages={normalizedMessages}
540
+ addMargin={true}
541
+ tools={tools}
542
+ verbose={verbose}
543
+ debug={debug}
544
+ erroredToolUseIDs={erroredToolUseIDs}
545
+ inProgressToolUseIDs={inProgressToolUseIDs}
546
+ shouldAnimate={
547
+ !toolJSX &&
548
+ !toolUseConfirm &&
549
+ !isMessageSelectorVisible &&
550
+ (!toolUseID || inProgressToolUseIDs.has(toolUseID))
551
+ }
552
+ shouldShowDot={true}
553
+ unresolvedToolUseIDs={unresolvedToolUseIDs}
554
+ />
555
+ )
556
+
557
+ const type = shouldRenderStatically(
558
+ _,
559
+ normalizedMessages,
560
+ unresolvedToolUseIDs,
561
+ )
562
+ ? 'static'
563
+ : 'transient'
564
+
565
+ if (debug) {
566
+ return {
567
+ type,
568
+ jsx: (
569
+ <Box
570
+ borderStyle="single"
571
+ borderColor={type === 'static' ? 'green' : 'red'}
572
+ key={_.uuid}
573
+ width="100%"
574
+ >
575
+ {message}
576
+ </Box>
577
+ ),
578
+ }
579
+ }
580
+
581
+ return {
582
+ type,
583
+ jsx: (
584
+ <Box key={_.uuid} width="100%">
585
+ {message}
586
+ </Box>
587
+ ),
588
+ }
589
+ }),
590
+ ]
591
+ }, [
592
+ forkNumber,
593
+ normalizedMessages,
594
+ tools,
595
+ verbose,
596
+ debug,
597
+ erroredToolUseIDs,
598
+ inProgressToolUseIDs,
599
+ toolJSX,
600
+ toolUseConfirm,
601
+ isMessageSelectorVisible,
602
+ unresolvedToolUseIDs,
603
+ mcpClients,
604
+ isDefaultModel,
605
+ ])
606
+
607
+ // only show the dialog once not loading
608
+ const showingCostDialog = !isLoading && showCostDialog
609
+
610
+ return (
611
+ <PermissionProvider isBypassPermissionsModeAvailable={!safeMode}>
612
+ <ModeIndicator />
613
+ <React.Fragment key={`static-messages-${forkNumber}`}>
614
+ <Static
615
+ items={messagesJSX.filter(_ => _.type === 'static')}
616
+ >
617
+ {_ => _.jsx}
618
+ </Static>
619
+ </React.Fragment>
620
+ {messagesJSX.filter(_ => _.type === 'transient').map(_ => _.jsx)}
621
+ <Box
622
+ borderColor="red"
623
+ borderStyle={debug ? 'single' : undefined}
624
+ flexDirection="column"
625
+ width="100%"
626
+ >
627
+ {!toolJSX && !toolUseConfirm && !binaryFeedbackContext && isLoading && (
628
+ <Spinner />
629
+ )}
630
+ {toolJSX ? toolJSX.jsx : null}
631
+ {!toolJSX && binaryFeedbackContext && !isMessageSelectorVisible && (
632
+ <BinaryFeedback
633
+ m1={binaryFeedbackContext.m1}
634
+ m2={binaryFeedbackContext.m2}
635
+ resolve={result => {
636
+ binaryFeedbackContext.resolve(result)
637
+ setTimeout(() => setBinaryFeedbackContext(null), 0)
638
+ }}
639
+ verbose={verbose}
640
+ normalizedMessages={normalizedMessages}
641
+ tools={tools}
642
+ debug={debug}
643
+ erroredToolUseIDs={erroredToolUseIDs}
644
+ inProgressToolUseIDs={inProgressToolUseIDs}
645
+ unresolvedToolUseIDs={unresolvedToolUseIDs}
646
+ />
647
+ )}
648
+ {!toolJSX &&
649
+ toolUseConfirm &&
650
+ !isMessageSelectorVisible &&
651
+ !binaryFeedbackContext && (
652
+ <PermissionRequest
653
+ toolUseConfirm={toolUseConfirm}
654
+ onDone={() => setToolUseConfirm(null)}
655
+ verbose={verbose}
656
+ />
657
+ )}
658
+ {!toolJSX &&
659
+ !toolUseConfirm &&
660
+ !isMessageSelectorVisible &&
661
+ !binaryFeedbackContext &&
662
+ showingCostDialog && (
663
+ <CostThresholdDialog
664
+ onDone={() => {
665
+ setShowCostDialog(false)
666
+ setHaveShownCostDialog(true)
667
+ const projectConfig = getGlobalConfig()
668
+ saveGlobalConfig({
669
+ ...projectConfig,
670
+ hasAcknowledgedCostThreshold: true,
671
+ })
672
+ logEvent('tengu_cost_threshold_acknowledged', {})
673
+ }}
674
+ />
675
+ )}
676
+
677
+ {!toolUseConfirm &&
678
+ !toolJSX?.shouldHidePromptInput &&
679
+ shouldShowPromptInput &&
680
+ !isMessageSelectorVisible &&
681
+ !binaryFeedbackContext &&
682
+ !showingCostDialog && (
683
+ <>
684
+ <PromptInput
685
+ commands={commands}
686
+ forkNumber={forkNumber}
687
+ messageLogName={messageLogName}
688
+ tools={tools}
689
+ isDisabled={apiKeyStatus === 'invalid'}
690
+ isLoading={isLoading}
691
+ onQuery={onQuery}
692
+ debug={debug}
693
+ verbose={verbose}
694
+ messages={messages}
695
+ setToolJSX={setToolJSX}
696
+ onAutoUpdaterResult={setAutoUpdaterResult}
697
+ autoUpdaterResult={autoUpdaterResult}
698
+ input={inputValue}
699
+ onInputChange={setInputValue}
700
+ mode={inputMode}
701
+ onModeChange={setInputMode}
702
+ submitCount={submitCount}
703
+ onSubmitCountChange={setSubmitCount}
704
+ setIsLoading={setIsLoading}
705
+ setAbortController={setAbortController}
706
+ onShowMessageSelector={() =>
707
+ setIsMessageSelectorVisible(prev => !prev)
708
+ }
709
+ setForkConvoWithMessagesOnTheNextRender={
710
+ setForkConvoWithMessagesOnTheNextRender
711
+ }
712
+ readFileTimestamps={readFileTimestamps.current}
713
+ abortController={abortController}
714
+ onModelChange={() => setForkNumber(prev => prev + 1)}
715
+ />
716
+ </>
717
+ )}
718
+ </Box>
719
+ {isMessageSelectorVisible && (
720
+ <MessageSelector
721
+ erroredToolUseIDs={erroredToolUseIDs}
722
+ unresolvedToolUseIDs={unresolvedToolUseIDs}
723
+ messages={normalizeMessagesForAPI(messages)}
724
+ onSelect={async message => {
725
+ setIsMessageSelectorVisible(false)
726
+
727
+ // If the user selected the current prompt, do nothing
728
+ if (!messages.includes(message)) {
729
+ return
730
+ }
731
+
732
+ // Cancel tool use calls/requests
733
+ onCancel()
734
+
735
+ // Hack: make sure the "Interrupted by user" message is
736
+ // rendered in response to the cancellation. Otherwise,
737
+ // the screen will be cleared but there will remain a
738
+ // vestigial "Interrupted by user" message at the top.
739
+ setImmediate(async () => {
740
+ // Clear messages, and re-render
741
+ await clearTerminal()
742
+ setMessages([])
743
+ setForkConvoWithMessagesOnTheNextRender(
744
+ messages.slice(0, messages.indexOf(message)),
745
+ )
746
+
747
+ // Populate/reset the prompt input
748
+ if (typeof message.message.content === 'string') {
749
+ setInputValue(message.message.content)
750
+ }
751
+ })
752
+ }}
753
+ onEscape={() => setIsMessageSelectorVisible(false)}
754
+ tools={tools}
755
+ />
756
+ )}
757
+ {/** Fix occasional rendering artifact */}
758
+ <Newline />
759
+ </PermissionProvider>
760
+ )
761
+ }
762
+
763
+ function shouldRenderStatically(
764
+ message: NormalizedMessage,
765
+ messages: NormalizedMessage[],
766
+ unresolvedToolUseIDs: Set<string>,
767
+ ): boolean {
768
+ switch (message.type) {
769
+ case 'user':
770
+ case 'assistant': {
771
+ const toolUseID = getToolUseID(message)
772
+ if (!toolUseID) {
773
+ return true
774
+ }
775
+ if (unresolvedToolUseIDs.has(toolUseID)) {
776
+ return false
777
+ }
778
+
779
+ const correspondingProgressMessage = messages.find(
780
+ _ => _.type === 'progress' && _.toolUseID === toolUseID,
781
+ ) as ProgressMessage | null
782
+ if (!correspondingProgressMessage) {
783
+ return true
784
+ }
785
+
786
+ return !intersects(
787
+ unresolvedToolUseIDs,
788
+ correspondingProgressMessage.siblingToolUseIDs,
789
+ )
790
+ }
791
+ case 'progress':
792
+ return !intersects(unresolvedToolUseIDs, message.siblingToolUseIDs)
793
+ }
794
+ }
795
+
796
+ function intersects<A>(a: Set<A>, b: Set<A>): boolean {
797
+ return a.size > 0 && b.size > 0 && [...a].some(_ => b.has(_))
798
+ }