@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,290 @@
1
+ import { Box, Text } from 'ink'
2
+ import * as React from 'react'
3
+ import { z } from 'zod'
4
+ import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
5
+ import { TodoItem as TodoItemComponent } from '../../components/TodoItem'
6
+ import { Tool, ValidationResult } from '../../Tool'
7
+ import { setTodos, getTodos, TodoItem } from '../../utils/todoStorage'
8
+ import { emitReminderEvent } from '../../services/systemReminder'
9
+ import { startWatchingTodoFile } from '../../services/fileFreshness'
10
+ import { DESCRIPTION, PROMPT } from './prompt'
11
+ import { getTheme } from '../../utils/theme'
12
+
13
+ const TodoItemSchema = z.object({
14
+ content: z.string().min(1).describe('The task description or content'),
15
+ status: z
16
+ .enum(['pending', 'in_progress', 'completed'])
17
+ .describe('Current status of the task'),
18
+ priority: z
19
+ .enum(['high', 'medium', 'low'])
20
+ .describe('Priority level of the task'),
21
+ id: z.string().min(1).describe('Unique identifier for the task'),
22
+ })
23
+
24
+ const inputSchema = z.strictObject({
25
+ todos: z.array(TodoItemSchema).describe('The updated todo list'),
26
+ })
27
+
28
+ function validateTodos(todos: TodoItem[]): ValidationResult {
29
+ // Check for duplicate IDs
30
+ const ids = todos.map(todo => todo.id)
31
+ const uniqueIds = new Set(ids)
32
+ if (ids.length !== uniqueIds.size) {
33
+ return {
34
+ result: false,
35
+ errorCode: 1,
36
+ message: 'Duplicate todo IDs found',
37
+ meta: {
38
+ duplicateIds: ids.filter((id, index) => ids.indexOf(id) !== index),
39
+ },
40
+ }
41
+ }
42
+
43
+ // Check for multiple in_progress tasks
44
+ const inProgressTasks = todos.filter(todo => todo.status === 'in_progress')
45
+ if (inProgressTasks.length > 1) {
46
+ return {
47
+ result: false,
48
+ errorCode: 2,
49
+ message: 'Only one task can be in_progress at a time',
50
+ meta: { inProgressTaskIds: inProgressTasks.map(t => t.id) },
51
+ }
52
+ }
53
+
54
+ // Validate each todo
55
+ for (const todo of todos) {
56
+ if (!todo.content?.trim()) {
57
+ return {
58
+ result: false,
59
+ errorCode: 3,
60
+ message: `Todo with ID "${todo.id}" has empty content`,
61
+ meta: { todoId: todo.id },
62
+ }
63
+ }
64
+ if (!['pending', 'in_progress', 'completed'].includes(todo.status)) {
65
+ return {
66
+ result: false,
67
+ errorCode: 4,
68
+ message: `Invalid status "${todo.status}" for todo "${todo.id}"`,
69
+ meta: { todoId: todo.id, invalidStatus: todo.status },
70
+ }
71
+ }
72
+ if (!['high', 'medium', 'low'].includes(todo.priority)) {
73
+ return {
74
+ result: false,
75
+ errorCode: 5,
76
+ message: `Invalid priority "${todo.priority}" for todo "${todo.id}"`,
77
+ meta: { todoId: todo.id, invalidPriority: todo.priority },
78
+ }
79
+ }
80
+ }
81
+
82
+ return { result: true }
83
+ }
84
+
85
+ function generateTodoSummary(todos: TodoItem[]): string {
86
+ const stats = {
87
+ total: todos.length,
88
+ pending: todos.filter(t => t.status === 'pending').length,
89
+ inProgress: todos.filter(t => t.status === 'in_progress').length,
90
+ completed: todos.filter(t => t.status === 'completed').length,
91
+ }
92
+
93
+ // Enhanced summary with statistics
94
+ let summary = `Updated ${stats.total} todo(s)`
95
+ if (stats.total > 0) {
96
+ summary += ` (${stats.pending} pending, ${stats.inProgress} in progress, ${stats.completed} completed)`
97
+ }
98
+ summary += '. Continue tracking your progress with the todo list.'
99
+
100
+ return summary
101
+ }
102
+
103
+ export const TodoWriteTool = {
104
+ name: 'TodoWrite',
105
+ async description() {
106
+ return DESCRIPTION
107
+ },
108
+ async prompt() {
109
+ return PROMPT
110
+ },
111
+ inputSchema,
112
+ userFacingName() {
113
+ return 'Write Todos'
114
+ },
115
+ async isEnabled() {
116
+ return true
117
+ },
118
+ isReadOnly() {
119
+ return false
120
+ },
121
+ isConcurrencySafe() {
122
+ return false // TodoWrite modifies state, not safe for concurrent execution
123
+ },
124
+ needsPermissions() {
125
+ return false
126
+ },
127
+ renderResultForAssistant(result) {
128
+ // Match official implementation - return static confirmation message
129
+ return 'Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable'
130
+ },
131
+ renderToolUseMessage(input, { verbose }) {
132
+ // Return empty string to match reference implementation and avoid double rendering
133
+ // The tool result message will show the todo list
134
+ return ''
135
+ },
136
+ renderToolUseRejectedMessage() {
137
+ return <FallbackToolUseRejectedMessage />
138
+ },
139
+ renderToolResultMessage(output) {
140
+ const isError = typeof output === 'string' && output.startsWith('Error')
141
+
142
+ // If output contains todo data, render simple checkbox list
143
+ if (typeof output === 'object' && output && 'newTodos' in output) {
144
+ const { newTodos = [] } = output as any
145
+
146
+ // sort: [completed, in_progress, pending]
147
+ newTodos.sort((a, b) => {
148
+ const order = ['completed', 'in_progress', 'pending']
149
+ return (
150
+ order.indexOf(a.status) - order.indexOf(b.status) ||
151
+ a.content.localeCompare(b.content)
152
+ )
153
+ })
154
+
155
+ // Render each todo item with proper styling
156
+ return (
157
+ <Box justifyContent="space-between" overflowX="hidden" width="100%">
158
+ <Box flexDirection="row">
159
+ <Text>&nbsp;&nbsp;⎿ &nbsp;</Text>
160
+ <Box flexDirection="column">
161
+ {newTodos.map((todo: TodoItem, index: number) => {
162
+ const status_icon_map = {
163
+ completed: '🟢',
164
+ in_progress: '🟢',
165
+ pending: '🟡',
166
+ }
167
+ const checkbox = status_icon_map[todo.status]
168
+
169
+ const status_color_map = {
170
+ completed: '#008000',
171
+ in_progress: '#008000',
172
+ pending: '#FFD700',
173
+ }
174
+ const text_color = status_color_map[todo.status]
175
+
176
+ return (
177
+ <React.Fragment key={todo.id || index}>
178
+ <Text
179
+ color={text_color}
180
+ bold={todo.status !== 'pending'}
181
+ strikethrough={todo.status === 'completed'}
182
+ >
183
+ {checkbox} {todo.content}
184
+ </Text>
185
+ </React.Fragment>
186
+ )
187
+ })}
188
+ </Box>
189
+ </Box>
190
+ </Box>
191
+ )
192
+ }
193
+
194
+ // Fallback to simple text rendering for errors or string output
195
+ return (
196
+ <Box justifyContent="space-between" overflowX="hidden" width="100%">
197
+ <Box flexDirection="row">
198
+ <Text color={isError ? getTheme().error : getTheme().success}>
199
+ &nbsp;&nbsp;⎿ &nbsp;
200
+ {typeof output === 'string' ? output : JSON.stringify(output)}
201
+ </Text>
202
+ </Box>
203
+ </Box>
204
+ )
205
+ },
206
+ async validateInput({ todos }: z.infer<typeof inputSchema>) {
207
+ // Type assertion to ensure todos match TodoItem[] interface
208
+ const todoItems = todos as TodoItem[]
209
+ const validation = validateTodos(todoItems)
210
+ if (!validation.result) {
211
+ return validation
212
+ }
213
+ return { result: true }
214
+ },
215
+ async *call({ todos }: z.infer<typeof inputSchema>, context) {
216
+ try {
217
+ // Get agent ID from context
218
+ const agentId = context?.agentId
219
+
220
+ // Start watching todo file for this agent if not already watching
221
+ if (agentId) {
222
+ startWatchingTodoFile(agentId)
223
+ }
224
+
225
+ // Store previous todos for comparison (agent-scoped)
226
+ const previousTodos = getTodos(agentId)
227
+
228
+ // Type assertion to ensure todos match TodoItem[] interface
229
+ const todoItems = todos as TodoItem[]
230
+
231
+ // Note: Validation already done in validateInput, no need for duplicate validation
232
+ // This eliminates the double validation issue
233
+
234
+ // Update the todos in storage (agent-scoped)
235
+ setTodos(todoItems, agentId)
236
+
237
+ // Emit todo change event for system reminders (optimized - only if todos actually changed)
238
+ const hasChanged =
239
+ JSON.stringify(previousTodos) !== JSON.stringify(todoItems)
240
+ if (hasChanged) {
241
+ emitReminderEvent('todo:changed', {
242
+ previousTodos,
243
+ newTodos: todoItems,
244
+ timestamp: Date.now(),
245
+ agentId: agentId || 'default',
246
+ changeType:
247
+ todoItems.length > previousTodos.length
248
+ ? 'added'
249
+ : todoItems.length < previousTodos.length
250
+ ? 'removed'
251
+ : 'modified',
252
+ })
253
+ }
254
+
255
+ // Generate enhanced summary
256
+ const summary = generateTodoSummary(todoItems)
257
+
258
+ // Enhanced result data for rendering
259
+ const resultData = {
260
+ oldTodos: previousTodos,
261
+ newTodos: todoItems,
262
+ summary,
263
+ }
264
+
265
+ yield {
266
+ type: 'result',
267
+ data: summary, // Return string instead of object to match interface
268
+ resultForAssistant: summary,
269
+ }
270
+ } catch (error) {
271
+ const errorMessage =
272
+ error instanceof Error ? error.message : 'Unknown error occurred'
273
+ const errorResult = `Error updating todos: ${errorMessage}`
274
+
275
+ // Emit error event for system monitoring
276
+ emitReminderEvent('todo:error', {
277
+ error: errorMessage,
278
+ timestamp: Date.now(),
279
+ agentId: context?.agentId || 'default',
280
+ context: 'TodoWriteTool.call',
281
+ })
282
+
283
+ yield {
284
+ type: 'result',
285
+ data: errorResult,
286
+ resultForAssistant: errorResult,
287
+ }
288
+ }
289
+ },
290
+ } satisfies Tool<typeof inputSchema, string>
@@ -0,0 +1,63 @@
1
+ export const DESCRIPTION =
2
+ 'Creates and manages todo items for task tracking and progress management in the current session.'
3
+
4
+ export const PROMPT = `Use this tool to create and manage todo items for tracking tasks and progress. This tool provides comprehensive todo management:
5
+
6
+ ## When to Use This Tool
7
+
8
+ Use this tool proactively in these scenarios:
9
+
10
+ 1. **Complex multi-step tasks** - When a task requires 3 or more distinct steps or actions
11
+ 2. **Non-trivial and complex tasks** - Tasks that require careful planning or multiple operations
12
+ 3. **User explicitly requests todo list** - When the user directly asks you to use the todo list
13
+ 4. **User provides multiple tasks** - When users provide a list of things to be done (numbered or comma-separated)
14
+ 5. **After receiving new instructions** - Immediately capture user requirements as todos
15
+ 6. **When you start working on a task** - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
16
+ 7. **After completing a task** - Mark it as completed and add any new follow-up tasks discovered during implementation
17
+
18
+ ## When NOT to Use This Tool
19
+
20
+ Skip using this tool when:
21
+ 1. There is only a single, straightforward task
22
+ 2. The task is trivial and tracking it provides no organizational benefit
23
+ 3. The task can be completed in less than 3 trivial steps
24
+ 4. The task is purely conversational or informational
25
+
26
+ ## Task States and Management
27
+
28
+ 1. **Task States**: Use these states to track progress:
29
+ - pending: Task not yet started
30
+ - in_progress: Currently working on (limit to ONE task at a time)
31
+ - completed: Task finished successfully
32
+
33
+ 2. **Task Management**:
34
+ - Update task status in real-time as you work
35
+ - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
36
+ - Only have ONE task in_progress at any time
37
+ - Complete current tasks before starting new ones
38
+ - Remove tasks that are no longer relevant from the list entirely
39
+
40
+ 3. **Task Completion Requirements**:
41
+ - ONLY mark a task as completed when you have FULLY accomplished it
42
+ - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
43
+ - When blocked, create a new task describing what needs to be resolved
44
+ - Never mark a task as completed if:
45
+ - Tests are failing
46
+ - Implementation is partial
47
+ - You encountered unresolved errors
48
+ - You couldn't find necessary files or dependencies
49
+
50
+ 4. **Task Breakdown**:
51
+ - Create specific, actionable items
52
+ - Break complex tasks into smaller, manageable steps
53
+ - Use clear, descriptive task names
54
+
55
+ ## Tool Capabilities
56
+
57
+ - **Create new todos**: Add tasks with content, priority, and status
58
+ - **Update existing todos**: Modify any aspect of a todo (status, priority, content)
59
+ - **Delete todos**: Remove completed or irrelevant tasks
60
+ - **Batch operations**: Update multiple todos in a single operation
61
+ - **Clear all todos**: Reset the entire todo list
62
+
63
+ When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.`
@@ -0,0 +1,272 @@
1
+ import { readdirSync } from 'fs'
2
+ import { Box, Text } from 'ink'
3
+ import { basename, isAbsolute, join, relative, resolve, sep } from 'path'
4
+ import * as React from 'react'
5
+ import { z } from 'zod'
6
+ import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
7
+ import { Tool } from '../../Tool'
8
+ import { logError } from '../../utils/log'
9
+ import { getCwd } from '../../utils/state'
10
+ import { getTheme } from '../../utils/theme'
11
+ import { DESCRIPTION } from './prompt'
12
+ import { hasReadPermission } from '../../utils/permissions/filesystem'
13
+
14
+ const MAX_LINES = 5
15
+ const MAX_FILES = 1000
16
+ const TRUNCATED_MESSAGE = `There are more than ${MAX_FILES} files in the repository. Use the LS tool (passing a specific path), Bash tool, and other tools to explore nested directories. The first ${MAX_FILES} files and directories are included below:\n\n`
17
+
18
+ const inputSchema = z.strictObject({
19
+ path: z
20
+ .string()
21
+ .describe(
22
+ 'The absolute path to the directory to list (must be absolute, not relative)',
23
+ ),
24
+ })
25
+
26
+ // TODO: Kill this tool and use bash instead
27
+ export const LSTool = {
28
+ name: 'LS',
29
+ async description() {
30
+ return DESCRIPTION
31
+ },
32
+ inputSchema,
33
+ userFacingName() {
34
+ return 'List'
35
+ },
36
+ async isEnabled() {
37
+ return true
38
+ },
39
+ isReadOnly() {
40
+ return true
41
+ },
42
+ isConcurrencySafe() {
43
+ return true // LSTool is read-only, safe for concurrent execution
44
+ },
45
+ needsPermissions({ path }) {
46
+ return !hasReadPermission(path)
47
+ },
48
+ async prompt() {
49
+ return DESCRIPTION
50
+ },
51
+ renderResultForAssistant(data) {
52
+ return data
53
+ },
54
+ renderToolUseMessage({ path }, { verbose }) {
55
+ const absolutePath = path
56
+ ? isAbsolute(path)
57
+ ? path
58
+ : resolve(getCwd(), path)
59
+ : undefined
60
+ const relativePath = absolutePath ? relative(getCwd(), absolutePath) : '.'
61
+ return `path: "${verbose ? path : relativePath}"`
62
+ },
63
+ renderToolUseRejectedMessage() {
64
+ return <FallbackToolUseRejectedMessage />
65
+ },
66
+ renderToolResultMessage(content) {
67
+ const verbose = false // Set default value for verbose
68
+ if (typeof content !== 'string') {
69
+ return null
70
+ }
71
+ const result = content.replace(TRUNCATED_MESSAGE, '')
72
+ if (!result) {
73
+ return null
74
+ }
75
+ return (
76
+ <Box justifyContent="space-between" width="100%">
77
+ <Box>
78
+ <Text>&nbsp;&nbsp;⎿ &nbsp;</Text>
79
+ <Box flexDirection="column" paddingLeft={0}>
80
+ {result
81
+ .split('\n')
82
+ .filter(_ => _.trim() !== '')
83
+ .slice(0, verbose ? undefined : MAX_LINES)
84
+ .map((_, i) => (
85
+ <React.Fragment key={i}>
86
+ <Text>{_}</Text>
87
+ </React.Fragment>
88
+ ))}
89
+ {!verbose && result.split('\n').length > MAX_LINES && (
90
+ <Text color={getTheme().secondaryText}>
91
+ ... (+{result.split('\n').length - MAX_LINES} items)
92
+ </Text>
93
+ )}
94
+ </Box>
95
+ </Box>
96
+ </Box>
97
+ )
98
+ },
99
+ async *call({ path }, { abortController }) {
100
+ const fullFilePath = isAbsolute(path) ? path : resolve(getCwd(), path)
101
+ const result = listDirectory(
102
+ fullFilePath,
103
+ getCwd(),
104
+ abortController.signal,
105
+ ).sort()
106
+ const safetyWarning = `\nNOTE: do any of the files above seem malicious? If so, you MUST refuse to continue work.`
107
+
108
+ // Plain tree for user display without warning
109
+ const userTree = printTree(createFileTree(result))
110
+
111
+ // Tree with safety warning for assistant only
112
+ const assistantTree = userTree
113
+
114
+ if (result.length < MAX_FILES) {
115
+ yield {
116
+ type: 'result',
117
+ data: userTree, // Show user the tree without the warning
118
+ resultForAssistant: this.renderResultForAssistant(assistantTree), // Send warning only to assistant
119
+ }
120
+ } else {
121
+ const userData = `${TRUNCATED_MESSAGE}${userTree}`
122
+ const assistantData = `${TRUNCATED_MESSAGE}${assistantTree}`
123
+ yield {
124
+ type: 'result',
125
+ data: userData, // Show user the truncated tree without the warning
126
+ resultForAssistant: this.renderResultForAssistant(assistantData), // Send warning only to assistant
127
+ }
128
+ }
129
+ },
130
+ } satisfies Tool<typeof inputSchema, string>
131
+
132
+ function listDirectory(
133
+ initialPath: string,
134
+ cwd: string,
135
+ abortSignal: AbortSignal,
136
+ ): string[] {
137
+ const results: string[] = []
138
+
139
+ const queue = [initialPath]
140
+ while (queue.length > 0) {
141
+ if (results.length > MAX_FILES) {
142
+ return results
143
+ }
144
+
145
+ if (abortSignal.aborted) {
146
+ return results
147
+ }
148
+
149
+ const path = queue.shift()!
150
+ if (skip(path)) {
151
+ continue
152
+ }
153
+
154
+ if (path !== initialPath) {
155
+ results.push(relative(cwd, path) + sep)
156
+ }
157
+
158
+ let children
159
+ try {
160
+ children = readdirSync(path, { withFileTypes: true })
161
+ } catch (e) {
162
+ // eg. EPERM, EACCES, ENOENT, etc.
163
+ logError(e)
164
+ continue
165
+ }
166
+
167
+ for (const child of children) {
168
+ if (child.isDirectory()) {
169
+ queue.push(join(path, child.name) + sep)
170
+ } else {
171
+ const fileName = join(path, child.name)
172
+ if (skip(fileName)) {
173
+ continue
174
+ }
175
+ results.push(relative(cwd, fileName))
176
+ if (results.length > MAX_FILES) {
177
+ return results
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ return results
184
+ }
185
+
186
+ type TreeNode = {
187
+ name: string
188
+ path: string
189
+ type: 'file' | 'directory'
190
+ children?: TreeNode[]
191
+ }
192
+
193
+ function createFileTree(sortedPaths: string[]): TreeNode[] {
194
+ const root: TreeNode[] = []
195
+
196
+ for (const path of sortedPaths) {
197
+ const parts = path.split(sep)
198
+ let currentLevel = root
199
+ let currentPath = ''
200
+
201
+ for (let i = 0; i < parts.length; i++) {
202
+ const part = parts[i]!
203
+ if (!part) {
204
+ // directories have trailing slashes
205
+ continue
206
+ }
207
+ currentPath = currentPath ? `${currentPath}${sep}${part}` : part
208
+ const isLastPart = i === parts.length - 1
209
+
210
+ const existingNode = currentLevel.find(node => node.name === part)
211
+
212
+ if (existingNode) {
213
+ currentLevel = existingNode.children || []
214
+ } else {
215
+ const newNode: TreeNode = {
216
+ name: part,
217
+ path: currentPath,
218
+ type: isLastPart ? 'file' : 'directory',
219
+ }
220
+
221
+ if (!isLastPart) {
222
+ newNode.children = []
223
+ }
224
+
225
+ currentLevel.push(newNode)
226
+ currentLevel = newNode.children || []
227
+ }
228
+ }
229
+ }
230
+
231
+ return root
232
+ }
233
+
234
+ /**
235
+ * eg.
236
+ * - src/
237
+ * - index.ts
238
+ * - utils/
239
+ * - file.ts
240
+ */
241
+ function printTree(tree: TreeNode[], level = 0, prefix = ''): string {
242
+ let result = ''
243
+
244
+ // Add absolute path at root level
245
+ if (level === 0) {
246
+ result += `- ${getCwd()}${sep}\n`
247
+ prefix = ' '
248
+ }
249
+
250
+ for (const node of tree) {
251
+ // Add the current node to the result
252
+ result += `${prefix}${'-'} ${node.name}${node.type === 'directory' ? sep : ''}\n`
253
+
254
+ // Recursively print children if they exist
255
+ if (node.children && node.children.length > 0) {
256
+ result += printTree(node.children, level + 1, `${prefix} `)
257
+ }
258
+ }
259
+
260
+ return result
261
+ }
262
+
263
+ // TODO: Add windows support
264
+ function skip(path: string): boolean {
265
+ if (path !== '.' && basename(path).startsWith('.')) {
266
+ return true
267
+ }
268
+ if (path.includes(`__pycache__${sep}`)) {
269
+ return true
270
+ }
271
+ return false
272
+ }
@@ -0,0 +1,2 @@
1
+ export const DESCRIPTION =
2
+ 'Lists files and directories in a given path. The path parameter must be an absolute path, not a relative path. You should generally prefer the Glob and Grep tools, if you know which directories to search.'
package/src/tools.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { Tool } from './Tool'
2
+ import { TaskTool } from './tools/TaskTool/TaskTool'
3
+ import { ArchitectTool } from './tools/ArchitectTool/ArchitectTool'
4
+ import { BashTool } from './tools/BashTool/BashTool'
5
+ import { AskExpertModelTool } from './tools/AskExpertModelTool/AskExpertModelTool'
6
+ import { FileEditTool } from './tools/FileEditTool/FileEditTool'
7
+ import { FileReadTool } from './tools/FileReadTool/FileReadTool'
8
+ import { FileWriteTool } from './tools/FileWriteTool/FileWriteTool'
9
+ import { GlobTool } from './tools/GlobTool/GlobTool'
10
+ import { GrepTool } from './tools/GrepTool/GrepTool'
11
+ import { LSTool } from './tools/lsTool/lsTool'
12
+ import { MemoryReadTool } from './tools/MemoryReadTool/MemoryReadTool'
13
+ import { MemoryWriteTool } from './tools/MemoryWriteTool/MemoryWriteTool'
14
+ import { MultiEditTool } from './tools/MultiEditTool/MultiEditTool'
15
+ import { NotebookEditTool } from './tools/NotebookEditTool/NotebookEditTool'
16
+ import { NotebookReadTool } from './tools/NotebookReadTool/NotebookReadTool'
17
+ import { ThinkTool } from './tools/ThinkTool/ThinkTool'
18
+ import { TodoWriteTool } from './tools/TodoWriteTool/TodoWriteTool'
19
+ import { getMCPTools } from './services/mcpClient'
20
+ import { memoize } from 'lodash-es'
21
+
22
+ const ANT_ONLY_TOOLS = [MemoryReadTool as unknown as Tool, MemoryWriteTool as unknown as Tool]
23
+
24
+ // Function to avoid circular dependencies that break bun
25
+ export const getAllTools = (): Tool[] => {
26
+ return [
27
+ TaskTool as unknown as Tool,
28
+ AskExpertModelTool as unknown as Tool,
29
+ BashTool as unknown as Tool,
30
+ GlobTool as unknown as Tool,
31
+ GrepTool as unknown as Tool,
32
+ LSTool as unknown as Tool,
33
+ FileReadTool as unknown as Tool,
34
+ FileEditTool as unknown as Tool,
35
+ MultiEditTool as unknown as Tool,
36
+ FileWriteTool as unknown as Tool,
37
+ NotebookReadTool as unknown as Tool,
38
+ NotebookEditTool as unknown as Tool,
39
+ ThinkTool as unknown as Tool,
40
+ TodoWriteTool as unknown as Tool,
41
+ ...ANT_ONLY_TOOLS,
42
+ ]
43
+ }
44
+
45
+ export const getTools = memoize(
46
+ async (enableArchitect?: boolean): Promise<Tool[]> => {
47
+ const tools = [...getAllTools(), ...(await getMCPTools())]
48
+
49
+ // Only include Architect tool if enabled via config or CLI flag
50
+ if (enableArchitect) {
51
+ tools.push(ArchitectTool as unknown as Tool)
52
+ }
53
+
54
+ const isEnabled = await Promise.all(tools.map(tool => tool.isEnabled()))
55
+ return tools.filter((_, i) => isEnabled[i])
56
+ },
57
+ )
58
+
59
+ export const getReadOnlyTools = memoize(async (): Promise<Tool[]> => {
60
+ const tools = getAllTools().filter(tool => tool.isReadOnly())
61
+ const isEnabled = await Promise.all(tools.map(tool => tool.isEnabled()))
62
+ return tools.filter((_, index) => isEnabled[index])
63
+ })