@shareai-lab/kode 1.1.13 → 1.1.16-dev.1

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 (288) hide show
  1. package/dist/entrypoints/cli.js +59 -38
  2. package/dist/entrypoints/cli.js.map +3 -3
  3. package/dist/index.js +5 -26
  4. package/dist/package.json +4 -1
  5. package/package.json +11 -104
  6. package/dist/test/testAdapters.js +0 -88
  7. package/dist/test/testAdapters.js.map +0 -1
  8. package/src/ProjectOnboarding.tsx +0 -198
  9. package/src/Tool.ts +0 -83
  10. package/src/commands/agents.tsx +0 -3416
  11. package/src/commands/approvedTools.ts +0 -53
  12. package/src/commands/bug.tsx +0 -20
  13. package/src/commands/clear.ts +0 -43
  14. package/src/commands/compact.ts +0 -120
  15. package/src/commands/config.tsx +0 -19
  16. package/src/commands/cost.ts +0 -18
  17. package/src/commands/ctx_viz.ts +0 -209
  18. package/src/commands/doctor.ts +0 -24
  19. package/src/commands/help.tsx +0 -19
  20. package/src/commands/init.ts +0 -37
  21. package/src/commands/listen.ts +0 -42
  22. package/src/commands/login.tsx +0 -51
  23. package/src/commands/logout.tsx +0 -40
  24. package/src/commands/mcp.ts +0 -41
  25. package/src/commands/model.tsx +0 -40
  26. package/src/commands/modelstatus.tsx +0 -20
  27. package/src/commands/onboarding.tsx +0 -34
  28. package/src/commands/pr_comments.ts +0 -59
  29. package/src/commands/refreshCommands.ts +0 -54
  30. package/src/commands/release-notes.ts +0 -34
  31. package/src/commands/resume.tsx +0 -31
  32. package/src/commands/review.ts +0 -49
  33. package/src/commands/terminalSetup.ts +0 -221
  34. package/src/commands.ts +0 -139
  35. package/src/components/ApproveApiKey.tsx +0 -93
  36. package/src/components/AsciiLogo.tsx +0 -13
  37. package/src/components/AutoUpdater.tsx +0 -148
  38. package/src/components/Bug.tsx +0 -367
  39. package/src/components/Config.tsx +0 -293
  40. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  41. package/src/components/Cost.tsx +0 -23
  42. package/src/components/CostThresholdDialog.tsx +0 -46
  43. package/src/components/CustomSelect/option-map.ts +0 -42
  44. package/src/components/CustomSelect/select-option.tsx +0 -78
  45. package/src/components/CustomSelect/select.tsx +0 -152
  46. package/src/components/CustomSelect/theme.ts +0 -45
  47. package/src/components/CustomSelect/use-select-state.ts +0 -414
  48. package/src/components/CustomSelect/use-select.ts +0 -35
  49. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  50. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  51. package/src/components/Help.tsx +0 -215
  52. package/src/components/HighlightedCode.tsx +0 -33
  53. package/src/components/InvalidConfigDialog.tsx +0 -113
  54. package/src/components/Link.tsx +0 -32
  55. package/src/components/LogSelector.tsx +0 -86
  56. package/src/components/Logo.tsx +0 -170
  57. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  58. package/src/components/MCPServerDialogCopy.tsx +0 -25
  59. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  60. package/src/components/Message.tsx +0 -221
  61. package/src/components/MessageResponse.tsx +0 -15
  62. package/src/components/MessageSelector.tsx +0 -211
  63. package/src/components/ModeIndicator.tsx +0 -88
  64. package/src/components/ModelConfig.tsx +0 -301
  65. package/src/components/ModelListManager.tsx +0 -227
  66. package/src/components/ModelSelector.tsx +0 -3387
  67. package/src/components/ModelStatusDisplay.tsx +0 -230
  68. package/src/components/Onboarding.tsx +0 -274
  69. package/src/components/PressEnterToContinue.tsx +0 -11
  70. package/src/components/PromptInput.tsx +0 -760
  71. package/src/components/SentryErrorBoundary.ts +0 -39
  72. package/src/components/Spinner.tsx +0 -129
  73. package/src/components/StickerRequestForm.tsx +0 -16
  74. package/src/components/StructuredDiff.tsx +0 -191
  75. package/src/components/TextInput.tsx +0 -259
  76. package/src/components/TodoItem.tsx +0 -47
  77. package/src/components/TokenWarning.tsx +0 -31
  78. package/src/components/ToolUseLoader.tsx +0 -40
  79. package/src/components/TrustDialog.tsx +0 -106
  80. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  81. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  82. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  83. package/src/components/binary-feedback/utils.ts +0 -220
  84. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  85. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  86. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  87. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  88. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  89. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  90. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  91. package/src/components/messages/TaskToolMessage.tsx +0 -58
  92. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  93. package/src/components/messages/UserCommandMessage.tsx +0 -30
  94. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  95. package/src/components/messages/UserPromptMessage.tsx +0 -35
  96. package/src/components/messages/UserTextMessage.tsx +0 -39
  97. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  98. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  99. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  100. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  101. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  102. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  103. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  104. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  105. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  107. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  108. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  109. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  110. package/src/components/permissions/PermissionRequest.tsx +0 -101
  111. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  112. package/src/components/permissions/hooks.ts +0 -44
  113. package/src/components/permissions/toolUseOptions.ts +0 -59
  114. package/src/components/permissions/utils.ts +0 -23
  115. package/src/constants/betas.ts +0 -5
  116. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  117. package/src/constants/figures.ts +0 -4
  118. package/src/constants/keys.ts +0 -3
  119. package/src/constants/macros.ts +0 -11
  120. package/src/constants/modelCapabilities.ts +0 -179
  121. package/src/constants/models.ts +0 -1025
  122. package/src/constants/oauth.ts +0 -18
  123. package/src/constants/product.ts +0 -17
  124. package/src/constants/prompts.ts +0 -168
  125. package/src/constants/releaseNotes.ts +0 -7
  126. package/src/context/PermissionContext.tsx +0 -149
  127. package/src/context.ts +0 -278
  128. package/src/cost-tracker.ts +0 -84
  129. package/src/entrypoints/cli.tsx +0 -1561
  130. package/src/entrypoints/mcp.ts +0 -175
  131. package/src/history.ts +0 -25
  132. package/src/hooks/useApiKeyVerification.ts +0 -59
  133. package/src/hooks/useArrowKeyHistory.ts +0 -55
  134. package/src/hooks/useCanUseTool.ts +0 -138
  135. package/src/hooks/useCancelRequest.ts +0 -39
  136. package/src/hooks/useDoublePress.ts +0 -41
  137. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  138. package/src/hooks/useInterval.ts +0 -25
  139. package/src/hooks/useLogMessages.ts +0 -16
  140. package/src/hooks/useLogStartupTime.ts +0 -12
  141. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  142. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  143. package/src/hooks/useTerminalSize.ts +0 -49
  144. package/src/hooks/useTextInput.ts +0 -317
  145. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  146. package/src/index.ts +0 -34
  147. package/src/messages.ts +0 -38
  148. package/src/permissions.ts +0 -268
  149. package/src/query.ts +0 -720
  150. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  151. package/src/screens/Doctor.tsx +0 -219
  152. package/src/screens/LogList.tsx +0 -68
  153. package/src/screens/REPL.tsx +0 -813
  154. package/src/screens/ResumeConversation.tsx +0 -68
  155. package/src/services/adapters/base.ts +0 -38
  156. package/src/services/adapters/chatCompletions.ts +0 -90
  157. package/src/services/adapters/responsesAPI.ts +0 -170
  158. package/src/services/browserMocks.ts +0 -66
  159. package/src/services/claude.ts +0 -2197
  160. package/src/services/customCommands.ts +0 -704
  161. package/src/services/fileFreshness.ts +0 -377
  162. package/src/services/gpt5ConnectionTest.ts +0 -340
  163. package/src/services/mcpClient.ts +0 -564
  164. package/src/services/mcpServerApproval.tsx +0 -50
  165. package/src/services/mentionProcessor.ts +0 -273
  166. package/src/services/modelAdapterFactory.ts +0 -69
  167. package/src/services/notifier.ts +0 -40
  168. package/src/services/oauth.ts +0 -357
  169. package/src/services/openai.ts +0 -1359
  170. package/src/services/responseStateManager.ts +0 -90
  171. package/src/services/sentry.ts +0 -3
  172. package/src/services/statsig.ts +0 -172
  173. package/src/services/statsigStorage.ts +0 -86
  174. package/src/services/systemReminder.ts +0 -507
  175. package/src/services/vcr.ts +0 -161
  176. package/src/test/testAdapters.ts +0 -96
  177. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  178. package/src/tools/ArchitectTool/prompt.ts +0 -15
  179. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  180. package/src/tools/BashTool/BashTool.tsx +0 -243
  181. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  182. package/src/tools/BashTool/OutputLine.tsx +0 -49
  183. package/src/tools/BashTool/prompt.ts +0 -174
  184. package/src/tools/BashTool/utils.ts +0 -56
  185. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  186. package/src/tools/FileEditTool/prompt.ts +0 -51
  187. package/src/tools/FileEditTool/utils.ts +0 -58
  188. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  189. package/src/tools/FileReadTool/prompt.ts +0 -7
  190. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  191. package/src/tools/FileWriteTool/prompt.ts +0 -10
  192. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  193. package/src/tools/GlobTool/prompt.ts +0 -8
  194. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  195. package/src/tools/GrepTool/prompt.ts +0 -11
  196. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  197. package/src/tools/MCPTool/prompt.ts +0 -3
  198. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  199. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  200. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  201. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  202. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  203. package/src/tools/MultiEditTool/prompt.ts +0 -45
  204. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  205. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  206. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  207. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  208. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  209. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  210. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  211. package/src/tools/TaskTool/constants.ts +0 -1
  212. package/src/tools/TaskTool/prompt.ts +0 -92
  213. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  214. package/src/tools/ThinkTool/prompt.ts +0 -12
  215. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  216. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  217. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  218. package/src/tools/URLFetcherTool/cache.ts +0 -55
  219. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  220. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  221. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  222. package/src/tools/WebSearchTool/prompt.ts +0 -13
  223. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  224. package/src/tools/lsTool/lsTool.tsx +0 -272
  225. package/src/tools/lsTool/prompt.ts +0 -2
  226. package/src/tools.ts +0 -67
  227. package/src/types/PermissionMode.ts +0 -120
  228. package/src/types/RequestContext.ts +0 -72
  229. package/src/types/common.d.ts +0 -2
  230. package/src/types/conversation.ts +0 -51
  231. package/src/types/logs.ts +0 -58
  232. package/src/types/modelCapabilities.ts +0 -64
  233. package/src/types/notebook.ts +0 -87
  234. package/src/utils/Cursor.ts +0 -436
  235. package/src/utils/PersistentShell.ts +0 -552
  236. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  237. package/src/utils/agentLoader.ts +0 -278
  238. package/src/utils/agentStorage.ts +0 -97
  239. package/src/utils/array.ts +0 -3
  240. package/src/utils/ask.tsx +0 -99
  241. package/src/utils/auth.ts +0 -13
  242. package/src/utils/autoCompactCore.ts +0 -223
  243. package/src/utils/autoUpdater.ts +0 -458
  244. package/src/utils/betas.ts +0 -20
  245. package/src/utils/browser.ts +0 -14
  246. package/src/utils/cleanup.ts +0 -72
  247. package/src/utils/commands.ts +0 -261
  248. package/src/utils/commonUnixCommands.ts +0 -161
  249. package/src/utils/config.ts +0 -945
  250. package/src/utils/conversationRecovery.ts +0 -55
  251. package/src/utils/debugLogger.ts +0 -1235
  252. package/src/utils/diff.ts +0 -42
  253. package/src/utils/env.ts +0 -57
  254. package/src/utils/errors.ts +0 -21
  255. package/src/utils/exampleCommands.ts +0 -109
  256. package/src/utils/execFileNoThrow.ts +0 -51
  257. package/src/utils/expertChatStorage.ts +0 -136
  258. package/src/utils/file.ts +0 -405
  259. package/src/utils/fileRecoveryCore.ts +0 -71
  260. package/src/utils/format.tsx +0 -44
  261. package/src/utils/fuzzyMatcher.ts +0 -328
  262. package/src/utils/generators.ts +0 -62
  263. package/src/utils/git.ts +0 -92
  264. package/src/utils/globalLogger.ts +0 -77
  265. package/src/utils/http.ts +0 -10
  266. package/src/utils/imagePaste.ts +0 -38
  267. package/src/utils/json.ts +0 -13
  268. package/src/utils/log.ts +0 -382
  269. package/src/utils/markdown.ts +0 -213
  270. package/src/utils/messageContextManager.ts +0 -294
  271. package/src/utils/messages.tsx +0 -945
  272. package/src/utils/model.ts +0 -914
  273. package/src/utils/permissions/filesystem.ts +0 -127
  274. package/src/utils/responseState.ts +0 -23
  275. package/src/utils/ripgrep.ts +0 -167
  276. package/src/utils/secureFile.ts +0 -564
  277. package/src/utils/sessionState.ts +0 -49
  278. package/src/utils/state.ts +0 -25
  279. package/src/utils/style.ts +0 -29
  280. package/src/utils/terminal.ts +0 -50
  281. package/src/utils/theme.ts +0 -127
  282. package/src/utils/thinking.ts +0 -144
  283. package/src/utils/todoStorage.ts +0 -431
  284. package/src/utils/tokens.ts +0 -43
  285. package/src/utils/toolExecutionController.ts +0 -163
  286. package/src/utils/unaryLogging.ts +0 -26
  287. package/src/utils/user.ts +0 -37
  288. package/src/utils/validate.ts +0 -165
@@ -1,507 +0,0 @@
1
- import { getTodos, TodoItem } from '../utils/todoStorage'
2
- import { logEvent } from './statsig'
3
-
4
- export interface ReminderMessage {
5
- role: 'system'
6
- content: string
7
- isMeta: boolean
8
- timestamp: number
9
- type: string
10
- priority: 'low' | 'medium' | 'high'
11
- category: 'task' | 'security' | 'performance' | 'general'
12
- }
13
-
14
- interface ReminderConfig {
15
- todoEmptyReminder: boolean
16
- securityReminder: boolean
17
- performanceReminder: boolean
18
- maxRemindersPerSession: number
19
- }
20
-
21
- interface SessionReminderState {
22
- lastTodoUpdate: number
23
- lastFileAccess: number
24
- sessionStartTime: number
25
- remindersSent: Set<string>
26
- contextPresent: boolean
27
- reminderCount: number
28
- config: ReminderConfig
29
- }
30
-
31
- class SystemReminderService {
32
- private sessionState: SessionReminderState = {
33
- lastTodoUpdate: 0,
34
- lastFileAccess: 0,
35
- sessionStartTime: Date.now(),
36
- remindersSent: new Set(),
37
- contextPresent: false,
38
- reminderCount: 0,
39
- config: {
40
- todoEmptyReminder: true,
41
- securityReminder: true,
42
- performanceReminder: true,
43
- maxRemindersPerSession: 10,
44
- },
45
- }
46
-
47
- private eventDispatcher = new Map<string, Array<(context: any) => void>>()
48
- private reminderCache = new Map<string, ReminderMessage>()
49
-
50
- constructor() {
51
- this.setupEventDispatcher()
52
- }
53
-
54
- /**
55
- * Conditional reminder injection - only when context is present
56
- * Enhanced with performance optimizations and priority management
57
- */
58
- public generateReminders(
59
- hasContext: boolean = false,
60
- agentId?: string,
61
- ): ReminderMessage[] {
62
- this.sessionState.contextPresent = hasContext
63
-
64
- // Only inject when context is present (matching original behavior)
65
- if (!hasContext) {
66
- return []
67
- }
68
-
69
- // Check session reminder limit to prevent overload
70
- if (
71
- this.sessionState.reminderCount >=
72
- this.sessionState.config.maxRemindersPerSession
73
- ) {
74
- return []
75
- }
76
-
77
- const reminders: ReminderMessage[] = []
78
- const currentTime = Date.now()
79
-
80
- // Use lazy evaluation for performance with agent context
81
- const reminderGenerators = [
82
- () => this.dispatchTodoEvent(agentId),
83
- () => this.dispatchSecurityEvent(),
84
- () => this.dispatchPerformanceEvent(),
85
- () => this.getMentionReminders(), // Add mention reminders
86
- ]
87
-
88
- for (const generator of reminderGenerators) {
89
- if (reminders.length >= 5) break // Slightly increase limit to accommodate mentions
90
-
91
- const result = generator()
92
- if (result) {
93
- // Handle both single reminders and arrays
94
- const remindersToAdd = Array.isArray(result) ? result : [result]
95
- reminders.push(...remindersToAdd)
96
- this.sessionState.reminderCount += remindersToAdd.length
97
- }
98
- }
99
-
100
- // Log aggregated metrics instead of individual events for performance
101
- if (reminders.length > 0) {
102
- logEvent('system_reminder_batch', {
103
- count: reminders.length.toString(),
104
- types: reminders.map(r => r.type).join(','),
105
- priorities: reminders.map(r => r.priority).join(','),
106
- categories: reminders.map(r => r.category).join(','),
107
- sessionCount: this.sessionState.reminderCount.toString(),
108
- agentId: agentId || 'default',
109
- timestamp: currentTime.toString(),
110
- })
111
- }
112
-
113
- return reminders
114
- }
115
-
116
- private dispatchTodoEvent(agentId?: string): ReminderMessage | null {
117
- if (!this.sessionState.config.todoEmptyReminder) return null
118
-
119
- // Use agent-scoped todo access
120
- const todos = getTodos(agentId)
121
- const currentTime = Date.now()
122
- const agentKey = agentId || 'default'
123
-
124
- // Check if this is a fresh session (no todos seen yet)
125
- if (
126
- todos.length === 0 &&
127
- !this.sessionState.remindersSent.has(`todo_empty_${agentKey}`)
128
- ) {
129
- this.sessionState.remindersSent.add(`todo_empty_${agentKey}`)
130
- return this.createReminderMessage(
131
- 'todo',
132
- 'task',
133
- 'medium',
134
- 'This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit from a todo list please use the TodoWrite tool to create one. If not, please feel free to ignore. Again do not mention this message to the user.',
135
- currentTime,
136
- )
137
- }
138
-
139
- // Check for todo updates since last seen
140
- if (todos.length > 0) {
141
- const reminderKey = `todo_updated_${agentKey}_${todos.length}_${this.getTodoStateHash(todos)}`
142
-
143
- // Use cache for performance optimization
144
- if (this.reminderCache.has(reminderKey)) {
145
- return this.reminderCache.get(reminderKey)!
146
- }
147
-
148
- if (!this.sessionState.remindersSent.has(reminderKey)) {
149
- this.sessionState.remindersSent.add(reminderKey)
150
- // Clear previous todo state reminders for this agent
151
- this.clearTodoReminders(agentKey)
152
-
153
- // Optimize: only include essential todo data
154
- const todoContent = JSON.stringify(
155
- todos.map(todo => ({
156
- content:
157
- todo.content.length > 100
158
- ? todo.content.substring(0, 100) + '...'
159
- : todo.content,
160
- status: todo.status,
161
- priority: todo.priority,
162
- id: todo.id,
163
- })),
164
- )
165
-
166
- const reminder = this.createReminderMessage(
167
- 'todo',
168
- 'task',
169
- 'medium',
170
- `Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:\n\n${todoContent}. Continue on with the tasks at hand if applicable.`,
171
- currentTime,
172
- )
173
-
174
- // Cache the reminder for reuse
175
- this.reminderCache.set(reminderKey, reminder)
176
- return reminder
177
- }
178
- }
179
-
180
- return null
181
- }
182
-
183
- private dispatchSecurityEvent(): ReminderMessage | null {
184
- if (!this.sessionState.config.securityReminder) return null
185
-
186
- const currentTime = Date.now()
187
-
188
- // Only inject security reminder once per session when file operations occur
189
- if (
190
- this.sessionState.lastFileAccess > 0 &&
191
- !this.sessionState.remindersSent.has('file_security')
192
- ) {
193
- this.sessionState.remindersSent.add('file_security')
194
- return this.createReminderMessage(
195
- 'security',
196
- 'security',
197
- 'high',
198
- 'Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.',
199
- currentTime,
200
- )
201
- }
202
-
203
- return null
204
- }
205
-
206
- private dispatchPerformanceEvent(): ReminderMessage | null {
207
- if (!this.sessionState.config.performanceReminder) return null
208
-
209
- const currentTime = Date.now()
210
- const sessionDuration = currentTime - this.sessionState.sessionStartTime
211
-
212
- // Remind about performance after long sessions (30 minutes)
213
- if (
214
- sessionDuration > 30 * 60 * 1000 &&
215
- !this.sessionState.remindersSent.has('performance_long_session')
216
- ) {
217
- this.sessionState.remindersSent.add('performance_long_session')
218
- return this.createReminderMessage(
219
- 'performance',
220
- 'performance',
221
- 'low',
222
- 'Long session detected. Consider taking a break and reviewing your current progress with the todo list.',
223
- currentTime,
224
- )
225
- }
226
-
227
- return null
228
- }
229
-
230
- /**
231
- * Retrieve cached mention reminders
232
- * Returns recent mentions (within 5 seconds) that haven't expired
233
- */
234
- private getMentionReminders(): ReminderMessage[] {
235
- const currentTime = Date.now()
236
- const MENTION_FRESHNESS_WINDOW = 5000 // 5 seconds
237
- const reminders: ReminderMessage[] = []
238
- const expiredKeys: string[] = []
239
-
240
- // Single pass through cache for both collection and cleanup identification
241
- for (const [key, reminder] of this.reminderCache.entries()) {
242
- if (this.isMentionReminder(reminder)) {
243
- const age = currentTime - reminder.timestamp
244
- if (age <= MENTION_FRESHNESS_WINDOW) {
245
- reminders.push(reminder)
246
- } else {
247
- expiredKeys.push(key)
248
- }
249
- }
250
- }
251
-
252
- // Clean up expired mention reminders in separate pass for performance
253
- expiredKeys.forEach(key => this.reminderCache.delete(key))
254
-
255
- return reminders
256
- }
257
-
258
- /**
259
- * Type guard for mention reminders - centralized type checking
260
- * Eliminates hardcoded type strings scattered throughout the code
261
- */
262
- private isMentionReminder(reminder: ReminderMessage): boolean {
263
- const mentionTypes = ['agent_mention', 'file_mention', 'ask_model_mention']
264
- return mentionTypes.includes(reminder.type)
265
- }
266
-
267
- /**
268
- * Generate reminders for external file changes
269
- * Called when todo files are modified externally
270
- */
271
- public generateFileChangeReminder(context: any): ReminderMessage | null {
272
- const { agentId, filePath, reminder } = context
273
-
274
- if (!reminder) {
275
- return null
276
- }
277
-
278
- const currentTime = Date.now()
279
- const reminderKey = `file_changed_${agentId}_${filePath}_${currentTime}`
280
-
281
- // Ensure this specific file change reminder is only shown once
282
- if (this.sessionState.remindersSent.has(reminderKey)) {
283
- return null
284
- }
285
-
286
- this.sessionState.remindersSent.add(reminderKey)
287
-
288
- return this.createReminderMessage(
289
- 'file_changed',
290
- 'general',
291
- 'medium',
292
- reminder,
293
- currentTime,
294
- )
295
- }
296
-
297
- private createReminderMessage(
298
- type: string,
299
- category: ReminderMessage['category'],
300
- priority: ReminderMessage['priority'],
301
- content: string,
302
- timestamp: number,
303
- ): ReminderMessage {
304
- return {
305
- role: 'system',
306
- content: `<system-reminder>\n${content}\n</system-reminder>`,
307
- isMeta: true,
308
- timestamp,
309
- type,
310
- priority,
311
- category,
312
- }
313
- }
314
-
315
- private getTodoStateHash(todos: TodoItem[]): string {
316
- return todos
317
- .map(t => `${t.id}:${t.status}`)
318
- .sort()
319
- .join('|')
320
- }
321
-
322
- private clearTodoReminders(agentId?: string): void {
323
- const agentKey = agentId || 'default'
324
- for (const key of this.sessionState.remindersSent) {
325
- if (key.startsWith(`todo_updated_${agentKey}_`)) {
326
- this.sessionState.remindersSent.delete(key)
327
- }
328
- }
329
- }
330
-
331
- private setupEventDispatcher(): void {
332
- // Session startup events
333
- this.addEventListener('session:startup', context => {
334
- // Reset session state on startup
335
- this.resetSession()
336
-
337
- // Initialize session tracking
338
- this.sessionState.sessionStartTime = Date.now()
339
- this.sessionState.contextPresent =
340
- Object.keys(context.context || {}).length > 0
341
-
342
- // Log session startup
343
- logEvent('system_reminder_session_startup', {
344
- agentId: context.agentId || 'default',
345
- contextKeys: Object.keys(context.context || {}).join(','),
346
- messageCount: (context.messages || 0).toString(),
347
- timestamp: context.timestamp.toString(),
348
- })
349
- })
350
-
351
- // Todo change events
352
- this.addEventListener('todo:changed', context => {
353
- this.sessionState.lastTodoUpdate = Date.now()
354
- this.clearTodoReminders(context.agentId)
355
- })
356
-
357
- // Todo file changed externally
358
- this.addEventListener('todo:file_changed', context => {
359
- // External file change detected, trigger reminder injection
360
- const agentId = context.agentId || 'default'
361
- this.clearTodoReminders(agentId)
362
- this.sessionState.lastTodoUpdate = Date.now()
363
-
364
- // Generate and inject file change reminder immediately
365
- const reminder = this.generateFileChangeReminder(context)
366
- if (reminder) {
367
- // Inject reminder into the latest user message through event system
368
- this.emitEvent('reminder:inject', {
369
- reminder: reminder.content,
370
- agentId,
371
- type: 'file_changed',
372
- timestamp: Date.now(),
373
- })
374
- }
375
- })
376
-
377
- // File access events
378
- this.addEventListener('file:read', context => {
379
- this.sessionState.lastFileAccess = Date.now()
380
- })
381
-
382
- // File edit events for freshness detection
383
- this.addEventListener('file:edited', context => {
384
- // File edit handling
385
- })
386
-
387
- // Unified mention event handlers - eliminates code duplication
388
- this.addEventListener('agent:mentioned', context => {
389
- this.createMentionReminder({
390
- type: 'agent_mention',
391
- key: `agent_mention_${context.agentType}_${context.timestamp}`,
392
- category: 'task',
393
- priority: 'high',
394
- content: `The user mentioned @${context.originalMention}. You MUST use the Task tool with subagent_type="${context.agentType}" to delegate this task to the specified agent. Provide a detailed, self-contained task description that fully captures the user's intent for the ${context.agentType} agent to execute.`,
395
- timestamp: context.timestamp
396
- })
397
- })
398
-
399
- this.addEventListener('file:mentioned', context => {
400
- this.createMentionReminder({
401
- type: 'file_mention',
402
- key: `file_mention_${context.filePath}_${context.timestamp}`,
403
- category: 'general',
404
- priority: 'high',
405
- content: `The user mentioned @${context.originalMention}. You MUST read the entire content of the file at path: ${context.filePath} using the Read tool to understand the full context before proceeding with the user's request.`,
406
- timestamp: context.timestamp
407
- })
408
- })
409
-
410
- this.addEventListener('ask-model:mentioned', context => {
411
- this.createMentionReminder({
412
- type: 'ask_model_mention',
413
- key: `ask_model_mention_${context.modelName}_${context.timestamp}`,
414
- category: 'task',
415
- priority: 'high',
416
- content: `The user mentioned @${context.modelName}. You MUST use the AskExpertModelTool to consult this specific model for expert opinions and analysis. Provide the user's question or context clearly to get the most relevant response from ${context.modelName}.`,
417
- timestamp: context.timestamp
418
- })
419
- })
420
- }
421
-
422
- public addEventListener(
423
- event: string,
424
- callback: (context: any) => void,
425
- ): void {
426
- if (!this.eventDispatcher.has(event)) {
427
- this.eventDispatcher.set(event, [])
428
- }
429
- this.eventDispatcher.get(event)!.push(callback)
430
- }
431
-
432
- public emitEvent(event: string, context: any): void {
433
- const listeners = this.eventDispatcher.get(event) || []
434
- listeners.forEach(callback => {
435
- try {
436
- callback(context)
437
- } catch (error) {
438
- console.error(`Error in event listener for ${event}:`, error)
439
- }
440
- })
441
- }
442
-
443
- /**
444
- * Unified mention reminder creation - eliminates duplicate logic
445
- * Centralizes reminder creation with consistent deduplication
446
- */
447
- private createMentionReminder(params: {
448
- type: string
449
- key: string
450
- category: ReminderMessage['category']
451
- priority: ReminderMessage['priority']
452
- content: string
453
- timestamp: number
454
- }): void {
455
- if (!this.sessionState.remindersSent.has(params.key)) {
456
- this.sessionState.remindersSent.add(params.key)
457
-
458
- const reminder = this.createReminderMessage(
459
- params.type,
460
- params.category,
461
- params.priority,
462
- params.content,
463
- params.timestamp
464
- )
465
-
466
- this.reminderCache.set(params.key, reminder)
467
- }
468
- }
469
-
470
- public resetSession(): void {
471
- this.sessionState = {
472
- lastTodoUpdate: 0,
473
- lastFileAccess: 0,
474
- sessionStartTime: Date.now(),
475
- remindersSent: new Set(),
476
- contextPresent: false,
477
- reminderCount: 0,
478
- config: { ...this.sessionState.config }, // Preserve config across resets
479
- }
480
- this.reminderCache.clear() // Clear cache on session reset
481
- }
482
-
483
- public updateConfig(config: Partial<ReminderConfig>): void {
484
- this.sessionState.config = { ...this.sessionState.config, ...config }
485
- }
486
-
487
- public getSessionState(): SessionReminderState {
488
- return { ...this.sessionState }
489
- }
490
- }
491
-
492
- export const systemReminderService = new SystemReminderService()
493
-
494
- export const generateSystemReminders = (
495
- hasContext: boolean = false,
496
- agentId?: string,
497
- ) => systemReminderService.generateReminders(hasContext, agentId)
498
-
499
- export const generateFileChangeReminder = (context: any) =>
500
- systemReminderService.generateFileChangeReminder(context)
501
-
502
- export const emitReminderEvent = (event: string, context: any) =>
503
- systemReminderService.emitEvent(event, context)
504
-
505
- export const resetReminderSession = () => systemReminderService.resetSession()
506
- export const getReminderSessionState = () =>
507
- systemReminderService.getSessionState()
@@ -1,161 +0,0 @@
1
- import { createHash, type UUID } from 'crypto'
2
- import { mkdirSync, readFileSync, writeFileSync } from 'fs'
3
- import { dirname } from 'path'
4
- import type { AssistantMessage, UserMessage } from '../query'
5
- import { existsSync } from 'fs'
6
- import { env } from '../utils/env'
7
- import { getCwd } from '../utils/state'
8
- import * as path from 'path'
9
- import { mapValues } from 'lodash-es'
10
- import type { ContentBlock } from '@anthropic-ai/sdk/resources/index.mjs'
11
-
12
- export async function withVCR(
13
- messages: (UserMessage | AssistantMessage)[],
14
- f: () => Promise<AssistantMessage>,
15
- ): Promise<AssistantMessage> {
16
- if (process.env.NODE_ENV !== 'test') {
17
- return await f()
18
- }
19
-
20
- const dehydratedInput = mapMessages(
21
- messages.map(_ => _.message.content),
22
- dehydrateValue,
23
- )
24
- const filename = `./fixtures/${dehydratedInput.map(_ => createHash('sha1').update(JSON.stringify(_)).digest('hex').slice(0, 6)).join('-')}.json`
25
-
26
- // Fetch cached fixture
27
- if (existsSync(filename)) {
28
- const cached = JSON.parse(readFileSync(filename, 'utf-8'))
29
- return mapAssistantMessage(cached.output, hydrateValue)
30
- }
31
-
32
- if (env.isCI) {
33
- console.warn(
34
- `Anthropic API fixture missing. Re-run npm test locally, then commit the result. ${JSON.stringify({ input: dehydratedInput }, null, 2)}`,
35
- )
36
- }
37
-
38
- // Create & write new fixture
39
- const result = await f()
40
- if (env.isCI) {
41
- return result
42
- }
43
-
44
- if (!existsSync(dirname(filename))) {
45
- mkdirSync(dirname(filename), { recursive: true })
46
- }
47
- writeFileSync(
48
- filename,
49
- JSON.stringify(
50
- {
51
- input: dehydratedInput,
52
- output: mapAssistantMessage(result, dehydrateValue),
53
- },
54
- null,
55
- 2,
56
- ),
57
- )
58
- return result
59
- }
60
-
61
- function mapMessages(
62
- messages: (UserMessage | AssistantMessage)['message']['content'][],
63
- f: (s: unknown) => unknown,
64
- ): (UserMessage | AssistantMessage)['message']['content'][] {
65
- return messages.map(_ => {
66
- if (typeof _ === 'string') {
67
- return f(_)
68
- }
69
- return _.map(_ => {
70
- switch (_.type) {
71
- case 'tool_result':
72
- if (typeof _.content === 'string') {
73
- return { ..._, content: f(_.content) }
74
- }
75
- if (Array.isArray(_.content)) {
76
- return {
77
- ..._,
78
- content: _.content.map(_ => {
79
- switch (_.type) {
80
- case 'text':
81
- return { ..._, text: f(_.text) }
82
- case 'image':
83
- return _
84
- }
85
- }),
86
- }
87
- }
88
- return _
89
- case 'text':
90
- return { ..._, text: f(_.text) }
91
- case 'tool_use':
92
- return {
93
- ..._,
94
- input: mapValues(_.input as Record<string, unknown>, f),
95
- }
96
- case 'image':
97
- return _
98
- }
99
- })
100
- }) as (UserMessage | AssistantMessage)['message']['content'][]
101
- }
102
-
103
- function mapAssistantMessage(
104
- message: AssistantMessage,
105
- f: (s: unknown) => unknown,
106
- ): AssistantMessage {
107
- return {
108
- durationMs: 'DURATION' as unknown as number,
109
- costUSD: 'COST' as unknown as number,
110
- uuid: 'UUID' as unknown as UUID,
111
- message: {
112
- ...message.message,
113
- content: message.message.content
114
- .map(_ => {
115
- switch (_.type) {
116
- case 'text':
117
- return {
118
- ..._,
119
- text: f(_.text) as string,
120
- citations: _.citations || [],
121
- } // Ensure citations
122
- case 'tool_use':
123
- return {
124
- ..._,
125
- input: mapValues(_.input as Record<string, unknown>, f),
126
- }
127
- default:
128
- return _ // Handle other block types unchanged
129
- }
130
- })
131
- .filter(Boolean) as ContentBlock[],
132
- },
133
- type: 'assistant',
134
- }
135
- }
136
-
137
- function dehydrateValue(s: unknown): unknown {
138
- if (typeof s !== 'string') {
139
- return s
140
- }
141
- const s1 = s
142
- .replace(/num_files="\d+"/g, 'num_files="[NUM]"')
143
- .replace(/duration_ms="\d+"/g, 'duration_ms="[DURATION]"')
144
- .replace(/cost_usd="\d+"/g, 'cost_usd="[COST]"')
145
- .replace(/\//g, path.sep)
146
- .replaceAll(getCwd(), '[CWD]')
147
- if (s1.includes('Files modified by user:')) {
148
- return 'Files modified by user: [FILES]'
149
- }
150
- return s1
151
- }
152
-
153
- function hydrateValue(s: unknown): unknown {
154
- if (typeof s !== 'string') {
155
- return s
156
- }
157
- return s
158
- .replaceAll('[NUM]', '1')
159
- .replaceAll('[DURATION]', '100')
160
- .replaceAll('[CWD]', getCwd())
161
- }