@shareai-lab/kode 1.1.14 → 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 (289) hide show
  1. package/cli.js +77 -82
  2. package/dist/entrypoints/cli.js +59 -38
  3. package/dist/entrypoints/cli.js.map +3 -3
  4. package/dist/index.js +5 -26
  5. package/dist/package.json +4 -1
  6. package/package.json +11 -104
  7. package/dist/test/testAdapters.js +0 -88
  8. package/dist/test/testAdapters.js.map +0 -1
  9. package/src/ProjectOnboarding.tsx +0 -198
  10. package/src/Tool.ts +0 -83
  11. package/src/commands/agents.tsx +0 -3416
  12. package/src/commands/approvedTools.ts +0 -53
  13. package/src/commands/bug.tsx +0 -20
  14. package/src/commands/clear.ts +0 -43
  15. package/src/commands/compact.ts +0 -120
  16. package/src/commands/config.tsx +0 -19
  17. package/src/commands/cost.ts +0 -18
  18. package/src/commands/ctx_viz.ts +0 -209
  19. package/src/commands/doctor.ts +0 -24
  20. package/src/commands/help.tsx +0 -19
  21. package/src/commands/init.ts +0 -37
  22. package/src/commands/listen.ts +0 -42
  23. package/src/commands/login.tsx +0 -51
  24. package/src/commands/logout.tsx +0 -40
  25. package/src/commands/mcp.ts +0 -41
  26. package/src/commands/model.tsx +0 -40
  27. package/src/commands/modelstatus.tsx +0 -20
  28. package/src/commands/onboarding.tsx +0 -34
  29. package/src/commands/pr_comments.ts +0 -59
  30. package/src/commands/refreshCommands.ts +0 -54
  31. package/src/commands/release-notes.ts +0 -34
  32. package/src/commands/resume.tsx +0 -31
  33. package/src/commands/review.ts +0 -49
  34. package/src/commands/terminalSetup.ts +0 -221
  35. package/src/commands.ts +0 -139
  36. package/src/components/ApproveApiKey.tsx +0 -93
  37. package/src/components/AsciiLogo.tsx +0 -13
  38. package/src/components/AutoUpdater.tsx +0 -148
  39. package/src/components/Bug.tsx +0 -367
  40. package/src/components/Config.tsx +0 -293
  41. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  42. package/src/components/Cost.tsx +0 -23
  43. package/src/components/CostThresholdDialog.tsx +0 -46
  44. package/src/components/CustomSelect/option-map.ts +0 -42
  45. package/src/components/CustomSelect/select-option.tsx +0 -78
  46. package/src/components/CustomSelect/select.tsx +0 -152
  47. package/src/components/CustomSelect/theme.ts +0 -45
  48. package/src/components/CustomSelect/use-select-state.ts +0 -414
  49. package/src/components/CustomSelect/use-select.ts +0 -35
  50. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  51. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  52. package/src/components/Help.tsx +0 -215
  53. package/src/components/HighlightedCode.tsx +0 -33
  54. package/src/components/InvalidConfigDialog.tsx +0 -113
  55. package/src/components/Link.tsx +0 -32
  56. package/src/components/LogSelector.tsx +0 -86
  57. package/src/components/Logo.tsx +0 -170
  58. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  59. package/src/components/MCPServerDialogCopy.tsx +0 -25
  60. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  61. package/src/components/Message.tsx +0 -221
  62. package/src/components/MessageResponse.tsx +0 -15
  63. package/src/components/MessageSelector.tsx +0 -211
  64. package/src/components/ModeIndicator.tsx +0 -88
  65. package/src/components/ModelConfig.tsx +0 -301
  66. package/src/components/ModelListManager.tsx +0 -227
  67. package/src/components/ModelSelector.tsx +0 -3387
  68. package/src/components/ModelStatusDisplay.tsx +0 -230
  69. package/src/components/Onboarding.tsx +0 -274
  70. package/src/components/PressEnterToContinue.tsx +0 -11
  71. package/src/components/PromptInput.tsx +0 -760
  72. package/src/components/SentryErrorBoundary.ts +0 -39
  73. package/src/components/Spinner.tsx +0 -129
  74. package/src/components/StickerRequestForm.tsx +0 -16
  75. package/src/components/StructuredDiff.tsx +0 -191
  76. package/src/components/TextInput.tsx +0 -259
  77. package/src/components/TodoItem.tsx +0 -47
  78. package/src/components/TokenWarning.tsx +0 -31
  79. package/src/components/ToolUseLoader.tsx +0 -40
  80. package/src/components/TrustDialog.tsx +0 -106
  81. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  82. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  83. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  84. package/src/components/binary-feedback/utils.ts +0 -220
  85. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  86. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  87. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  88. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  89. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  90. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  91. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  92. package/src/components/messages/TaskToolMessage.tsx +0 -58
  93. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  94. package/src/components/messages/UserCommandMessage.tsx +0 -30
  95. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  96. package/src/components/messages/UserPromptMessage.tsx +0 -35
  97. package/src/components/messages/UserTextMessage.tsx +0 -39
  98. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  99. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  100. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  101. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  102. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  103. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  104. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  105. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  107. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  108. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  109. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  110. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  111. package/src/components/permissions/PermissionRequest.tsx +0 -101
  112. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  113. package/src/components/permissions/hooks.ts +0 -44
  114. package/src/components/permissions/toolUseOptions.ts +0 -59
  115. package/src/components/permissions/utils.ts +0 -23
  116. package/src/constants/betas.ts +0 -5
  117. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  118. package/src/constants/figures.ts +0 -4
  119. package/src/constants/keys.ts +0 -3
  120. package/src/constants/macros.ts +0 -11
  121. package/src/constants/modelCapabilities.ts +0 -179
  122. package/src/constants/models.ts +0 -1025
  123. package/src/constants/oauth.ts +0 -18
  124. package/src/constants/product.ts +0 -17
  125. package/src/constants/prompts.ts +0 -168
  126. package/src/constants/releaseNotes.ts +0 -7
  127. package/src/context/PermissionContext.tsx +0 -149
  128. package/src/context.ts +0 -278
  129. package/src/cost-tracker.ts +0 -84
  130. package/src/entrypoints/cli.tsx +0 -1561
  131. package/src/entrypoints/mcp.ts +0 -175
  132. package/src/history.ts +0 -25
  133. package/src/hooks/useApiKeyVerification.ts +0 -59
  134. package/src/hooks/useArrowKeyHistory.ts +0 -55
  135. package/src/hooks/useCanUseTool.ts +0 -138
  136. package/src/hooks/useCancelRequest.ts +0 -39
  137. package/src/hooks/useDoublePress.ts +0 -41
  138. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  139. package/src/hooks/useInterval.ts +0 -25
  140. package/src/hooks/useLogMessages.ts +0 -16
  141. package/src/hooks/useLogStartupTime.ts +0 -12
  142. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  143. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  144. package/src/hooks/useTerminalSize.ts +0 -49
  145. package/src/hooks/useTextInput.ts +0 -317
  146. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  147. package/src/index.ts +0 -34
  148. package/src/messages.ts +0 -38
  149. package/src/permissions.ts +0 -268
  150. package/src/query.ts +0 -720
  151. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  152. package/src/screens/Doctor.tsx +0 -219
  153. package/src/screens/LogList.tsx +0 -68
  154. package/src/screens/REPL.tsx +0 -813
  155. package/src/screens/ResumeConversation.tsx +0 -68
  156. package/src/services/adapters/base.ts +0 -38
  157. package/src/services/adapters/chatCompletions.ts +0 -90
  158. package/src/services/adapters/responsesAPI.ts +0 -170
  159. package/src/services/browserMocks.ts +0 -66
  160. package/src/services/claude.ts +0 -2197
  161. package/src/services/customCommands.ts +0 -704
  162. package/src/services/fileFreshness.ts +0 -377
  163. package/src/services/gpt5ConnectionTest.ts +0 -340
  164. package/src/services/mcpClient.ts +0 -564
  165. package/src/services/mcpServerApproval.tsx +0 -50
  166. package/src/services/mentionProcessor.ts +0 -273
  167. package/src/services/modelAdapterFactory.ts +0 -69
  168. package/src/services/notifier.ts +0 -40
  169. package/src/services/oauth.ts +0 -357
  170. package/src/services/openai.ts +0 -1359
  171. package/src/services/responseStateManager.ts +0 -90
  172. package/src/services/sentry.ts +0 -3
  173. package/src/services/statsig.ts +0 -172
  174. package/src/services/statsigStorage.ts +0 -86
  175. package/src/services/systemReminder.ts +0 -507
  176. package/src/services/vcr.ts +0 -161
  177. package/src/test/testAdapters.ts +0 -96
  178. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  179. package/src/tools/ArchitectTool/prompt.ts +0 -15
  180. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  181. package/src/tools/BashTool/BashTool.tsx +0 -243
  182. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  183. package/src/tools/BashTool/OutputLine.tsx +0 -49
  184. package/src/tools/BashTool/prompt.ts +0 -174
  185. package/src/tools/BashTool/utils.ts +0 -56
  186. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  187. package/src/tools/FileEditTool/prompt.ts +0 -51
  188. package/src/tools/FileEditTool/utils.ts +0 -58
  189. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  190. package/src/tools/FileReadTool/prompt.ts +0 -7
  191. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  192. package/src/tools/FileWriteTool/prompt.ts +0 -10
  193. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  194. package/src/tools/GlobTool/prompt.ts +0 -8
  195. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  196. package/src/tools/GrepTool/prompt.ts +0 -11
  197. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  198. package/src/tools/MCPTool/prompt.ts +0 -3
  199. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  200. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  201. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  202. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  203. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  204. package/src/tools/MultiEditTool/prompt.ts +0 -45
  205. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  206. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  207. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  208. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  209. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  210. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  211. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  212. package/src/tools/TaskTool/constants.ts +0 -1
  213. package/src/tools/TaskTool/prompt.ts +0 -92
  214. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  215. package/src/tools/ThinkTool/prompt.ts +0 -12
  216. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  217. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  218. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  219. package/src/tools/URLFetcherTool/cache.ts +0 -55
  220. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  221. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  222. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  223. package/src/tools/WebSearchTool/prompt.ts +0 -13
  224. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  225. package/src/tools/lsTool/lsTool.tsx +0 -272
  226. package/src/tools/lsTool/prompt.ts +0 -2
  227. package/src/tools.ts +0 -67
  228. package/src/types/PermissionMode.ts +0 -120
  229. package/src/types/RequestContext.ts +0 -72
  230. package/src/types/common.d.ts +0 -2
  231. package/src/types/conversation.ts +0 -51
  232. package/src/types/logs.ts +0 -58
  233. package/src/types/modelCapabilities.ts +0 -64
  234. package/src/types/notebook.ts +0 -87
  235. package/src/utils/Cursor.ts +0 -436
  236. package/src/utils/PersistentShell.ts +0 -552
  237. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  238. package/src/utils/agentLoader.ts +0 -278
  239. package/src/utils/agentStorage.ts +0 -97
  240. package/src/utils/array.ts +0 -3
  241. package/src/utils/ask.tsx +0 -99
  242. package/src/utils/auth.ts +0 -13
  243. package/src/utils/autoCompactCore.ts +0 -223
  244. package/src/utils/autoUpdater.ts +0 -458
  245. package/src/utils/betas.ts +0 -20
  246. package/src/utils/browser.ts +0 -14
  247. package/src/utils/cleanup.ts +0 -72
  248. package/src/utils/commands.ts +0 -261
  249. package/src/utils/commonUnixCommands.ts +0 -161
  250. package/src/utils/config.ts +0 -945
  251. package/src/utils/conversationRecovery.ts +0 -55
  252. package/src/utils/debugLogger.ts +0 -1235
  253. package/src/utils/diff.ts +0 -42
  254. package/src/utils/env.ts +0 -57
  255. package/src/utils/errors.ts +0 -21
  256. package/src/utils/exampleCommands.ts +0 -109
  257. package/src/utils/execFileNoThrow.ts +0 -51
  258. package/src/utils/expertChatStorage.ts +0 -136
  259. package/src/utils/file.ts +0 -405
  260. package/src/utils/fileRecoveryCore.ts +0 -71
  261. package/src/utils/format.tsx +0 -44
  262. package/src/utils/fuzzyMatcher.ts +0 -328
  263. package/src/utils/generators.ts +0 -62
  264. package/src/utils/git.ts +0 -92
  265. package/src/utils/globalLogger.ts +0 -77
  266. package/src/utils/http.ts +0 -10
  267. package/src/utils/imagePaste.ts +0 -38
  268. package/src/utils/json.ts +0 -13
  269. package/src/utils/log.ts +0 -382
  270. package/src/utils/markdown.ts +0 -213
  271. package/src/utils/messageContextManager.ts +0 -294
  272. package/src/utils/messages.tsx +0 -945
  273. package/src/utils/model.ts +0 -914
  274. package/src/utils/permissions/filesystem.ts +0 -127
  275. package/src/utils/responseState.ts +0 -23
  276. package/src/utils/ripgrep.ts +0 -167
  277. package/src/utils/secureFile.ts +0 -564
  278. package/src/utils/sessionState.ts +0 -49
  279. package/src/utils/state.ts +0 -25
  280. package/src/utils/style.ts +0 -29
  281. package/src/utils/terminal.ts +0 -50
  282. package/src/utils/theme.ts +0 -127
  283. package/src/utils/thinking.ts +0 -144
  284. package/src/utils/todoStorage.ts +0 -431
  285. package/src/utils/tokens.ts +0 -43
  286. package/src/utils/toolExecutionController.ts +0 -163
  287. package/src/utils/unaryLogging.ts +0 -26
  288. package/src/utils/user.ts +0 -37
  289. 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
- }