@shareai-lab/kode 1.0.70 → 1.0.71

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 (253) hide show
  1. package/README.md +202 -76
  2. package/README.zh-CN.md +246 -0
  3. package/cli.js +62 -0
  4. package/package.json +45 -25
  5. package/scripts/postinstall.js +56 -0
  6. package/src/ProjectOnboarding.tsx +180 -0
  7. package/src/Tool.ts +53 -0
  8. package/src/commands/approvedTools.ts +53 -0
  9. package/src/commands/bug.tsx +20 -0
  10. package/src/commands/clear.ts +43 -0
  11. package/src/commands/compact.ts +120 -0
  12. package/src/commands/config.tsx +19 -0
  13. package/src/commands/cost.ts +18 -0
  14. package/src/commands/ctx_viz.ts +209 -0
  15. package/src/commands/doctor.ts +24 -0
  16. package/src/commands/help.tsx +19 -0
  17. package/src/commands/init.ts +37 -0
  18. package/src/commands/listen.ts +42 -0
  19. package/src/commands/login.tsx +51 -0
  20. package/src/commands/logout.tsx +40 -0
  21. package/src/commands/mcp.ts +41 -0
  22. package/src/commands/model.tsx +40 -0
  23. package/src/commands/modelstatus.tsx +20 -0
  24. package/src/commands/onboarding.tsx +34 -0
  25. package/src/commands/pr_comments.ts +59 -0
  26. package/src/commands/refreshCommands.ts +54 -0
  27. package/src/commands/release-notes.ts +34 -0
  28. package/src/commands/resume.tsx +30 -0
  29. package/src/commands/review.ts +49 -0
  30. package/src/commands/terminalSetup.ts +221 -0
  31. package/src/commands.ts +136 -0
  32. package/src/components/ApproveApiKey.tsx +93 -0
  33. package/src/components/AsciiLogo.tsx +13 -0
  34. package/src/components/AutoUpdater.tsx +148 -0
  35. package/src/components/Bug.tsx +367 -0
  36. package/src/components/Config.tsx +289 -0
  37. package/src/components/ConsoleOAuthFlow.tsx +326 -0
  38. package/src/components/Cost.tsx +23 -0
  39. package/src/components/CostThresholdDialog.tsx +46 -0
  40. package/src/components/CustomSelect/option-map.ts +42 -0
  41. package/src/components/CustomSelect/select-option.tsx +52 -0
  42. package/src/components/CustomSelect/select.tsx +143 -0
  43. package/src/components/CustomSelect/use-select-state.ts +414 -0
  44. package/src/components/CustomSelect/use-select.ts +35 -0
  45. package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
  46. package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
  47. package/src/components/Help.tsx +215 -0
  48. package/src/components/HighlightedCode.tsx +33 -0
  49. package/src/components/InvalidConfigDialog.tsx +113 -0
  50. package/src/components/Link.tsx +32 -0
  51. package/src/components/LogSelector.tsx +86 -0
  52. package/src/components/Logo.tsx +145 -0
  53. package/src/components/MCPServerApprovalDialog.tsx +100 -0
  54. package/src/components/MCPServerDialogCopy.tsx +25 -0
  55. package/src/components/MCPServerMultiselectDialog.tsx +109 -0
  56. package/src/components/Message.tsx +219 -0
  57. package/src/components/MessageResponse.tsx +15 -0
  58. package/src/components/MessageSelector.tsx +211 -0
  59. package/src/components/ModeIndicator.tsx +88 -0
  60. package/src/components/ModelConfig.tsx +301 -0
  61. package/src/components/ModelListManager.tsx +223 -0
  62. package/src/components/ModelSelector.tsx +3208 -0
  63. package/src/components/ModelStatusDisplay.tsx +228 -0
  64. package/src/components/Onboarding.tsx +274 -0
  65. package/src/components/PressEnterToContinue.tsx +11 -0
  66. package/src/components/PromptInput.tsx +710 -0
  67. package/src/components/SentryErrorBoundary.ts +33 -0
  68. package/src/components/Spinner.tsx +129 -0
  69. package/src/components/StructuredDiff.tsx +184 -0
  70. package/src/components/TextInput.tsx +246 -0
  71. package/src/components/TokenWarning.tsx +31 -0
  72. package/src/components/ToolUseLoader.tsx +40 -0
  73. package/src/components/TrustDialog.tsx +106 -0
  74. package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
  75. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
  76. package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
  77. package/src/components/binary-feedback/utils.ts +220 -0
  78. package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
  79. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
  80. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
  81. package/src/components/messages/AssistantTextMessage.tsx +144 -0
  82. package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
  83. package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
  84. package/src/components/messages/UserBashInputMessage.tsx +28 -0
  85. package/src/components/messages/UserCommandMessage.tsx +30 -0
  86. package/src/components/messages/UserKodingInputMessage.tsx +28 -0
  87. package/src/components/messages/UserPromptMessage.tsx +35 -0
  88. package/src/components/messages/UserTextMessage.tsx +39 -0
  89. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
  90. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
  91. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
  92. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
  93. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
  94. package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
  95. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
  96. package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
  97. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
  98. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
  99. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
  100. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
  101. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
  102. package/src/components/permissions/PermissionRequest.tsx +103 -0
  103. package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
  104. package/src/components/permissions/hooks.ts +44 -0
  105. package/src/components/permissions/toolUseOptions.ts +59 -0
  106. package/src/components/permissions/utils.ts +23 -0
  107. package/src/constants/betas.ts +5 -0
  108. package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
  109. package/src/constants/figures.ts +4 -0
  110. package/src/constants/keys.ts +3 -0
  111. package/src/constants/macros.ts +6 -0
  112. package/src/constants/models.ts +935 -0
  113. package/src/constants/oauth.ts +18 -0
  114. package/src/constants/product.ts +17 -0
  115. package/src/constants/prompts.ts +177 -0
  116. package/src/constants/releaseNotes.ts +7 -0
  117. package/src/context/PermissionContext.tsx +149 -0
  118. package/src/context.ts +278 -0
  119. package/src/cost-tracker.ts +84 -0
  120. package/src/entrypoints/cli.tsx +1498 -0
  121. package/src/entrypoints/mcp.ts +176 -0
  122. package/src/history.ts +25 -0
  123. package/src/hooks/useApiKeyVerification.ts +59 -0
  124. package/src/hooks/useArrowKeyHistory.ts +55 -0
  125. package/src/hooks/useCanUseTool.ts +138 -0
  126. package/src/hooks/useCancelRequest.ts +39 -0
  127. package/src/hooks/useDoublePress.ts +42 -0
  128. package/src/hooks/useExitOnCtrlCD.ts +31 -0
  129. package/src/hooks/useInterval.ts +25 -0
  130. package/src/hooks/useLogMessages.ts +16 -0
  131. package/src/hooks/useLogStartupTime.ts +12 -0
  132. package/src/hooks/useNotifyAfterTimeout.ts +65 -0
  133. package/src/hooks/usePermissionRequestLogging.ts +44 -0
  134. package/src/hooks/useSlashCommandTypeahead.ts +137 -0
  135. package/src/hooks/useTerminalSize.ts +49 -0
  136. package/src/hooks/useTextInput.ts +315 -0
  137. package/src/messages.ts +37 -0
  138. package/src/permissions.ts +268 -0
  139. package/src/query.ts +704 -0
  140. package/src/screens/ConfigureNpmPrefix.tsx +197 -0
  141. package/src/screens/Doctor.tsx +219 -0
  142. package/src/screens/LogList.tsx +68 -0
  143. package/src/screens/REPL.tsx +792 -0
  144. package/src/screens/ResumeConversation.tsx +68 -0
  145. package/src/services/browserMocks.ts +66 -0
  146. package/src/services/claude.ts +1947 -0
  147. package/src/services/customCommands.ts +683 -0
  148. package/src/services/fileFreshness.ts +377 -0
  149. package/src/services/mcpClient.ts +564 -0
  150. package/src/services/mcpServerApproval.tsx +50 -0
  151. package/src/services/notifier.ts +40 -0
  152. package/src/services/oauth.ts +357 -0
  153. package/src/services/openai.ts +796 -0
  154. package/src/services/sentry.ts +3 -0
  155. package/src/services/statsig.ts +171 -0
  156. package/src/services/statsigStorage.ts +86 -0
  157. package/src/services/systemReminder.ts +406 -0
  158. package/src/services/vcr.ts +161 -0
  159. package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
  160. package/src/tools/ArchitectTool/prompt.ts +15 -0
  161. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
  162. package/src/tools/BashTool/BashTool.tsx +270 -0
  163. package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
  164. package/src/tools/BashTool/OutputLine.tsx +48 -0
  165. package/src/tools/BashTool/prompt.ts +174 -0
  166. package/src/tools/BashTool/utils.ts +56 -0
  167. package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
  168. package/src/tools/FileEditTool/prompt.ts +51 -0
  169. package/src/tools/FileEditTool/utils.ts +58 -0
  170. package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
  171. package/src/tools/FileReadTool/prompt.ts +7 -0
  172. package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
  173. package/src/tools/FileWriteTool/prompt.ts +10 -0
  174. package/src/tools/GlobTool/GlobTool.tsx +119 -0
  175. package/src/tools/GlobTool/prompt.ts +8 -0
  176. package/src/tools/GrepTool/GrepTool.tsx +147 -0
  177. package/src/tools/GrepTool/prompt.ts +11 -0
  178. package/src/tools/MCPTool/MCPTool.tsx +106 -0
  179. package/src/tools/MCPTool/prompt.ts +3 -0
  180. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
  181. package/src/tools/MemoryReadTool/prompt.ts +3 -0
  182. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
  183. package/src/tools/MemoryWriteTool/prompt.ts +3 -0
  184. package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
  185. package/src/tools/MultiEditTool/prompt.ts +45 -0
  186. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
  187. package/src/tools/NotebookEditTool/prompt.ts +3 -0
  188. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
  189. package/src/tools/NotebookReadTool/prompt.ts +3 -0
  190. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
  191. package/src/tools/StickerRequestTool/prompt.ts +19 -0
  192. package/src/tools/TaskTool/TaskTool.tsx +382 -0
  193. package/src/tools/TaskTool/constants.ts +1 -0
  194. package/src/tools/TaskTool/prompt.ts +56 -0
  195. package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
  196. package/src/tools/ThinkTool/prompt.ts +12 -0
  197. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
  198. package/src/tools/TodoWriteTool/prompt.ts +63 -0
  199. package/src/tools/lsTool/lsTool.tsx +269 -0
  200. package/src/tools/lsTool/prompt.ts +2 -0
  201. package/src/tools.ts +63 -0
  202. package/src/types/PermissionMode.ts +120 -0
  203. package/src/types/RequestContext.ts +72 -0
  204. package/src/utils/Cursor.ts +436 -0
  205. package/src/utils/PersistentShell.ts +373 -0
  206. package/src/utils/agentStorage.ts +97 -0
  207. package/src/utils/array.ts +3 -0
  208. package/src/utils/ask.tsx +98 -0
  209. package/src/utils/auth.ts +13 -0
  210. package/src/utils/autoCompactCore.ts +223 -0
  211. package/src/utils/autoUpdater.ts +318 -0
  212. package/src/utils/betas.ts +20 -0
  213. package/src/utils/browser.ts +14 -0
  214. package/src/utils/cleanup.ts +72 -0
  215. package/src/utils/commands.ts +261 -0
  216. package/src/utils/config.ts +771 -0
  217. package/src/utils/conversationRecovery.ts +54 -0
  218. package/src/utils/debugLogger.ts +1123 -0
  219. package/src/utils/diff.ts +42 -0
  220. package/src/utils/env.ts +57 -0
  221. package/src/utils/errors.ts +21 -0
  222. package/src/utils/exampleCommands.ts +108 -0
  223. package/src/utils/execFileNoThrow.ts +51 -0
  224. package/src/utils/expertChatStorage.ts +136 -0
  225. package/src/utils/file.ts +402 -0
  226. package/src/utils/fileRecoveryCore.ts +71 -0
  227. package/src/utils/format.tsx +44 -0
  228. package/src/utils/generators.ts +62 -0
  229. package/src/utils/git.ts +92 -0
  230. package/src/utils/globalLogger.ts +77 -0
  231. package/src/utils/http.ts +10 -0
  232. package/src/utils/imagePaste.ts +38 -0
  233. package/src/utils/json.ts +13 -0
  234. package/src/utils/log.ts +382 -0
  235. package/src/utils/markdown.ts +213 -0
  236. package/src/utils/messageContextManager.ts +289 -0
  237. package/src/utils/messages.tsx +938 -0
  238. package/src/utils/model.ts +836 -0
  239. package/src/utils/permissions/filesystem.ts +118 -0
  240. package/src/utils/ripgrep.ts +167 -0
  241. package/src/utils/sessionState.ts +49 -0
  242. package/src/utils/state.ts +25 -0
  243. package/src/utils/style.ts +29 -0
  244. package/src/utils/terminal.ts +49 -0
  245. package/src/utils/theme.ts +122 -0
  246. package/src/utils/thinking.ts +144 -0
  247. package/src/utils/todoStorage.ts +431 -0
  248. package/src/utils/tokens.ts +43 -0
  249. package/src/utils/toolExecutionController.ts +163 -0
  250. package/src/utils/unaryLogging.ts +26 -0
  251. package/src/utils/user.ts +37 -0
  252. package/src/utils/validate.ts +165 -0
  253. package/cli.mjs +0 -1803
@@ -0,0 +1,373 @@
1
+ import * as fs from 'fs'
2
+ import { homedir } from 'os'
3
+ import { existsSync } from 'fs'
4
+ import shellquote from 'shell-quote'
5
+ import { spawn, execSync, type ChildProcess } from 'child_process'
6
+ import { isAbsolute, resolve, join } from 'path'
7
+ import { logError } from './log'
8
+ import * as os from 'os'
9
+ import { logEvent } from '../services/statsig'
10
+ import { PRODUCT_COMMAND } from '../constants/product'
11
+
12
+ type ExecResult = {
13
+ stdout: string
14
+ stderr: string
15
+ code: number
16
+ interrupted: boolean
17
+ }
18
+ type QueuedCommand = {
19
+ command: string
20
+ abortSignal?: AbortSignal
21
+ timeout?: number
22
+ resolve: (result: ExecResult) => void
23
+ reject: (error: Error) => void
24
+ }
25
+
26
+ const TEMPFILE_PREFIX = os.tmpdir() + `/${PRODUCT_COMMAND}-`
27
+ const DEFAULT_TIMEOUT = 30 * 60 * 1000
28
+ const SIGTERM_CODE = 143 // Standard exit code for SIGTERM
29
+ const FILE_SUFFIXES = {
30
+ STATUS: '-status',
31
+ STDOUT: '-stdout',
32
+ STDERR: '-stderr',
33
+ CWD: '-cwd',
34
+ }
35
+ const SHELL_CONFIGS: Record<string, string> = {
36
+ '/bin/bash': '.bashrc',
37
+ '/bin/zsh': '.zshrc',
38
+ }
39
+
40
+ export class PersistentShell {
41
+ private commandQueue: QueuedCommand[] = []
42
+ private isExecuting: boolean = false
43
+ private shell: ChildProcess
44
+ private isAlive: boolean = true
45
+ private commandInterrupted: boolean = false
46
+ private statusFile: string
47
+ private stdoutFile: string
48
+ private stderrFile: string
49
+ private cwdFile: string
50
+ private cwd: string
51
+ private binShell: string
52
+
53
+ constructor(cwd: string) {
54
+ this.binShell = process.env.SHELL || '/bin/bash'
55
+ this.shell = spawn(this.binShell, ['-l'], {
56
+ stdio: ['pipe', 'pipe', 'pipe'],
57
+ cwd,
58
+ env: {
59
+ ...process.env,
60
+ GIT_EDITOR: 'true',
61
+ },
62
+ })
63
+
64
+ this.cwd = cwd
65
+
66
+ this.shell.on('exit', (code, signal) => {
67
+ if (code) {
68
+ // TODO: It would be nice to alert the user that shell crashed
69
+ logError(`Shell exited with code ${code} and signal ${signal}`)
70
+ logEvent('persistent_shell_exit', {
71
+ code: code?.toString() || 'null',
72
+ signal: signal || 'null',
73
+ })
74
+ }
75
+ for (const file of [
76
+ this.statusFile,
77
+ this.stdoutFile,
78
+ this.stderrFile,
79
+ this.cwdFile,
80
+ ]) {
81
+ if (fs.existsSync(file)) {
82
+ fs.unlinkSync(file)
83
+ }
84
+ }
85
+ this.isAlive = false
86
+ })
87
+
88
+ const id = Math.floor(Math.random() * 0x10000)
89
+ .toString(16)
90
+ .padStart(4, '0')
91
+
92
+ this.statusFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STATUS
93
+ this.stdoutFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDOUT
94
+ this.stderrFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDERR
95
+ this.cwdFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.CWD
96
+ for (const file of [this.statusFile, this.stdoutFile, this.stderrFile]) {
97
+ fs.writeFileSync(file, '')
98
+ }
99
+ // Initialize CWD file with initial directory
100
+ fs.writeFileSync(this.cwdFile, cwd)
101
+ const configFile = SHELL_CONFIGS[this.binShell]
102
+ if (configFile) {
103
+ const configFilePath = join(homedir(), configFile)
104
+ if (existsSync(configFilePath)) {
105
+ this.sendToShell(`source ${configFilePath}`)
106
+ }
107
+ }
108
+ }
109
+
110
+ private static instance: PersistentShell | null = null
111
+
112
+ static restart() {
113
+ if (PersistentShell.instance) {
114
+ PersistentShell.instance.close()
115
+ PersistentShell.instance = null
116
+ }
117
+ }
118
+
119
+ static getInstance(): PersistentShell {
120
+ if (!PersistentShell.instance || !PersistentShell.instance.isAlive) {
121
+ PersistentShell.instance = new PersistentShell(process.cwd())
122
+ }
123
+ return PersistentShell.instance
124
+ }
125
+
126
+ killChildren() {
127
+ const parentPid = this.shell.pid
128
+ try {
129
+ const childPids = execSync(`pgrep -P ${parentPid}`)
130
+ .toString()
131
+ .trim()
132
+ .split('\n')
133
+ .filter(Boolean) // Filter out empty strings
134
+
135
+ if (childPids.length > 0) {
136
+ logEvent('persistent_shell_command_interrupted', {
137
+ numChildProcesses: childPids.length.toString(),
138
+ })
139
+ }
140
+
141
+ childPids.forEach(pid => {
142
+ try {
143
+ process.kill(Number(pid), 'SIGTERM')
144
+ } catch (error) {
145
+ logError(`Failed to kill process ${pid}: ${error}`)
146
+ logEvent('persistent_shell_kill_process_error', {
147
+ error: (error as Error).message.substring(0, 10),
148
+ })
149
+ }
150
+ })
151
+ } catch {
152
+ // pgrep returns non-zero when no processes are found - this is expected
153
+ } finally {
154
+ this.commandInterrupted = true
155
+ }
156
+ }
157
+
158
+ private async processQueue() {
159
+ /**
160
+ * Processes commands from the queue one at a time.
161
+ * Concurrency invariants:
162
+ * - Only one instance runs at a time (controlled by isExecuting)
163
+ * - Is the only caller of updateCwd() in the system
164
+ * - Calls updateCwd() after each command completes
165
+ * - Ensures commands execute serially via the queue
166
+ * - Handles interruption via abortSignal by calling killChildren()
167
+ * - Cleans up abortSignal listeners after command completion or interruption
168
+ */
169
+ if (this.isExecuting || this.commandQueue.length === 0) return
170
+
171
+ this.isExecuting = true
172
+ const { command, abortSignal, timeout, resolve, reject } =
173
+ this.commandQueue.shift()!
174
+
175
+ const killChildren = () => this.killChildren()
176
+ if (abortSignal) {
177
+ abortSignal.addEventListener('abort', killChildren)
178
+ }
179
+
180
+ try {
181
+ const result = await this.exec_(command, timeout)
182
+
183
+ // No need to update cwd - it's handled in exec_ via the CWD file
184
+
185
+ resolve(result)
186
+ } catch (error) {
187
+ logEvent('persistent_shell_command_error', {
188
+ error: (error as Error).message.substring(0, 10),
189
+ })
190
+ reject(error as Error)
191
+ } finally {
192
+ this.isExecuting = false
193
+ if (abortSignal) {
194
+ abortSignal.removeEventListener('abort', killChildren)
195
+ }
196
+ // Process next command in queue
197
+ this.processQueue()
198
+ }
199
+ }
200
+
201
+ async exec(
202
+ command: string,
203
+ abortSignal?: AbortSignal,
204
+ timeout?: number,
205
+ ): Promise<ExecResult> {
206
+ return new Promise((resolve, reject) => {
207
+ this.commandQueue.push({ command, abortSignal, timeout, resolve, reject })
208
+ this.processQueue()
209
+ })
210
+ }
211
+
212
+ private async exec_(command: string, timeout?: number): Promise<ExecResult> {
213
+ /**
214
+ * Direct command execution without going through the queue.
215
+ * Concurrency invariants:
216
+ * - Not safe for concurrent calls (uses shared files)
217
+ * - Called only when queue is idle
218
+ * - Relies on file-based IPC to handle shell interaction
219
+ * - Does not modify the command queue state
220
+ * - Tracks interruption state via commandInterrupted flag
221
+ * - Resets interruption state at start of new command
222
+ * - Reports interruption status in result object
223
+ *
224
+ * Exit Code & CWD Handling:
225
+ * - Executes command and immediately captures its exit code into a shell variable
226
+ * - Updates the CWD file with the working directory after capturing exit code
227
+ * - Writes the preserved exit code to the status file as the final step
228
+ * - This sequence eliminates race conditions between exit code capture and CWD updates
229
+ * - The pwd() method reads the CWD file directly for current directory info
230
+ */
231
+ const quotedCommand = shellquote.quote([command])
232
+
233
+ // Check the syntax of the command
234
+ try {
235
+ execSync(`${this.binShell} -n -c ${quotedCommand}`, {
236
+ stdio: 'ignore',
237
+ timeout: 1000,
238
+ })
239
+ } catch (stderr) {
240
+ // If there's a syntax error, return an error and log it
241
+ const errorStr =
242
+ typeof stderr === 'string' ? stderr : String(stderr || '')
243
+ logEvent('persistent_shell_syntax_error', {
244
+ error: errorStr.substring(0, 10),
245
+ })
246
+ return Promise.resolve({
247
+ stdout: '',
248
+ stderr: errorStr,
249
+ code: 128,
250
+ interrupted: false,
251
+ })
252
+ }
253
+
254
+ const commandTimeout = timeout || DEFAULT_TIMEOUT
255
+ // Reset interrupted state for new command
256
+ this.commandInterrupted = false
257
+ return new Promise<ExecResult>(resolve => {
258
+ // Truncate output files
259
+ fs.writeFileSync(this.stdoutFile, '')
260
+ fs.writeFileSync(this.stderrFile, '')
261
+ fs.writeFileSync(this.statusFile, '')
262
+ // Break up the command sequence for clarity using an array of commands
263
+ const commandParts = []
264
+
265
+ // 1. Execute the main command with redirections
266
+ commandParts.push(
267
+ `eval ${quotedCommand} < /dev/null > ${this.stdoutFile} 2> ${this.stderrFile}`,
268
+ )
269
+
270
+ // 2. Capture exit code immediately after command execution to avoid losing it
271
+ commandParts.push(`EXEC_EXIT_CODE=$?`)
272
+
273
+ // 3. Update CWD file
274
+ commandParts.push(`pwd > ${this.cwdFile}`)
275
+
276
+ // 4. Write the preserved exit code to status file to avoid race with pwd
277
+ commandParts.push(`echo $EXEC_EXIT_CODE > ${this.statusFile}`)
278
+
279
+ // Send the combined commands as a single operation to maintain atomicity
280
+ this.sendToShell(commandParts.join('\n'))
281
+
282
+ // Check for command completion or timeout
283
+ const start = Date.now()
284
+ const checkCompletion = setInterval(() => {
285
+ try {
286
+ let statusFileSize = 0
287
+ if (fs.existsSync(this.statusFile)) {
288
+ statusFileSize = fs.statSync(this.statusFile).size
289
+ }
290
+
291
+ if (
292
+ statusFileSize > 0 ||
293
+ Date.now() - start > commandTimeout ||
294
+ this.commandInterrupted
295
+ ) {
296
+ clearInterval(checkCompletion)
297
+ const stdout = fs.existsSync(this.stdoutFile)
298
+ ? fs.readFileSync(this.stdoutFile, 'utf8')
299
+ : ''
300
+ let stderr = fs.existsSync(this.stderrFile)
301
+ ? fs.readFileSync(this.stderrFile, 'utf8')
302
+ : ''
303
+ let code: number
304
+ if (statusFileSize) {
305
+ code = Number(fs.readFileSync(this.statusFile, 'utf8'))
306
+ } else {
307
+ // Timeout occurred - kill any running processes
308
+ this.killChildren()
309
+ code = SIGTERM_CODE
310
+ stderr += (stderr ? '\n' : '') + 'Command execution timed out'
311
+ logEvent('persistent_shell_command_timeout', {
312
+ command: command.substring(0, 10),
313
+ timeout: commandTimeout.toString(),
314
+ })
315
+ }
316
+ resolve({
317
+ stdout,
318
+ stderr,
319
+ code,
320
+ interrupted: this.commandInterrupted,
321
+ })
322
+ }
323
+ } catch {
324
+ // Ignore file system errors during polling - they are expected
325
+ // as we check for completion before files exist
326
+ }
327
+ }, 10) // increasing this will introduce latency
328
+ })
329
+ }
330
+
331
+ private sendToShell(command: string) {
332
+ try {
333
+ this.shell!.stdin!.write(command + '\n')
334
+ } catch (error) {
335
+ const errorString =
336
+ error instanceof Error
337
+ ? error.message
338
+ : String(error || 'Unknown error')
339
+ logError(`Error in sendToShell: ${errorString}`)
340
+ logEvent('persistent_shell_write_error', {
341
+ error: errorString.substring(0, 100),
342
+ command: command.substring(0, 30),
343
+ })
344
+ throw error
345
+ }
346
+ }
347
+
348
+ pwd(): string {
349
+ try {
350
+ const newCwd = fs.readFileSync(this.cwdFile, 'utf8').trim()
351
+ if (newCwd) {
352
+ this.cwd = newCwd
353
+ }
354
+ } catch (error) {
355
+ logError(`Shell pwd error ${error}`)
356
+ }
357
+ // Always return the cached value
358
+ return this.cwd
359
+ }
360
+
361
+ async setCwd(cwd: string) {
362
+ const resolved = isAbsolute(cwd) ? cwd : resolve(process.cwd(), cwd)
363
+ if (!existsSync(resolved)) {
364
+ throw new Error(`Path "${resolved}" does not exist`)
365
+ }
366
+ await this.exec(`cd ${resolved}`)
367
+ }
368
+
369
+ close(): void {
370
+ this.shell!.stdin!.end()
371
+ this.shell.kill()
372
+ }
373
+ }
@@ -0,0 +1,97 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
2
+ import { join } from 'path'
3
+ import { homedir } from 'os'
4
+ import { randomUUID } from 'crypto'
5
+
6
+ /**
7
+ * Agent Storage Utilities
8
+ * Provides file-based state isolation for different agents
9
+ * Based on Kode's Agent ID architecture
10
+ */
11
+
12
+ /**
13
+ * Get the kode config directory
14
+ */
15
+ function getConfigDirectory(): string {
16
+ return process.env.KODE_CONFIG_DIR ?? process.env.ANYKODE_CONFIG_DIR ?? join(homedir(), '.kode')
17
+ }
18
+
19
+ /**
20
+ * Get the current session ID
21
+ */
22
+ function getSessionId(): string {
23
+ // This should be set when the session starts
24
+ return process.env.ANYKODE_SESSION_ID ?? 'default-session'
25
+ }
26
+
27
+ /**
28
+ * Generate agent-specific file path
29
+ * Pattern: ${sessionId}-agent-${agentId}.json
30
+ * Stored in ~/.kode/ directory
31
+ */
32
+ export function getAgentFilePath(agentId: string): string {
33
+ const sessionId = getSessionId()
34
+ const filename = `${sessionId}-agent-${agentId}.json`
35
+ const configDir = getConfigDirectory()
36
+
37
+ // Ensure kode config directory exists
38
+ if (!existsSync(configDir)) {
39
+ mkdirSync(configDir, { recursive: true })
40
+ }
41
+
42
+ return join(configDir, filename)
43
+ }
44
+
45
+ /**
46
+ * Read agent-specific data from storage
47
+ */
48
+ export function readAgentData<T = any>(agentId: string): T | null {
49
+ const filePath = getAgentFilePath(agentId)
50
+
51
+ if (!existsSync(filePath)) {
52
+ return null
53
+ }
54
+
55
+ try {
56
+ const content = readFileSync(filePath, 'utf-8')
57
+ return JSON.parse(content) as T
58
+ } catch (error) {
59
+ console.error(`Failed to read agent data for ${agentId}:`, error)
60
+ return null
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Write agent-specific data to storage
66
+ */
67
+ export function writeAgentData<T = any>(agentId: string, data: T): void {
68
+ const filePath = getAgentFilePath(agentId)
69
+
70
+ try {
71
+ writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8')
72
+ } catch (error) {
73
+ console.error(`Failed to write agent data for ${agentId}:`, error)
74
+ throw error
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Get default agent ID if none is provided
80
+ */
81
+ export function getDefaultAgentId(): string {
82
+ return 'default'
83
+ }
84
+
85
+ /**
86
+ * Resolve agent ID from context
87
+ */
88
+ export function resolveAgentId(agentId?: string): string {
89
+ return agentId || getDefaultAgentId()
90
+ }
91
+
92
+ /**
93
+ * Generate a new unique Agent ID
94
+ */
95
+ export function generateAgentId(): string {
96
+ return randomUUID()
97
+ }
@@ -0,0 +1,3 @@
1
+ export function intersperse<A>(as: A[], separator: (index: number) => A): A[] {
2
+ return as.flatMap((a, i) => (i ? [separator(i), a] : [a]))
3
+ }
@@ -0,0 +1,98 @@
1
+ import { last } from 'lodash-es'
2
+ import { Command } from '../commands'
3
+ import { getSystemPrompt } from '../constants/prompts'
4
+ import { getContext } from '../context'
5
+ import { getTotalCost } from '../cost-tracker'
6
+ import { Message, query } from '../query'
7
+ import { CanUseToolFn } from '../hooks/useCanUseTool'
8
+ import { Tool } from '../Tool'
9
+ import { getModelManager } from '../utils/model'
10
+ import { setCwd } from './state'
11
+ import { getMessagesPath, overwriteLog } from './log'
12
+ import { createUserMessage } from './messages'
13
+
14
+ type Props = {
15
+ commands: Command[]
16
+ safeMode?: boolean
17
+ hasPermissionsToUseTool: CanUseToolFn
18
+ messageLogName: string
19
+ prompt: string
20
+ cwd: string
21
+ tools: Tool[]
22
+ verbose?: boolean
23
+ }
24
+
25
+ // Sends a single prompt to the Claude API and returns the response.
26
+ // Assumes that claude is being used non-interactively -- will not
27
+ // ask the user for permissions or further input.
28
+ export async function ask({
29
+ commands,
30
+ safeMode,
31
+ hasPermissionsToUseTool,
32
+ messageLogName,
33
+ prompt,
34
+ cwd,
35
+ tools,
36
+ verbose = false,
37
+ }: Props): Promise<{
38
+ resultText: string
39
+ totalCost: number
40
+ messageHistoryFile: string
41
+ }> {
42
+ await setCwd(cwd)
43
+ const message = createUserMessage(prompt)
44
+ const messages: Message[] = [message]
45
+
46
+ const [systemPrompt, context, model] = await Promise.all([
47
+ getSystemPrompt(),
48
+ getContext(),
49
+ getModelManager().getModelName('main'),
50
+ ])
51
+
52
+ for await (const m of query(
53
+ messages,
54
+ systemPrompt,
55
+ context,
56
+ hasPermissionsToUseTool,
57
+ {
58
+ options: {
59
+ commands,
60
+ tools,
61
+ verbose,
62
+ safeMode,
63
+ forkNumber: 0,
64
+ messageLogName: 'unused',
65
+ maxThinkingTokens: 0,
66
+ },
67
+ abortController: new AbortController(),
68
+ messageId: undefined,
69
+ readFileTimestamps: {},
70
+ },
71
+ )) {
72
+ messages.push(m)
73
+ }
74
+
75
+ const result = last(messages)
76
+ if (!result || result.type !== 'assistant') {
77
+ throw new Error('Expected content to be an assistant message')
78
+ }
79
+ if (result.message.content[0]?.type !== 'text') {
80
+ throw new Error(
81
+ `Expected first content item to be text, but got ${JSON.stringify(
82
+ result.message.content[0],
83
+ null,
84
+ 2,
85
+ )}`,
86
+ )
87
+ }
88
+
89
+ // Write log that can be retrieved with `claude log`
90
+ const messageHistoryFile = getMessagesPath(messageLogName, 0, 0)
91
+ overwriteLog(messageHistoryFile, messages)
92
+
93
+ return {
94
+ resultText: result.message.content[0].text,
95
+ totalCost: getTotalCost(),
96
+ messageHistoryFile,
97
+ }
98
+ }
@@ -0,0 +1,13 @@
1
+ import { USE_BEDROCK, USE_VERTEX } from './model'
2
+ import { getGlobalConfig } from './config'
3
+
4
+ export function isAnthropicAuthEnabled(): boolean {
5
+ return false
6
+ // return !(USE_BEDROCK || USE_VERTEX)
7
+ }
8
+
9
+ export function isLoggedInToAnthropic(): boolean {
10
+ return false
11
+ // const config = getGlobalConfig()
12
+ // return !!config.primaryApiKey
13
+ }