@within-7/minto 0.3.6 → 0.3.10

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 (238) hide show
  1. package/{cli.js → cli.cjs} +25 -23
  2. package/dist/commands/agents/AgentsCommand.js +459 -655
  3. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  4. package/dist/commands/agents/types.js +1 -0
  5. package/dist/commands/agents/types.js.map +2 -2
  6. package/dist/commands/agents/utils/fileOperations.js +96 -36
  7. package/dist/commands/agents/utils/fileOperations.js.map +3 -3
  8. package/dist/commands/agents/utils/index.js +3 -1
  9. package/dist/commands/agents/utils/index.js.map +2 -2
  10. package/dist/commands/context.js +54 -23
  11. package/dist/commands/context.js.map +2 -2
  12. package/dist/commands/export.js +673 -93
  13. package/dist/commands/export.js.map +2 -2
  14. package/dist/commands/language.js +110 -0
  15. package/dist/commands/language.js.map +7 -0
  16. package/dist/commands/mcp-interactive.js +419 -217
  17. package/dist/commands/mcp-interactive.js.map +2 -2
  18. package/dist/commands/model.js +415 -66
  19. package/dist/commands/model.js.map +2 -2
  20. package/dist/commands/new.js +56 -0
  21. package/dist/commands/new.js.map +7 -0
  22. package/dist/commands/permissions.js +75 -49
  23. package/dist/commands/permissions.js.map +2 -2
  24. package/dist/commands/plugin.js +882 -185
  25. package/dist/commands/plugin.js.map +3 -3
  26. package/dist/commands/resume.js +251 -16
  27. package/dist/commands/resume.js.map +2 -2
  28. package/dist/commands/sandbox.js +168 -70
  29. package/dist/commands/sandbox.js.map +2 -2
  30. package/dist/commands/sessions.js +224 -0
  31. package/dist/commands/sessions.js.map +7 -0
  32. package/dist/commands/setup.js +596 -109
  33. package/dist/commands/setup.js.map +2 -2
  34. package/dist/commands/stats.js +292 -0
  35. package/dist/commands/stats.js.map +7 -0
  36. package/dist/commands/status.js +75 -7
  37. package/dist/commands/status.js.map +2 -2
  38. package/dist/commands/undo.js +154 -180
  39. package/dist/commands/undo.js.map +2 -2
  40. package/dist/commands.js +6 -0
  41. package/dist/commands.js.map +2 -2
  42. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  43. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  44. package/dist/components/Config.js +9 -8
  45. package/dist/components/Config.js.map +2 -2
  46. package/dist/components/HeaderBar.js +2 -1
  47. package/dist/components/HeaderBar.js.map +2 -2
  48. package/dist/components/Help.js +166 -32
  49. package/dist/components/Help.js.map +2 -2
  50. package/dist/components/HotkeyHelpPanel.js +46 -44
  51. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  52. package/dist/components/InfoPanel/InfoPanel.js +123 -0
  53. package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
  54. package/dist/components/InfoPanel/index.js +5 -0
  55. package/dist/components/InfoPanel/index.js.map +7 -0
  56. package/dist/components/InfoPanel/types.js +1 -0
  57. package/dist/components/InfoPanel/types.js.map +7 -0
  58. package/dist/components/Logo.js +5 -2
  59. package/dist/components/Logo.js.map +2 -2
  60. package/dist/components/MCPServerApprovalDialog.js +6 -5
  61. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  62. package/dist/components/MCPServerMultiselectDialog.js +5 -4
  63. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  64. package/dist/components/MessageSelector.js +4 -3
  65. package/dist/components/MessageSelector.js.map +2 -2
  66. package/dist/components/ModelConfig.js +13 -12
  67. package/dist/components/ModelConfig.js.map +2 -2
  68. package/dist/components/ModelListManager.js +4 -3
  69. package/dist/components/ModelListManager.js.map +2 -2
  70. package/dist/components/ModelSelector/BrandTextInput.js +43 -0
  71. package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
  72. package/dist/components/ModelSelector/ModelSelector.js +419 -501
  73. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  74. package/dist/components/ModelSelector/WizardContainer.js +45 -0
  75. package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
  76. package/dist/components/ModelSelector/index.js +1 -3
  77. package/dist/components/ModelSelector/index.js.map +2 -2
  78. package/dist/components/PromptInput.js +77 -44
  79. package/dist/components/PromptInput.js.map +2 -2
  80. package/dist/components/SensitiveFileWarning.js +12 -8
  81. package/dist/components/SensitiveFileWarning.js.map +2 -2
  82. package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
  83. package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
  84. package/dist/components/SimpleSelector/index.js +5 -0
  85. package/dist/components/SimpleSelector/index.js.map +7 -0
  86. package/dist/components/SimpleSelector/types.js +1 -0
  87. package/dist/components/SimpleSelector/types.js.map +7 -0
  88. package/dist/components/StatusOverlayContent.js +21 -0
  89. package/dist/components/StatusOverlayContent.js.map +7 -0
  90. package/dist/components/TabbedListView/ScrollableList.js +117 -0
  91. package/dist/components/TabbedListView/ScrollableList.js.map +7 -0
  92. package/dist/components/TabbedListView/SearchInput.js +23 -0
  93. package/dist/components/TabbedListView/SearchInput.js.map +7 -0
  94. package/dist/components/TabbedListView/TabBar.js +20 -0
  95. package/dist/components/TabbedListView/TabBar.js.map +7 -0
  96. package/dist/components/TabbedListView/TabbedListView.js +246 -0
  97. package/dist/components/TabbedListView/TabbedListView.js.map +7 -0
  98. package/dist/components/TabbedListView/index.js +11 -0
  99. package/dist/components/TabbedListView/index.js.map +7 -0
  100. package/dist/components/TabbedListView/types.js +1 -0
  101. package/dist/components/TabbedListView/types.js.map +7 -0
  102. package/dist/components/TodoChangeBlock.js +6 -5
  103. package/dist/components/TodoChangeBlock.js.map +3 -3
  104. package/dist/components/TodoPanel.js +6 -3
  105. package/dist/components/TodoPanel.js.map +3 -3
  106. package/dist/components/TrustDialog.js +6 -5
  107. package/dist/components/TrustDialog.js.map +2 -2
  108. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js +2 -1
  109. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js.map +2 -2
  110. package/dist/constants/macros.js +1 -1
  111. package/dist/constants/macros.js.map +1 -1
  112. package/dist/constants/product.js +2 -2
  113. package/dist/constants/product.js.map +1 -1
  114. package/dist/constants/prompts.js +17 -0
  115. package/dist/constants/prompts.js.map +2 -2
  116. package/dist/constants/toolInputExamples.js +5 -1
  117. package/dist/constants/toolInputExamples.js.map +2 -2
  118. package/dist/core/backupHook.js +29 -0
  119. package/dist/core/backupHook.js.map +7 -0
  120. package/dist/core/config/defaults.js +8 -2
  121. package/dist/core/config/defaults.js.map +2 -2
  122. package/dist/core/config/schema.js +14 -2
  123. package/dist/core/config/schema.js.map +2 -2
  124. package/dist/core/costTracker.js +0 -16
  125. package/dist/core/costTracker.js.map +2 -2
  126. package/dist/core/tokenStatsManager.js +5 -0
  127. package/dist/core/tokenStatsManager.js.map +2 -2
  128. package/dist/cost-tracker.js +0 -16
  129. package/dist/cost-tracker.js.map +2 -2
  130. package/dist/entrypoints/bootstrap.js +56 -0
  131. package/dist/entrypoints/bootstrap.js.map +7 -0
  132. package/dist/entrypoints/cli.js +164 -23
  133. package/dist/entrypoints/cli.js.map +3 -3
  134. package/dist/history.js +75 -15
  135. package/dist/history.js.map +2 -2
  136. package/dist/i18n/index.js +2 -2
  137. package/dist/i18n/index.js.map +2 -2
  138. package/dist/i18n/locales/en.js +582 -1
  139. package/dist/i18n/locales/en.js.map +2 -2
  140. package/dist/i18n/locales/zh-CN.js +582 -1
  141. package/dist/i18n/locales/zh-CN.js.map +2 -2
  142. package/dist/i18n/types.js.map +1 -1
  143. package/dist/index.js +1 -1
  144. package/dist/index.js.map +2 -2
  145. package/dist/messages.js +11 -0
  146. package/dist/messages.js.map +2 -2
  147. package/dist/permissions.js.map +2 -2
  148. package/dist/query.js +9 -0
  149. package/dist/query.js.map +2 -2
  150. package/dist/screens/REPL.js +45 -7
  151. package/dist/screens/REPL.js.map +2 -2
  152. package/dist/services/customCommands.js +44 -16
  153. package/dist/services/customCommands.js.map +2 -2
  154. package/dist/services/plugins/lspServers.js +1 -1
  155. package/dist/services/plugins/lspServers.js.map +2 -2
  156. package/dist/services/plugins/pluginRuntime.js +2 -1
  157. package/dist/services/plugins/pluginRuntime.js.map +2 -2
  158. package/dist/services/plugins/pluginValidation.js +10 -3
  159. package/dist/services/plugins/pluginValidation.js.map +2 -2
  160. package/dist/services/plugins/skillMarketplace.js +16 -8
  161. package/dist/services/plugins/skillMarketplace.js.map +2 -2
  162. package/dist/services/systemReminder.js +17 -6
  163. package/dist/services/systemReminder.js.map +2 -2
  164. package/dist/tools/FileEditTool/FileEditTool.js +7 -0
  165. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  166. package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
  167. package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
  168. package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
  169. package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
  170. package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
  171. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  172. package/dist/tools/TaskTool/TaskTool.js +179 -1
  173. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  174. package/dist/tools/TodoWriteTool/prompt.js +21 -0
  175. package/dist/tools/TodoWriteTool/prompt.js.map +2 -2
  176. package/dist/tools/URLFetcherTool/prompt.js +14 -9
  177. package/dist/tools/URLFetcherTool/prompt.js.map +2 -2
  178. package/dist/tools/WebSearchTool/prompt.js +12 -6
  179. package/dist/tools/WebSearchTool/prompt.js.map +2 -2
  180. package/dist/types/PermissionMode.js +30 -1
  181. package/dist/types/PermissionMode.js.map +2 -2
  182. package/dist/types/plugin.js +2 -4
  183. package/dist/types/plugin.js.map +2 -2
  184. package/dist/utils/agentHookExecutor.js +103 -0
  185. package/dist/utils/agentHookExecutor.js.map +7 -0
  186. package/dist/utils/agentLoader.js +272 -32
  187. package/dist/utils/agentLoader.js.map +2 -2
  188. package/dist/utils/agentMemory.js +134 -0
  189. package/dist/utils/agentMemory.js.map +7 -0
  190. package/dist/utils/claudeCodeSync.js +439 -0
  191. package/dist/utils/claudeCodeSync.js.map +7 -0
  192. package/dist/utils/config.js +52 -24
  193. package/dist/utils/config.js.map +2 -2
  194. package/dist/utils/configPaths.js +199 -0
  195. package/dist/utils/configPaths.js.map +7 -0
  196. package/dist/utils/execFileNoThrow.js +2 -1
  197. package/dist/utils/execFileNoThrow.js.map +2 -2
  198. package/dist/utils/historyManager.js +234 -0
  199. package/dist/utils/historyManager.js.map +7 -0
  200. package/dist/utils/marketplaceManager.js +80 -43
  201. package/dist/utils/marketplaceManager.js.map +2 -2
  202. package/dist/utils/messages.js +13 -8
  203. package/dist/utils/messages.js.map +2 -2
  204. package/dist/utils/migration/index.js +37 -0
  205. package/dist/utils/migration/index.js.map +7 -0
  206. package/dist/utils/migration/migrateHistory.js +273 -0
  207. package/dist/utils/migration/migrateHistory.js.map +7 -0
  208. package/dist/utils/migration/migrateTodos.js +323 -0
  209. package/dist/utils/migration/migrateTodos.js.map +7 -0
  210. package/dist/utils/pasteCache.js +309 -0
  211. package/dist/utils/pasteCache.js.map +7 -0
  212. package/dist/utils/pluginInstaller.js +34 -24
  213. package/dist/utils/pluginInstaller.js.map +2 -2
  214. package/dist/utils/pluginLoader.js +54 -28
  215. package/dist/utils/pluginLoader.js.map +2 -2
  216. package/dist/utils/repoFetcher.js +110 -0
  217. package/dist/utils/repoFetcher.js.map +7 -0
  218. package/dist/utils/sessionIndex.js +192 -0
  219. package/dist/utils/sessionIndex.js.map +7 -0
  220. package/dist/utils/sessionTracker.js +170 -0
  221. package/dist/utils/sessionTracker.js.map +7 -0
  222. package/dist/utils/skillLoader.js +103 -5
  223. package/dist/utils/skillLoader.js.map +2 -2
  224. package/dist/utils/stats.js +417 -0
  225. package/dist/utils/stats.js.map +7 -0
  226. package/dist/utils/stringSubstitution.js +106 -0
  227. package/dist/utils/stringSubstitution.js.map +7 -0
  228. package/dist/utils/teamConfig.js +156 -14
  229. package/dist/utils/teamConfig.js.map +2 -2
  230. package/dist/utils/terminal.js +1 -1
  231. package/dist/utils/terminal.js.map +2 -2
  232. package/dist/utils/todoStorage.js +51 -19
  233. package/dist/utils/todoStorage.js.map +2 -2
  234. package/dist/utils/tooling/safeRender.js.map +2 -2
  235. package/dist/version.js +2 -2
  236. package/dist/version.js.map +1 -1
  237. package/package.json +71 -28
  238. package/scripts/{postinstall.js → postinstall.cjs} +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/MultiEditTool/MultiEditTool.tsx"],
4
- "sourcesContent": ["import { existsSync, mkdirSync, readFileSync, statSync } from 'fs'\nimport { Box, Text } from 'ink'\nimport { dirname, isAbsolute, relative, resolve, sep } from 'path'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { FileEditToolUpdatedMessage } from '@components/FileEditToolUpdatedMessage'\nimport { StructuredDiff } from '@components/StructuredDiff'\nimport { Tool, ValidationResult } from '@tool'\nimport { intersperse } from '@utils/array'\nimport {\n addLineNumbers,\n detectFileEncoding,\n detectLineEndings,\n findSimilarFile,\n writeTextContent,\n} from '@utils/file'\nimport { logError } from '@utils/log'\nimport { getCwd } from '@utils/state'\nimport { getTheme } from '@utils/theme'\nimport { NotebookEditTool } from '@tools/NotebookEditTool/NotebookEditTool'\n// Local content-based edit function for MultiEditTool\nfunction applyContentEdit(\n content: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n): { newContent: string; occurrences: number } {\n if (replaceAll) {\n const regex = new RegExp(\n oldString.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'),\n 'g',\n )\n const matches = content.match(regex)\n const occurrences = matches ? matches.length : 0\n const newContent = content.replace(regex, newString)\n return { newContent, occurrences }\n } else {\n if (content.includes(oldString)) {\n const newContent = content.replace(oldString, newString)\n return { newContent, occurrences: 1 }\n } else {\n throw new Error(`String not found: ${oldString.substring(0, 50)}...`)\n }\n }\n}\nimport { hasWritePermission } from '@utils/permissions/filesystem'\nimport { PROJECT_FILE } from '@constants/product'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport { emitReminderEvent } from '@services/systemReminder'\nimport { recordFileEdit } from '@services/fileFreshness'\nimport { getPatch } from '@utils/diff'\n\nconst EditSchema = z.object({\n old_string: z.string().describe('The text to replace'),\n new_string: z.string().describe('The text to replace it with'),\n replace_all: z\n .boolean()\n .optional()\n .default(false)\n .describe('Replace all occurences of old_string (default false)'),\n})\n\nconst inputSchema = z.strictObject({\n file_path: z.string().describe('The absolute path to the file to modify'),\n edits: z\n .array(EditSchema)\n .min(1)\n .describe('Array of edit operations to perform sequentially on the file'),\n})\n\nexport type In = typeof inputSchema\n\n// Number of lines of context to include before/after the change in our result message\nconst N_LINES_SNIPPET = 4\n\nexport const MultiEditTool = {\n name: 'MultiEdit',\n async description() {\n return 'A tool for making multiple edits to a single file atomically'\n },\n async prompt() {\n return PROMPT\n },\n inputSchema,\n userFacingName() {\n return 'Multi-Edit'\n },\n async isEnabled() {\n return true\n },\n isReadOnly() {\n return false\n },\n isConcurrencySafe() {\n return false // MultiEdit modifies files, not safe for concurrent execution\n },\n needsPermissions(input?: z.infer<typeof inputSchema>) {\n if (!input) return true\n return !hasWritePermission(input.file_path)\n },\n renderResultForAssistant(content) {\n return content\n },\n renderToolUseMessage(input, { verbose }) {\n const { file_path, edits } = input\n const workingDir = getCwd()\n const relativePath = isAbsolute(file_path)\n ? relative(workingDir, file_path)\n : file_path\n\n if (verbose) {\n const editSummary = edits\n .map(\n (edit, index) =>\n `${index + 1}. Replace \"${edit.old_string.substring(0, 50)}${edit.old_string.length > 50 ? '...' : ''}\" with \"${edit.new_string.substring(0, 50)}${edit.new_string.length > 50 ? '...' : ''}\"`,\n )\n .join('\\n')\n return `Multiple edits to ${relativePath}:\\n${editSummary}`\n }\n\n return `Making ${edits.length} edits to ${relativePath}`\n },\n renderToolUseRejectedMessage() {\n return (\n <Box>\n <Text color={getTheme().error}>\u26A0 Edit request rejected</Text>\n </Box>\n )\n },\n renderToolResultMessage(output, options?: { verbose?: boolean }) {\n const verbose = options?.verbose ?? false\n\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\">\n <Text color={getTheme().secondaryText}>Multi-edit completed</Text>\n </Box>\n )\n }\n\n if (typeof output === 'string') {\n const isError = output.includes('Error:')\n return (\n <Box flexDirection=\"column\">\n <Text color={isError ? getTheme().error : getTheme().success}>\n {output}\n </Text>\n </Box>\n )\n }\n\n return (\n <FileEditToolUpdatedMessage\n filePath={output.filePath}\n structuredPatch={output.structuredPatch}\n verbose={verbose}\n />\n )\n },\n async validateInput(\n { file_path, edits }: z.infer<typeof inputSchema>,\n context?: { readFileTimestamps?: Record<string, number> },\n ): Promise<ValidationResult> {\n const workingDir = getCwd()\n const normalizedPath = isAbsolute(file_path)\n ? resolve(file_path)\n : resolve(workingDir, file_path)\n\n // Check if it's a notebook file\n if (normalizedPath.endsWith('.ipynb')) {\n return {\n result: false,\n errorCode: 1,\n message: `For Jupyter notebooks (.ipynb files), use the ${NotebookEditTool.name} tool instead.`,\n }\n }\n\n // For new files, check parent directory exists\n if (!existsSync(normalizedPath)) {\n const parentDir = dirname(normalizedPath)\n if (!existsSync(parentDir)) {\n return {\n result: false,\n errorCode: 2,\n message: `Parent directory does not exist: ${parentDir}`,\n }\n }\n\n // For new files, ensure first edit creates the file (empty old_string)\n if (edits.length === 0 || edits[0].old_string !== '') {\n return {\n result: false,\n errorCode: 6,\n message:\n 'For new files, the first edit must have an empty old_string to create the file content.',\n }\n }\n } else {\n // For existing files, apply file protection mechanisms\n const readFileTimestamps = context?.readFileTimestamps || {}\n const readTimestamp = readFileTimestamps[normalizedPath]\n\n if (!readTimestamp) {\n return {\n result: false,\n errorCode: 7,\n message:\n 'File has not been read yet. Read it first before editing it.',\n meta: {\n filePath: normalizedPath,\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n // Check if file has been modified since last read\n const stats = statSync(normalizedPath)\n const lastWriteTime = stats.mtimeMs\n if (lastWriteTime > readTimestamp) {\n return {\n result: false,\n errorCode: 8,\n message:\n 'File has been modified since read, either by the user or by a linter. Read it again before attempting to edit it.',\n meta: {\n filePath: normalizedPath,\n lastWriteTime,\n readTimestamp,\n },\n }\n }\n\n // Pre-validate that all old_strings exist in the file\n const encoding = detectFileEncoding(normalizedPath)\n if (encoding === 'binary') {\n return {\n result: false,\n errorCode: 9,\n message: 'Cannot edit binary files.',\n }\n }\n\n const currentContent = readFileSync(normalizedPath, 'utf-8')\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i]\n if (\n edit.old_string !== '' &&\n !currentContent.includes(edit.old_string)\n ) {\n return {\n result: false,\n errorCode: 10,\n message: `Edit ${i + 1}: String to replace not found in file: \"${edit.old_string.substring(0, 100)}${edit.old_string.length > 100 ? '...' : ''}\"`,\n meta: {\n editIndex: i + 1,\n oldString: edit.old_string.substring(0, 200),\n },\n }\n }\n }\n }\n\n // Validate each edit\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i]\n if (edit.old_string === edit.new_string) {\n return {\n result: false,\n errorCode: 3,\n message: `Edit ${i + 1}: old_string and new_string cannot be the same`,\n }\n }\n }\n\n return { result: true }\n },\n async *call({ file_path, edits }, { readFileTimestamps }) {\n const startTime = Date.now()\n const workingDir = getCwd()\n const filePath = isAbsolute(file_path)\n ? resolve(file_path)\n : resolve(workingDir, file_path)\n\n try {\n // Read current file content (or empty for new files)\n let currentContent = ''\n let fileExists = existsSync(filePath)\n\n if (fileExists) {\n const encoding = detectFileEncoding(filePath)\n if (encoding === 'binary') {\n yield {\n type: 'result',\n data: 'Error: Cannot edit binary files',\n resultForAssistant: 'Error: Cannot edit binary files',\n }\n return\n }\n currentContent = readFileSync(filePath, 'utf-8')\n } else {\n // For new files, ensure parent directory exists\n const parentDir = dirname(filePath)\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true })\n }\n }\n\n // Apply all edits sequentially with progress updates\n let modifiedContent = currentContent\n const appliedEdits = []\n const totalEdits = edits.length\n\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i]\n const { old_string, new_string, replace_all } = edit\n\n // Yield progress update for multi-edit operations\n if (totalEdits > 1) {\n yield {\n type: 'progress',\n content: {\n type: 'streaming',\n toolName: 'Multi-Edit',\n stdout: `Applying edit ${i + 1}/${totalEdits}...`,\n stderr: '',\n isStreaming: true,\n },\n }\n }\n\n try {\n const result = applyContentEdit(\n modifiedContent,\n old_string,\n new_string,\n replace_all,\n )\n modifiedContent = result.newContent\n appliedEdits.push({\n editIndex: i + 1,\n success: true,\n old_string: old_string.substring(0, 100),\n new_string: new_string.substring(0, 100),\n occurrences: result.occurrences,\n })\n } catch (error) {\n // If any edit fails, abort the entire operation\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error'\n yield {\n type: 'result',\n data: `Error in edit ${i + 1}: ${errorMessage}`,\n resultForAssistant: `Error in edit ${i + 1}: ${errorMessage}`,\n }\n return\n }\n }\n\n // Write the modified content\n const lineEndings = fileExists ? detectLineEndings(currentContent) : 'LF'\n const encoding = fileExists ? detectFileEncoding(filePath) : 'utf8'\n writeTextContent(filePath, modifiedContent, encoding, lineEndings)\n\n // Record Agent edit operation for file freshness tracking\n recordFileEdit(filePath, modifiedContent)\n\n // Update readFileTimestamps to prevent stale file warnings\n readFileTimestamps[filePath] = Date.now()\n\n // Emit file edited event for system reminders\n emitReminderEvent('file:edited', {\n filePath,\n edits: edits.map(e => ({\n oldString: e.old_string,\n newString: e.new_string,\n })),\n originalContent: currentContent,\n newContent: modifiedContent,\n timestamp: Date.now(),\n operation: fileExists ? 'update' : 'create',\n })\n\n // Generate result data\n const relativePath = relative(workingDir, filePath)\n const summary = `Successfully applied ${edits.length} edits to ${relativePath}`\n\n const structuredPatch = getPatch({\n filePath: file_path,\n fileContents: currentContent,\n oldStr: currentContent,\n newStr: modifiedContent,\n })\n\n const resultData = {\n filePath: file_path,\n wasNewFile: !fileExists,\n editsApplied: appliedEdits,\n totalEdits: edits.length,\n summary,\n structuredPatch,\n }\n\n // Log the operation\n\n yield {\n type: 'result',\n data: resultData,\n resultForAssistant: summary,\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error occurred'\n const errorResult = `Error applying multi-edit: ${errorMessage}`\n\n logError(error)\n\n yield {\n type: 'result',\n data: errorResult,\n resultForAssistant: errorResult,\n }\n }\n },\n} satisfies Tool<typeof inputSchema, any>\n"],
5
- "mappings": "AAAA,SAAS,YAAY,WAAW,cAAc,gBAAgB;AAC9D,SAAS,KAAK,YAAY;AAC1B,SAAS,SAAS,YAAY,UAAU,eAAoB;AAC5D,YAAY,WAAW;AACvB,SAAS,SAAS;AAClB,SAAS,kCAAkC;AAI3C;AAAA,EAEE;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AAEjC,SAAS,iBACP,SACA,WACA,WACA,aAAsB,OACuB;AAC7C,MAAI,YAAY;AACd,UAAM,QAAQ,IAAI;AAAA,MAChB,UAAU,QAAQ,uBAAuB,MAAM;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,UAAU,QAAQ,MAAM,KAAK;AACnC,UAAM,cAAc,UAAU,QAAQ,SAAS;AAC/C,UAAM,aAAa,QAAQ,QAAQ,OAAO,SAAS;AACnD,WAAO,EAAE,YAAY,YAAY;AAAA,EACnC,OAAO;AACL,QAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,YAAM,aAAa,QAAQ,QAAQ,WAAW,SAAS;AACvD,aAAO,EAAE,YAAY,aAAa,EAAE;AAAA,IACtC,OAAO;AACL,YAAM,IAAI,MAAM,qBAAqB,UAAU,UAAU,GAAG,EAAE,CAAC,KAAK;AAAA,IACtE;AAAA,EACF;AACF;AACA,SAAS,0BAA0B;AAEnC,SAAsB,cAAc;AACpC,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB;AAEzB,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,EACrD,YAAY,EAAE,OAAO,EAAE,SAAS,6BAA6B;AAAA,EAC7D,aAAa,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,KAAK,EACb,SAAS,sDAAsD;AACpE,CAAC;AAED,MAAM,cAAc,EAAE,aAAa;AAAA,EACjC,WAAW,EAAE,OAAO,EAAE,SAAS,yCAAyC;AAAA,EACxE,OAAO,EACJ,MAAM,UAAU,EAChB,IAAI,CAAC,EACL,SAAS,8DAA8D;AAC5E,CAAC;AAKD,MAAM,kBAAkB;AAEjB,MAAM,gBAAgB;AAAA,EAC3B,MAAM;AAAA,EACN,MAAM,cAAc;AAClB,WAAO;AAAA,EACT;AAAA,EACA,MAAM,SAAS;AACb,WAAO;AAAA,EACT;AAAA,EACA;AAAA,EACA,iBAAiB;AACf,WAAO;AAAA,EACT;AAAA,EACA,MAAM,YAAY;AAChB,WAAO;AAAA,EACT;AAAA,EACA,aAAa;AACX,WAAO;AAAA,EACT;AAAA,EACA,oBAAoB;AAClB,WAAO;AAAA,EACT;AAAA,EACA,iBAAiB,OAAqC;AACpD,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,CAAC,mBAAmB,MAAM,SAAS;AAAA,EAC5C;AAAA,EACA,yBAAyB,SAAS;AAChC,WAAO;AAAA,EACT;AAAA,EACA,qBAAqB,OAAO,EAAE,QAAQ,GAAG;AACvC,UAAM,EAAE,WAAW,MAAM,IAAI;AAC7B,UAAM,aAAa,OAAO;AAC1B,UAAM,eAAe,WAAW,SAAS,IACrC,SAAS,YAAY,SAAS,IAC9B;AAEJ,QAAI,SAAS;AACX,YAAM,cAAc,MACjB;AAAA,QACC,CAAC,MAAM,UACL,GAAG,QAAQ,CAAC,cAAc,KAAK,WAAW,UAAU,GAAG,EAAE,CAAC,GAAG,KAAK,WAAW,SAAS,KAAK,QAAQ,EAAE,WAAW,KAAK,WAAW,UAAU,GAAG,EAAE,CAAC,GAAG,KAAK,WAAW,SAAS,KAAK,QAAQ,EAAE;AAAA,MAC/L,EACC,KAAK,IAAI;AACZ,aAAO,qBAAqB,YAAY;AAAA,EAAM,WAAW;AAAA,IAC3D;AAEA,WAAO,UAAU,MAAM,MAAM,aAAa,YAAY;AAAA,EACxD;AAAA,EACA,+BAA+B;AAC7B,WACE,oCAAC,WACC,oCAAC,QAAK,OAAO,SAAS,EAAE,SAAO,8BAAuB,CACxD;AAAA,EAEJ;AAAA,EACA,wBAAwB,QAAQ,SAAiC;AAC/D,UAAM,UAAU,SAAS,WAAW;AAGpC,QAAI,CAAC,QAAQ;AACX,aACE,oCAAC,OAAI,eAAc,YACjB,oCAAC,QAAK,OAAO,SAAS,EAAE,iBAAe,sBAAoB,CAC7D;AAAA,IAEJ;AAEA,QAAI,OAAO,WAAW,UAAU;AAC9B,YAAM,UAAU,OAAO,SAAS,QAAQ;AACxC,aACE,oCAAC,OAAI,eAAc,YACjB,oCAAC,QAAK,OAAO,UAAU,SAAS,EAAE,QAAQ,SAAS,EAAE,WAClD,MACH,CACF;AAAA,IAEJ;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,UAAU,OAAO;AAAA,QACjB,iBAAiB,OAAO;AAAA,QACxB;AAAA;AAAA,IACF;AAAA,EAEJ;AAAA,EACA,MAAM,cACJ,EAAE,WAAW,MAAM,GACnB,SAC2B;AAC3B,UAAM,aAAa,OAAO;AAC1B,UAAM,iBAAiB,WAAW,SAAS,IACvC,QAAQ,SAAS,IACjB,QAAQ,YAAY,SAAS;AAGjC,QAAI,eAAe,SAAS,QAAQ,GAAG;AACrC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,SAAS,iDAAiD,iBAAiB,IAAI;AAAA,MACjF;AAAA,IACF;AAGA,QAAI,CAAC,WAAW,cAAc,GAAG;AAC/B,YAAM,YAAY,QAAQ,cAAc;AACxC,UAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS,oCAAoC,SAAS;AAAA,QACxD;AAAA,MACF;AAGA,UAAI,MAAM,WAAW,KAAK,MAAM,CAAC,EAAE,eAAe,IAAI;AACpD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SACE;AAAA,QACJ;AAAA,MACF;AAAA,IACF,OAAO;AAEL,YAAM,qBAAqB,SAAS,sBAAsB,CAAC;AAC3D,YAAM,gBAAgB,mBAAmB,cAAc;AAEvD,UAAI,CAAC,eAAe;AAClB,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SACE;AAAA,UACF,MAAM;AAAA,YACJ,UAAU;AAAA,YACV,oBAAoB,OAAO,WAAW,SAAS,CAAC;AAAA,UAClD;AAAA,QACF;AAAA,MACF;AAGA,YAAM,QAAQ,SAAS,cAAc;AACrC,YAAM,gBAAgB,MAAM;AAC5B,UAAI,gBAAgB,eAAe;AACjC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SACE;AAAA,UACF,MAAM;AAAA,YACJ,UAAU;AAAA,YACV;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,YAAM,WAAW,mBAAmB,cAAc;AAClD,UAAI,aAAa,UAAU;AACzB,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,iBAAiB,aAAa,gBAAgB,OAAO;AAC3D,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,OAAO,MAAM,CAAC;AACpB,YACE,KAAK,eAAe,MACpB,CAAC,eAAe,SAAS,KAAK,UAAU,GACxC;AACA,iBAAO;AAAA,YACL,QAAQ;AAAA,YACR,WAAW;AAAA,YACX,SAAS,QAAQ,IAAI,CAAC,2CAA2C,KAAK,WAAW,UAAU,GAAG,GAAG,CAAC,GAAG,KAAK,WAAW,SAAS,MAAM,QAAQ,EAAE;AAAA,YAC9I,MAAM;AAAA,cACJ,WAAW,IAAI;AAAA,cACf,WAAW,KAAK,WAAW,UAAU,GAAG,GAAG;AAAA,YAC7C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,OAAO,MAAM,CAAC;AACpB,UAAI,KAAK,eAAe,KAAK,YAAY;AACvC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,SAAS,QAAQ,IAAI,CAAC;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAAA,EACA,OAAO,KAAK,EAAE,WAAW,MAAM,GAAG,EAAE,mBAAmB,GAAG;AACxD,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,aAAa,OAAO;AAC1B,UAAM,WAAW,WAAW,SAAS,IACjC,QAAQ,SAAS,IACjB,QAAQ,YAAY,SAAS;AAEjC,QAAI;AAEF,UAAI,iBAAiB;AACrB,UAAI,aAAa,WAAW,QAAQ;AAEpC,UAAI,YAAY;AACd,cAAMA,YAAW,mBAAmB,QAAQ;AAC5C,YAAIA,cAAa,UAAU;AACzB,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,MAAM;AAAA,YACN,oBAAoB;AAAA,UACtB;AACA;AAAA,QACF;AACA,yBAAiB,aAAa,UAAU,OAAO;AAAA,MACjD,OAAO;AAEL,cAAM,YAAY,QAAQ,QAAQ;AAClC,YAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,oBAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,QAC1C;AAAA,MACF;AAGA,UAAI,kBAAkB;AACtB,YAAM,eAAe,CAAC;AACtB,YAAM,aAAa,MAAM;AAEzB,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,OAAO,MAAM,CAAC;AACpB,cAAM,EAAE,YAAY,YAAY,YAAY,IAAI;AAGhD,YAAI,aAAa,GAAG;AAClB,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,SAAS;AAAA,cACP,MAAM;AAAA,cACN,UAAU;AAAA,cACV,QAAQ,iBAAiB,IAAI,CAAC,IAAI,UAAU;AAAA,cAC5C,QAAQ;AAAA,cACR,aAAa;AAAA,YACf;AAAA,UACF;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,SAAS;AAAA,YACb;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,4BAAkB,OAAO;AACzB,uBAAa,KAAK;AAAA,YAChB,WAAW,IAAI;AAAA,YACf,SAAS;AAAA,YACT,YAAY,WAAW,UAAU,GAAG,GAAG;AAAA,YACvC,YAAY,WAAW,UAAU,GAAG,GAAG;AAAA,YACvC,aAAa,OAAO;AAAA,UACtB,CAAC;AAAA,QACH,SAAS,OAAO;AAEd,gBAAM,eACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,MAAM,iBAAiB,IAAI,CAAC,KAAK,YAAY;AAAA,YAC7C,oBAAoB,iBAAiB,IAAI,CAAC,KAAK,YAAY;AAAA,UAC7D;AACA;AAAA,QACF;AAAA,MACF;AAGA,YAAM,cAAc,aAAa,kBAAkB,cAAc,IAAI;AACrE,YAAM,WAAW,aAAa,mBAAmB,QAAQ,IAAI;AAC7D,uBAAiB,UAAU,iBAAiB,UAAU,WAAW;AAGjE,qBAAe,UAAU,eAAe;AAGxC,yBAAmB,QAAQ,IAAI,KAAK,IAAI;AAGxC,wBAAkB,eAAe;AAAA,QAC/B;AAAA,QACA,OAAO,MAAM,IAAI,QAAM;AAAA,UACrB,WAAW,EAAE;AAAA,UACb,WAAW,EAAE;AAAA,QACf,EAAE;AAAA,QACF,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,WAAW,KAAK,IAAI;AAAA,QACpB,WAAW,aAAa,WAAW;AAAA,MACrC,CAAC;AAGD,YAAM,eAAe,SAAS,YAAY,QAAQ;AAClD,YAAM,UAAU,wBAAwB,MAAM,MAAM,aAAa,YAAY;AAE7E,YAAM,kBAAkB,SAAS;AAAA,QAC/B,UAAU;AAAA,QACV,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AAED,YAAM,aAAa;AAAA,QACjB,UAAU;AAAA,QACV,YAAY,CAAC;AAAA,QACb,cAAc;AAAA,QACd,YAAY,MAAM;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAIA,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,oBAAoB;AAAA,MACtB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,eACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,YAAM,cAAc,8BAA8B,YAAY;AAE9D,eAAS,KAAK;AAEd,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,oBAAoB;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { existsSync, mkdirSync, readFileSync, statSync } from 'fs'\nimport { Box, Text } from 'ink'\nimport { dirname, isAbsolute, relative, resolve, sep } from 'path'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { FileEditToolUpdatedMessage } from '@components/FileEditToolUpdatedMessage'\nimport { StructuredDiff } from '@components/StructuredDiff'\nimport { Tool, ValidationResult } from '@tool'\nimport { intersperse } from '@utils/array'\nimport {\n addLineNumbers,\n detectFileEncoding,\n detectLineEndings,\n findSimilarFile,\n writeTextContent,\n} from '@utils/file'\nimport { logError } from '@utils/log'\nimport { getCwd } from '@utils/state'\nimport { getTheme } from '@utils/theme'\nimport { NotebookEditTool } from '@tools/NotebookEditTool/NotebookEditTool'\n// Local content-based edit function for MultiEditTool\nfunction applyContentEdit(\n content: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n): { newContent: string; occurrences: number } {\n if (replaceAll) {\n const regex = new RegExp(\n oldString.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'),\n 'g',\n )\n const matches = content.match(regex)\n const occurrences = matches ? matches.length : 0\n const newContent = content.replace(regex, newString)\n return { newContent, occurrences }\n } else {\n if (content.includes(oldString)) {\n const newContent = content.replace(oldString, newString)\n return { newContent, occurrences: 1 }\n } else {\n throw new Error(`String not found: ${oldString.substring(0, 50)}...`)\n }\n }\n}\nimport { hasWritePermission } from '@utils/permissions/filesystem'\nimport { PROJECT_FILE } from '@constants/product'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport { emitReminderEvent } from '@services/systemReminder'\nimport { recordFileEdit } from '@services/fileFreshness'\nimport { getPatch } from '@utils/diff'\nimport { triggerBackup } from '@core/backupHook'\n\nconst EditSchema = z.object({\n old_string: z.string().describe('The text to replace'),\n new_string: z.string().describe('The text to replace it with'),\n replace_all: z\n .boolean()\n .optional()\n .default(false)\n .describe('Replace all occurences of old_string (default false)'),\n})\n\nconst inputSchema = z.strictObject({\n file_path: z.string().describe('The absolute path to the file to modify'),\n edits: z\n .array(EditSchema)\n .min(1)\n .describe('Array of edit operations to perform sequentially on the file'),\n})\n\nexport type In = typeof inputSchema\n\n// Number of lines of context to include before/after the change in our result message\nconst N_LINES_SNIPPET = 4\n\nexport const MultiEditTool = {\n name: 'MultiEdit',\n async description() {\n return 'A tool for making multiple edits to a single file atomically'\n },\n async prompt() {\n return PROMPT\n },\n inputSchema,\n userFacingName() {\n return 'Multi-Edit'\n },\n async isEnabled() {\n return true\n },\n isReadOnly() {\n return false\n },\n isConcurrencySafe() {\n return false // MultiEdit modifies files, not safe for concurrent execution\n },\n needsPermissions(input?: z.infer<typeof inputSchema>) {\n if (!input) return true\n return !hasWritePermission(input.file_path)\n },\n renderResultForAssistant(content) {\n return content\n },\n renderToolUseMessage(input, { verbose }) {\n const { file_path, edits } = input\n const workingDir = getCwd()\n const relativePath = isAbsolute(file_path)\n ? relative(workingDir, file_path)\n : file_path\n\n if (verbose) {\n const editSummary = edits\n .map(\n (edit, index) =>\n `${index + 1}. Replace \"${edit.old_string.substring(0, 50)}${edit.old_string.length > 50 ? '...' : ''}\" with \"${edit.new_string.substring(0, 50)}${edit.new_string.length > 50 ? '...' : ''}\"`,\n )\n .join('\\n')\n return `Multiple edits to ${relativePath}:\\n${editSummary}`\n }\n\n return `Making ${edits.length} edits to ${relativePath}`\n },\n renderToolUseRejectedMessage() {\n return (\n <Box>\n <Text color={getTheme().error}>\u26A0 Edit request rejected</Text>\n </Box>\n )\n },\n renderToolResultMessage(output, options?: { verbose?: boolean }) {\n const verbose = options?.verbose ?? false\n\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\">\n <Text color={getTheme().secondaryText}>Multi-edit completed</Text>\n </Box>\n )\n }\n\n if (typeof output === 'string') {\n const isError = output.includes('Error:')\n return (\n <Box flexDirection=\"column\">\n <Text color={isError ? getTheme().error : getTheme().success}>\n {output}\n </Text>\n </Box>\n )\n }\n\n return (\n <FileEditToolUpdatedMessage\n filePath={output.filePath}\n structuredPatch={output.structuredPatch}\n verbose={verbose}\n />\n )\n },\n async validateInput(\n { file_path, edits }: z.infer<typeof inputSchema>,\n context?: { readFileTimestamps?: Record<string, number> },\n ): Promise<ValidationResult> {\n const workingDir = getCwd()\n const normalizedPath = isAbsolute(file_path)\n ? resolve(file_path)\n : resolve(workingDir, file_path)\n\n // Check if it's a notebook file\n if (normalizedPath.endsWith('.ipynb')) {\n return {\n result: false,\n errorCode: 1,\n message: `For Jupyter notebooks (.ipynb files), use the ${NotebookEditTool.name} tool instead.`,\n }\n }\n\n // For new files, check parent directory exists\n if (!existsSync(normalizedPath)) {\n const parentDir = dirname(normalizedPath)\n if (!existsSync(parentDir)) {\n return {\n result: false,\n errorCode: 2,\n message: `Parent directory does not exist: ${parentDir}`,\n }\n }\n\n // For new files, ensure first edit creates the file (empty old_string)\n if (edits.length === 0 || edits[0].old_string !== '') {\n return {\n result: false,\n errorCode: 6,\n message:\n 'For new files, the first edit must have an empty old_string to create the file content.',\n }\n }\n } else {\n // For existing files, apply file protection mechanisms\n const readFileTimestamps = context?.readFileTimestamps || {}\n const readTimestamp = readFileTimestamps[normalizedPath]\n\n if (!readTimestamp) {\n return {\n result: false,\n errorCode: 7,\n message:\n 'File has not been read yet. Read it first before editing it.',\n meta: {\n filePath: normalizedPath,\n isFilePathAbsolute: String(isAbsolute(file_path)),\n },\n }\n }\n\n // Check if file has been modified since last read\n const stats = statSync(normalizedPath)\n const lastWriteTime = stats.mtimeMs\n if (lastWriteTime > readTimestamp) {\n return {\n result: false,\n errorCode: 8,\n message:\n 'File has been modified since read, either by the user or by a linter. Read it again before attempting to edit it.',\n meta: {\n filePath: normalizedPath,\n lastWriteTime,\n readTimestamp,\n },\n }\n }\n\n // Pre-validate that all old_strings exist in the file\n const encoding = detectFileEncoding(normalizedPath)\n if (encoding === 'binary') {\n return {\n result: false,\n errorCode: 9,\n message: 'Cannot edit binary files.',\n }\n }\n\n const currentContent = readFileSync(normalizedPath, 'utf-8')\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i]\n if (\n edit.old_string !== '' &&\n !currentContent.includes(edit.old_string)\n ) {\n return {\n result: false,\n errorCode: 10,\n message: `Edit ${i + 1}: String to replace not found in file: \"${edit.old_string.substring(0, 100)}${edit.old_string.length > 100 ? '...' : ''}\"`,\n meta: {\n editIndex: i + 1,\n oldString: edit.old_string.substring(0, 200),\n },\n }\n }\n }\n }\n\n // Validate each edit\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i]\n if (edit.old_string === edit.new_string) {\n return {\n result: false,\n errorCode: 3,\n message: `Edit ${i + 1}: old_string and new_string cannot be the same`,\n }\n }\n }\n\n return { result: true }\n },\n async *call({ file_path, edits }, { readFileTimestamps }) {\n const startTime = Date.now()\n const workingDir = getCwd()\n const filePath = isAbsolute(file_path)\n ? resolve(file_path)\n : resolve(workingDir, file_path)\n\n try {\n // Read current file content (or empty for new files)\n let currentContent = ''\n let fileExists = existsSync(filePath)\n\n if (fileExists) {\n const encoding = detectFileEncoding(filePath)\n if (encoding === 'binary') {\n yield {\n type: 'result',\n data: 'Error: Cannot edit binary files',\n resultForAssistant: 'Error: Cannot edit binary files',\n }\n return\n }\n currentContent = readFileSync(filePath, 'utf-8')\n } else {\n // For new files, ensure parent directory exists\n const parentDir = dirname(filePath)\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true })\n }\n }\n\n // Apply all edits sequentially with progress updates\n let modifiedContent = currentContent\n const appliedEdits = []\n const totalEdits = edits.length\n\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i]\n const { old_string, new_string, replace_all } = edit\n\n // Yield progress update for multi-edit operations\n if (totalEdits > 1) {\n yield {\n type: 'progress',\n content: {\n type: 'streaming',\n toolName: 'Multi-Edit',\n stdout: `Applying edit ${i + 1}/${totalEdits}...`,\n stderr: '',\n isStreaming: true,\n },\n }\n }\n\n try {\n const result = applyContentEdit(\n modifiedContent,\n old_string,\n new_string,\n replace_all,\n )\n modifiedContent = result.newContent\n appliedEdits.push({\n editIndex: i + 1,\n success: true,\n old_string: old_string.substring(0, 100),\n new_string: new_string.substring(0, 100),\n occurrences: result.occurrences,\n })\n } catch (error) {\n // If any edit fails, abort the entire operation\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error'\n yield {\n type: 'result',\n data: `Error in edit ${i + 1}: ${errorMessage}`,\n resultForAssistant: `Error in edit ${i + 1}: ${errorMessage}`,\n }\n return\n }\n }\n\n // Write the modified content\n const lineEndings = fileExists ? detectLineEndings(currentContent) : 'LF'\n const encoding = fileExists ? detectFileEncoding(filePath) : 'utf8'\n writeTextContent(filePath, modifiedContent, encoding, lineEndings)\n\n // Record Agent edit operation for file freshness tracking\n recordFileEdit(filePath, modifiedContent)\n triggerBackup(\n filePath,\n currentContent || null,\n modifiedContent,\n fileExists ? 'update' : 'create',\n )\n\n // Update readFileTimestamps to prevent stale file warnings\n readFileTimestamps[filePath] = Date.now()\n\n // Emit file edited event for system reminders\n emitReminderEvent('file:edited', {\n filePath,\n edits: edits.map(e => ({\n oldString: e.old_string,\n newString: e.new_string,\n })),\n originalContent: currentContent,\n newContent: modifiedContent,\n timestamp: Date.now(),\n operation: fileExists ? 'update' : 'create',\n })\n\n // Generate result data\n const relativePath = relative(workingDir, filePath)\n const summary = `Successfully applied ${edits.length} edits to ${relativePath}`\n\n const structuredPatch = getPatch({\n filePath: file_path,\n fileContents: currentContent,\n oldStr: currentContent,\n newStr: modifiedContent,\n })\n\n const resultData = {\n filePath: file_path,\n wasNewFile: !fileExists,\n editsApplied: appliedEdits,\n totalEdits: edits.length,\n summary,\n structuredPatch,\n }\n\n // Log the operation\n\n yield {\n type: 'result',\n data: resultData,\n resultForAssistant: summary,\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error occurred'\n const errorResult = `Error applying multi-edit: ${errorMessage}`\n\n logError(error)\n\n yield {\n type: 'result',\n data: errorResult,\n resultForAssistant: errorResult,\n }\n }\n },\n} satisfies Tool<typeof inputSchema, any>\n"],
5
+ "mappings": "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",
6
6
  "names": ["encoding"]
7
7
  }
@@ -16,6 +16,7 @@ import { DESCRIPTION, PROMPT } from "./prompt.js";
16
16
  import { hasWritePermission } from "../../utils/permissions/filesystem.js";
17
17
  import { emitReminderEvent } from "../../services/systemReminder.js";
18
18
  import { recordFileEdit } from "../../services/fileFreshness.js";
19
+ import { triggerBackup } from "../../core/backupHook.js";
19
20
  import { FileEditTool } from "../FileEditTool/FileEditTool.js";
20
21
  const inputSchema = z.strictObject({
21
22
  notebook_path: z.string().describe(
@@ -194,6 +195,7 @@ const NotebookEditTool = {
194
195
  const updatedNotebook = JSON.stringify(notebook, null, 1);
195
196
  writeTextContent(fullPath, updatedNotebook, enc, endings);
196
197
  recordFileEdit(fullPath, updatedNotebook);
198
+ triggerBackup(fullPath, content, updatedNotebook, "update");
197
199
  emitReminderEvent("file:edited", {
198
200
  filePath: fullPath,
199
201
  cellId: cell_id,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/NotebookEditTool/NotebookEditTool.tsx"],
4
- "sourcesContent": ["import { existsSync, readFileSync } from 'fs'\nimport { Box, Text } from 'ink'\nimport { extname, isAbsolute, relative, resolve } from 'path'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { HighlightedCode } from '@components/HighlightedCode'\nimport type { Tool } from '@tool'\nimport { NotebookCellType, NotebookContent } from '@minto-types/notebook'\nimport {\n detectFileEncoding,\n detectLineEndings,\n writeTextContent,\n} from '@utils/file'\nimport { safeParseJSON } from '@utils/json'\nimport { getCwd } from '@utils/state'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport { hasWritePermission } from '@utils/permissions/filesystem'\nimport { emitReminderEvent } from '@services/systemReminder'\nimport { recordFileEdit } from '@services/fileFreshness'\nimport { FileEditTool } from '@tools/FileEditTool/FileEditTool'\n\nconst inputSchema = z.strictObject({\n notebook_path: z\n .string()\n .describe(\n 'The absolute path to the Jupyter notebook file to edit (must be absolute, not relative)',\n ),\n cell_id: z\n .string()\n .optional()\n .describe(\n 'The ID of the cell to edit. When inserting a new cell, the new cell will be inserted after the cell with this ID, or at the beginning if not specified.',\n ),\n new_source: z.string().describe('The new source for the cell'),\n cell_type: z\n .enum(['code', 'markdown'])\n .optional()\n .describe(\n 'The type of the cell (code or markdown). If not specified, it defaults to the current cell type. If using edit_mode=insert, this is required.',\n ),\n edit_mode: z\n .enum(['replace', 'insert', 'delete'])\n .optional()\n .describe(\n 'The type of edit to make (replace, insert, delete). Defaults to replace.',\n ),\n})\n\nexport const NotebookEditTool = {\n name: 'NotebookEdit',\n async description() {\n return DESCRIPTION\n },\n async prompt() {\n return PROMPT\n },\n inputSchema,\n userFacingName() {\n return 'Edit Notebook'\n },\n async isEnabled() {\n return true\n },\n isReadOnly() {\n return false\n },\n isConcurrencySafe() {\n return false // NotebookEditTool modifies state/files, not safe for concurrent execution\n },\n needsPermissions({ notebook_path }) {\n return !hasWritePermission(notebook_path)\n },\n renderResultForAssistant({ cell_id, edit_mode, new_source, error }) {\n if (error) {\n return error\n }\n switch (edit_mode) {\n case 'replace':\n return `Updated cell ${cell_id} with ${new_source}`\n case 'insert':\n return `Inserted cell after ${cell_id || 'beginning'} with ${new_source}`\n case 'delete':\n return `Deleted cell ${cell_id}`\n }\n },\n renderToolUseMessage(input, { verbose }) {\n return `notebook_path: ${verbose ? input.notebook_path : relative(getCwd(), input.notebook_path)}, cell_id: ${input.cell_id || '(beginning)'}, content: ${input.new_source.slice(0, 30)}\u2026, cell_type: ${input.cell_type}, edit_mode: ${input.edit_mode ?? 'replace'}`\n },\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n renderToolResultMessage(output) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\">\n <Text>Notebook cell updated</Text>\n </Box>\n )\n }\n\n const { cell_id, new_source, language, error } = output\n\n if (error) {\n return (\n <Box flexDirection=\"column\">\n <Text color=\"red\">{error}</Text>\n </Box>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n <Text>Updated cell {cell_id}:</Text>\n <Box marginLeft={2}>\n <HighlightedCode code={new_source} language={language} />\n </Box>\n </Box>\n )\n },\n async validateInput({\n notebook_path,\n cell_id,\n cell_type,\n edit_mode = 'replace',\n }) {\n const fullPath = isAbsolute(notebook_path)\n ? notebook_path\n : resolve(getCwd(), notebook_path)\n\n if (!existsSync(fullPath)) {\n return {\n result: false,\n message: 'Notebook file does not exist.',\n }\n }\n\n if (extname(fullPath) !== '.ipynb') {\n return {\n result: false,\n message: `File must be a Jupyter notebook (.ipynb file). For editing other file types, use the ${FileEditTool.name} tool.`,\n }\n }\n\n if (edit_mode === 'insert' && !cell_type) {\n return {\n result: false,\n message: 'Cell type is required when using edit_mode=insert.',\n }\n }\n\n const enc = detectFileEncoding(fullPath)\n const content = readFileSync(fullPath, enc)\n const notebook = safeParseJSON(content) as NotebookContent | null\n if (!notebook) {\n return {\n result: false,\n message: 'Notebook is not valid JSON.',\n }\n }\n\n // For replace and delete, cell_id is required and must exist\n if ((edit_mode === 'replace' || edit_mode === 'delete') && !cell_id) {\n return {\n result: false,\n message: 'cell_id is required for replace and delete operations.',\n }\n }\n\n // Validate cell_id exists in notebook (if provided)\n if (cell_id) {\n const cellIndex = notebook.cells.findIndex(\n (cell: { id?: string }, index: number) =>\n cell.id === cell_id || String(index) === cell_id,\n )\n if (cellIndex === -1 && edit_mode !== 'insert') {\n return {\n result: false,\n message: `Cell with ID '${cell_id}' not found in notebook. Available cells: ${notebook.cells.length}`,\n }\n }\n }\n\n return { result: true }\n },\n async *call({ notebook_path, cell_id, new_source, cell_type, edit_mode }) {\n const fullPath = isAbsolute(notebook_path)\n ? notebook_path\n : resolve(getCwd(), notebook_path)\n\n try {\n const enc = detectFileEncoding(fullPath)\n const content = readFileSync(fullPath, enc)\n const notebook = JSON.parse(content) as NotebookContent\n const language = notebook.metadata.language_info?.name ?? 'python'\n\n // Find cell index by ID (or treat cell_id as index string for backward compatibility)\n const findCellIndex = (id: string | undefined): number => {\n if (!id) return 0 // Insert at beginning if no cell_id\n const byId = notebook.cells.findIndex(\n (cell: { id?: string }) => cell.id === id,\n )\n if (byId !== -1) return byId\n // Fallback: treat as numeric index for backward compatibility\n const numIndex = parseInt(id, 10)\n if (\n !isNaN(numIndex) &&\n numIndex >= 0 &&\n numIndex < notebook.cells.length\n ) {\n return numIndex\n }\n return -1\n }\n\n const cellIndex = findCellIndex(cell_id)\n\n if (edit_mode === 'delete') {\n // Delete the specified cell\n if (cellIndex === -1) {\n throw new Error(`Cell with ID '${cell_id}' not found`)\n }\n notebook.cells.splice(cellIndex, 1)\n } else if (edit_mode === 'insert') {\n // Insert the new cell after the specified cell (or at beginning if no cell_id)\n const insertIndex = cell_id ? cellIndex + 1 : 0\n const newCellId = `cell-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n const new_cell = {\n id: newCellId,\n cell_type: cell_type!, // validateInput ensures cell_type is not undefined\n source: new_source,\n metadata: {},\n }\n notebook.cells.splice(\n insertIndex,\n 0,\n cell_type === 'markdown' ? new_cell : { ...new_cell, outputs: [] },\n )\n } else {\n // Replace: Find the specified cell\n if (cellIndex === -1) {\n throw new Error(`Cell with ID '${cell_id}' not found`)\n }\n const targetCell = notebook.cells[cellIndex]!\n targetCell.source = new_source\n // Reset execution count and clear outputs since cell was modified\n targetCell.execution_count = undefined\n targetCell.outputs = []\n if (cell_type && cell_type !== targetCell.cell_type) {\n targetCell.cell_type = cell_type\n }\n }\n // Write back to file\n const endings = detectLineEndings(fullPath)\n const updatedNotebook = JSON.stringify(notebook, null, 1)\n writeTextContent(fullPath, updatedNotebook, enc, endings!)\n\n // Record Agent edit operation for file freshness tracking\n recordFileEdit(fullPath, updatedNotebook)\n\n // Emit file edited event for system reminders\n emitReminderEvent('file:edited', {\n filePath: fullPath,\n cellId: cell_id,\n newSource: new_source,\n cellType: cell_type,\n editMode: edit_mode || 'replace',\n timestamp: Date.now(),\n operation: 'notebook_edit',\n })\n const data = {\n cell_id: cell_id || '0',\n new_source,\n cell_type: cell_type ?? 'code',\n language,\n edit_mode: edit_mode ?? 'replace',\n error: '',\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n } catch (error) {\n if (error instanceof Error) {\n const data = {\n cell_id: cell_id || '0',\n new_source,\n cell_type: cell_type ?? 'code',\n language: 'python',\n edit_mode: 'replace',\n error: error.message,\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n return\n }\n const data = {\n cell_id: cell_id || '0',\n new_source,\n cell_type: cell_type ?? 'code',\n language: 'python',\n edit_mode: 'replace',\n error: 'Unknown error occurred while editing notebook',\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n }\n },\n} satisfies Tool<\n typeof inputSchema,\n {\n cell_id: string\n new_source: string\n cell_type: NotebookCellType\n language: string\n edit_mode: string\n error?: string\n }\n>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { existsSync, readFileSync } from 'fs'\nimport { Box, Text } from 'ink'\nimport { extname, isAbsolute, relative, resolve } from 'path'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { HighlightedCode } from '@components/HighlightedCode'\nimport type { Tool } from '@tool'\nimport { NotebookCellType, NotebookContent } from '@minto-types/notebook'\nimport {\n detectFileEncoding,\n detectLineEndings,\n writeTextContent,\n} from '@utils/file'\nimport { safeParseJSON } from '@utils/json'\nimport { getCwd } from '@utils/state'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport { hasWritePermission } from '@utils/permissions/filesystem'\nimport { emitReminderEvent } from '@services/systemReminder'\nimport { recordFileEdit } from '@services/fileFreshness'\nimport { triggerBackup } from '@core/backupHook'\nimport { FileEditTool } from '@tools/FileEditTool/FileEditTool'\n\nconst inputSchema = z.strictObject({\n notebook_path: z\n .string()\n .describe(\n 'The absolute path to the Jupyter notebook file to edit (must be absolute, not relative)',\n ),\n cell_id: z\n .string()\n .optional()\n .describe(\n 'The ID of the cell to edit. When inserting a new cell, the new cell will be inserted after the cell with this ID, or at the beginning if not specified.',\n ),\n new_source: z.string().describe('The new source for the cell'),\n cell_type: z\n .enum(['code', 'markdown'])\n .optional()\n .describe(\n 'The type of the cell (code or markdown). If not specified, it defaults to the current cell type. If using edit_mode=insert, this is required.',\n ),\n edit_mode: z\n .enum(['replace', 'insert', 'delete'])\n .optional()\n .describe(\n 'The type of edit to make (replace, insert, delete). Defaults to replace.',\n ),\n})\n\nexport const NotebookEditTool = {\n name: 'NotebookEdit',\n async description() {\n return DESCRIPTION\n },\n async prompt() {\n return PROMPT\n },\n inputSchema,\n userFacingName() {\n return 'Edit Notebook'\n },\n async isEnabled() {\n return true\n },\n isReadOnly() {\n return false\n },\n isConcurrencySafe() {\n return false // NotebookEditTool modifies state/files, not safe for concurrent execution\n },\n needsPermissions({ notebook_path }) {\n return !hasWritePermission(notebook_path)\n },\n renderResultForAssistant({ cell_id, edit_mode, new_source, error }) {\n if (error) {\n return error\n }\n switch (edit_mode) {\n case 'replace':\n return `Updated cell ${cell_id} with ${new_source}`\n case 'insert':\n return `Inserted cell after ${cell_id || 'beginning'} with ${new_source}`\n case 'delete':\n return `Deleted cell ${cell_id}`\n }\n },\n renderToolUseMessage(input, { verbose }) {\n return `notebook_path: ${verbose ? input.notebook_path : relative(getCwd(), input.notebook_path)}, cell_id: ${input.cell_id || '(beginning)'}, content: ${input.new_source.slice(0, 30)}\u2026, cell_type: ${input.cell_type}, edit_mode: ${input.edit_mode ?? 'replace'}`\n },\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n renderToolResultMessage(output) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\">\n <Text>Notebook cell updated</Text>\n </Box>\n )\n }\n\n const { cell_id, new_source, language, error } = output\n\n if (error) {\n return (\n <Box flexDirection=\"column\">\n <Text color=\"red\">{error}</Text>\n </Box>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n <Text>Updated cell {cell_id}:</Text>\n <Box marginLeft={2}>\n <HighlightedCode code={new_source} language={language} />\n </Box>\n </Box>\n )\n },\n async validateInput({\n notebook_path,\n cell_id,\n cell_type,\n edit_mode = 'replace',\n }) {\n const fullPath = isAbsolute(notebook_path)\n ? notebook_path\n : resolve(getCwd(), notebook_path)\n\n if (!existsSync(fullPath)) {\n return {\n result: false,\n message: 'Notebook file does not exist.',\n }\n }\n\n if (extname(fullPath) !== '.ipynb') {\n return {\n result: false,\n message: `File must be a Jupyter notebook (.ipynb file). For editing other file types, use the ${FileEditTool.name} tool.`,\n }\n }\n\n if (edit_mode === 'insert' && !cell_type) {\n return {\n result: false,\n message: 'Cell type is required when using edit_mode=insert.',\n }\n }\n\n const enc = detectFileEncoding(fullPath)\n const content = readFileSync(fullPath, enc)\n const notebook = safeParseJSON(content) as NotebookContent | null\n if (!notebook) {\n return {\n result: false,\n message: 'Notebook is not valid JSON.',\n }\n }\n\n // For replace and delete, cell_id is required and must exist\n if ((edit_mode === 'replace' || edit_mode === 'delete') && !cell_id) {\n return {\n result: false,\n message: 'cell_id is required for replace and delete operations.',\n }\n }\n\n // Validate cell_id exists in notebook (if provided)\n if (cell_id) {\n const cellIndex = notebook.cells.findIndex(\n (cell: { id?: string }, index: number) =>\n cell.id === cell_id || String(index) === cell_id,\n )\n if (cellIndex === -1 && edit_mode !== 'insert') {\n return {\n result: false,\n message: `Cell with ID '${cell_id}' not found in notebook. Available cells: ${notebook.cells.length}`,\n }\n }\n }\n\n return { result: true }\n },\n async *call({ notebook_path, cell_id, new_source, cell_type, edit_mode }) {\n const fullPath = isAbsolute(notebook_path)\n ? notebook_path\n : resolve(getCwd(), notebook_path)\n\n try {\n const enc = detectFileEncoding(fullPath)\n const content = readFileSync(fullPath, enc)\n const notebook = JSON.parse(content) as NotebookContent\n const language = notebook.metadata.language_info?.name ?? 'python'\n\n // Find cell index by ID (or treat cell_id as index string for backward compatibility)\n const findCellIndex = (id: string | undefined): number => {\n if (!id) return 0 // Insert at beginning if no cell_id\n const byId = notebook.cells.findIndex(\n (cell: { id?: string }) => cell.id === id,\n )\n if (byId !== -1) return byId\n // Fallback: treat as numeric index for backward compatibility\n const numIndex = parseInt(id, 10)\n if (\n !isNaN(numIndex) &&\n numIndex >= 0 &&\n numIndex < notebook.cells.length\n ) {\n return numIndex\n }\n return -1\n }\n\n const cellIndex = findCellIndex(cell_id)\n\n if (edit_mode === 'delete') {\n // Delete the specified cell\n if (cellIndex === -1) {\n throw new Error(`Cell with ID '${cell_id}' not found`)\n }\n notebook.cells.splice(cellIndex, 1)\n } else if (edit_mode === 'insert') {\n // Insert the new cell after the specified cell (or at beginning if no cell_id)\n const insertIndex = cell_id ? cellIndex + 1 : 0\n const newCellId = `cell-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n const new_cell = {\n id: newCellId,\n cell_type: cell_type!, // validateInput ensures cell_type is not undefined\n source: new_source,\n metadata: {},\n }\n notebook.cells.splice(\n insertIndex,\n 0,\n cell_type === 'markdown' ? new_cell : { ...new_cell, outputs: [] },\n )\n } else {\n // Replace: Find the specified cell\n if (cellIndex === -1) {\n throw new Error(`Cell with ID '${cell_id}' not found`)\n }\n const targetCell = notebook.cells[cellIndex]!\n targetCell.source = new_source\n // Reset execution count and clear outputs since cell was modified\n targetCell.execution_count = undefined\n targetCell.outputs = []\n if (cell_type && cell_type !== targetCell.cell_type) {\n targetCell.cell_type = cell_type\n }\n }\n // Write back to file\n const endings = detectLineEndings(fullPath)\n const updatedNotebook = JSON.stringify(notebook, null, 1)\n writeTextContent(fullPath, updatedNotebook, enc, endings!)\n\n // Record Agent edit operation for file freshness tracking\n recordFileEdit(fullPath, updatedNotebook)\n triggerBackup(fullPath, content, updatedNotebook, 'update')\n\n // Emit file edited event for system reminders\n emitReminderEvent('file:edited', {\n filePath: fullPath,\n cellId: cell_id,\n newSource: new_source,\n cellType: cell_type,\n editMode: edit_mode || 'replace',\n timestamp: Date.now(),\n operation: 'notebook_edit',\n })\n const data = {\n cell_id: cell_id || '0',\n new_source,\n cell_type: cell_type ?? 'code',\n language,\n edit_mode: edit_mode ?? 'replace',\n error: '',\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n } catch (error) {\n if (error instanceof Error) {\n const data = {\n cell_id: cell_id || '0',\n new_source,\n cell_type: cell_type ?? 'code',\n language: 'python',\n edit_mode: 'replace',\n error: error.message,\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n return\n }\n const data = {\n cell_id: cell_id || '0',\n new_source,\n cell_type: cell_type ?? 'code',\n language: 'python',\n edit_mode: 'replace',\n error: 'Unknown error occurred while editing notebook',\n }\n yield {\n type: 'result',\n data,\n resultForAssistant: this.renderResultForAssistant(data),\n }\n }\n },\n} satisfies Tool<\n typeof inputSchema,\n {\n cell_id: string\n new_source: string\n cell_type: NotebookCellType\n language: string\n edit_mode: string\n error?: string\n }\n>\n"],
5
+ "mappings": "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",
6
6
  "names": ["data"]
7
7
  }
@@ -49,6 +49,18 @@ import {
49
49
  popAgentContext,
50
50
  cleanupAgentStreamingState
51
51
  } from "../../components/Spinner.js";
52
+ import {
53
+ validateAgentPermission,
54
+ MODE_CONFIGS
55
+ } from "../../types/PermissionMode.js";
56
+ import { executeAgentHooks, createHookContext } from "../../utils/agentHookExecutor.js";
57
+ import {
58
+ loadAgentMemory,
59
+ saveAgentMemory,
60
+ formatMemoryForPrompt
61
+ } from "../../utils/agentMemory.js";
62
+ import { getSkill, loadSkillContent } from "../../utils/skillLoader.js";
63
+ import { substituteVariables } from "../../utils/stringSubstitution.js";
52
64
  const inputSchema = z.object({
53
65
  description: z.string().describe("A short (3-5 word) description of the task"),
54
66
  prompt: z.string().describe("The task for the agent to perform"),
@@ -130,7 +142,11 @@ const TaskTool = {
130
142
  let effectivePrompt = prompt;
131
143
  let effectiveModel = model_name || "task";
132
144
  let toolFilter = null;
145
+ let disallowedToolFilter = null;
133
146
  let temperature = void 0;
147
+ let agentMaxThinkingTokens = void 0;
148
+ let effectivePermissionMode = "default";
149
+ let loadedAgentConfig = null;
134
150
  if (agentType) {
135
151
  const agentConfig = await getAgentByType(agentType);
136
152
  if (!agentConfig) {
@@ -149,6 +165,7 @@ Use /agents command to manage agent configurations.`;
149
165
  hasYieldedResult = true;
150
166
  return;
151
167
  }
168
+ loadedAgentConfig = agentConfig;
152
169
  if (agentConfig.systemPrompt) {
153
170
  effectivePrompt = `${agentConfig.systemPrompt}
154
171
 
@@ -160,6 +177,97 @@ ${prompt}`;
160
177
  }
161
178
  }
162
179
  toolFilter = agentConfig.tools;
180
+ if (agentConfig.disallowedTools?.length) {
181
+ disallowedToolFilter = agentConfig.disallowedTools;
182
+ }
183
+ if (agentConfig.maxThinkingTokens) {
184
+ agentMaxThinkingTokens = agentConfig.maxThinkingTokens;
185
+ }
186
+ if (agentConfig.permissionMode) {
187
+ const sessionMode = safeMode ? "plan" : "default";
188
+ effectivePermissionMode = validateAgentPermission(
189
+ agentConfig.permissionMode,
190
+ sessionMode
191
+ );
192
+ }
193
+ if (agentConfig.memory) {
194
+ try {
195
+ const memoryContent = await loadAgentMemory(
196
+ agentConfig.agentType,
197
+ agentConfig.memory
198
+ );
199
+ if (memoryContent) {
200
+ effectivePrompt = formatMemoryForPrompt(memoryContent, agentConfig.agentType) + "\n" + effectivePrompt;
201
+ }
202
+ } catch (error) {
203
+ debugLogger.warn("TASK_AGENT_MEMORY_LOAD_FAILED", {
204
+ agentId,
205
+ agentType,
206
+ error: error instanceof Error ? error.message : String(error)
207
+ });
208
+ }
209
+ }
210
+ if (agentConfig.skills?.length) {
211
+ for (const skillName of agentConfig.skills) {
212
+ try {
213
+ const skill = getSkill(skillName);
214
+ if (skill) {
215
+ const skillContent = await loadSkillContent(skill);
216
+ const substitutedContent = await substituteVariables(
217
+ skillContent,
218
+ "",
219
+ // No arguments for preloaded skills
220
+ { sessionId: agentId, cwd: process.cwd() }
221
+ );
222
+ effectivePrompt += `
223
+
224
+ <preloaded-skill name="${skillName}">
225
+ ${substitutedContent}
226
+ </preloaded-skill>`;
227
+ }
228
+ } catch (error) {
229
+ debugLogger.warn("TASK_AGENT_SKILL_PRELOAD_FAILED", {
230
+ agentId,
231
+ skillName,
232
+ error: error instanceof Error ? error.message : String(error)
233
+ });
234
+ }
235
+ }
236
+ }
237
+ if (agentConfig.hooks?.onStart?.length) {
238
+ try {
239
+ const hookContext = createHookContext({
240
+ sessionId: agentId,
241
+ agentId,
242
+ agentType
243
+ });
244
+ const hookResult = await executeAgentHooks(
245
+ "onStart",
246
+ agentConfig,
247
+ hookContext
248
+ );
249
+ if (!hookResult.continue) {
250
+ yield {
251
+ type: "result",
252
+ data: [
253
+ {
254
+ type: "text",
255
+ text: `Agent ${agentType} blocked by onStart hook: ${hookResult.reason || "Hook requested stop"}`
256
+ }
257
+ ],
258
+ resultForAssistant: `Agent blocked by onStart hook: ${hookResult.reason}`
259
+ };
260
+ hasYieldedResult = true;
261
+ return;
262
+ }
263
+ } catch (error) {
264
+ debugLogger.warn("TASK_AGENT_ONSTART_HOOK_FAILED", {
265
+ agentId,
266
+ agentType,
267
+ error: error instanceof Error ? error.message : String(error)
268
+ });
269
+ }
270
+ }
163
271
  }
164
272
  if (model) {
165
273
  const modelPointerMap = {
@@ -186,6 +294,17 @@ ${prompt}`;
186
294
  tools = tools.filter((tool) => toolFilter.includes(tool.name));
187
295
  }
188
296
  }
297
+ if (disallowedToolFilter?.length) {
298
+ tools = tools.filter((tool) => !disallowedToolFilter.includes(tool.name));
299
+ }
300
+ if (effectivePermissionMode !== "default") {
301
+ const modeConfig = MODE_CONFIGS[effectivePermissionMode];
302
+ if (modeConfig && modeConfig.allowedTools[0] !== "*") {
303
+ tools = tools.filter(
304
+ (tool) => modeConfig.allowedTools.includes(tool.name)
305
+ );
306
+ }
307
+ }
189
308
  const modelToUse = effectiveModel;
190
309
  if (resumedTranscript) {
191
310
  yield {
@@ -265,6 +384,7 @@ IMPORTANT: You are currently running as ${modelToUse}. You do not need to consul
265
384
  createUserMessage(`[Resumed] ${prompt}`)
266
385
  );
267
386
  }
387
+ const effectiveMaxThinkingTokens = agentMaxThinkingTokens ?? maxThinkingTokens;
268
388
  const queryOptions = {
269
389
  safeMode,
270
390
  forkNumber,
@@ -272,7 +392,7 @@ IMPORTANT: You are currently running as ${modelToUse}. You do not need to consul
272
392
  tools,
273
393
  commands: [],
274
394
  verbose,
275
- maxThinkingTokens,
395
+ maxThinkingTokens: effectiveMaxThinkingTokens,
276
396
  model: modelToUse
277
397
  };
278
398
  if (temperature !== void 0) {
@@ -387,6 +507,47 @@ IMPORTANT: You are currently running as ${modelToUse}. You do not need to consul
387
507
  } else {
388
508
  completeAgentTranscript(agentId, tokenUsage);
389
509
  debugLogger.info("TASK_AGENT_COMPLETED", { agentId, toolUseCount });
510
+ if (loadedAgentConfig?.hooks?.onComplete?.length) {
511
+ try {
512
+ const hookContext = createHookContext({
513
+ sessionId: agentId,
514
+ agentId,
515
+ agentType,
516
+ result: this.renderResultForAssistant(
517
+ lastMessage.message.content.filter((_) => _.type === "text")
518
+ )
519
+ });
520
+ await executeAgentHooks(
521
+ "onComplete",
522
+ loadedAgentConfig,
523
+ hookContext
524
+ );
525
+ } catch (hookError) {
526
+ debugLogger.warn("TASK_AGENT_ONCOMPLETE_HOOK_FAILED", {
527
+ agentId,
528
+ agentType,
529
+ error: hookError instanceof Error ? hookError.message : String(hookError)
530
+ });
531
+ }
532
+ }
533
+ if (loadedAgentConfig?.memory) {
534
+ try {
535
+ const finalResponse = lastMessage.message.content.filter((_) => _.type === "text").map((_) => _.text).join("\n");
536
+ if (finalResponse.trim()) {
537
+ await saveAgentMemory(
538
+ agentType,
539
+ loadedAgentConfig.memory,
540
+ finalResponse
541
+ );
542
+ }
543
+ } catch (memoryError) {
544
+ debugLogger.warn("TASK_AGENT_MEMORY_SAVE_FAILED", {
545
+ agentId,
546
+ agentType,
547
+ error: memoryError instanceof Error ? memoryError.message : String(memoryError)
548
+ });
549
+ }
550
+ }
390
551
  const result = [
391
552
  toolUseCount === 1 ? "1 tool use" : `${toolUseCount} tool uses`,
392
553
  formatNumber(
@@ -444,6 +605,23 @@ ${this.renderResultForAssistant(data)}` : this.renderResultForAssistant(data);
444
605
  error: errorMessage,
445
606
  errorId: friendlyError.id
446
607
  });
608
+ if (loadedAgentConfig?.hooks?.onError?.length) {
609
+ try {
610
+ const hookContext = createHookContext({
611
+ sessionId: agentId,
612
+ agentId,
613
+ agentType,
614
+ error: error instanceof Error ? error : new Error(String(error))
615
+ });
616
+ await executeAgentHooks("onError", loadedAgentConfig, hookContext);
617
+ } catch (hookError) {
618
+ debugLogger.warn("TASK_AGENT_ONERROR_HOOK_FAILED", {
619
+ agentId,
620
+ agentType,
621
+ error: hookError instanceof Error ? hookError.message : String(hookError)
622
+ });
623
+ }
624
+ }
447
625
  yield {
448
626
  type: "result",
449
627
  data: [