@nexus-cortex/executors 4.26.0
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.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +13 -0
- package/dist/ExecutorRegistry.d.ts +89 -0
- package/dist/ExecutorRegistry.d.ts.map +1 -0
- package/dist/ExecutorRegistry.js +219 -0
- package/dist/ExecutorRegistry.js.map +1 -0
- package/dist/base/BaseTool.d.ts +108 -0
- package/dist/base/BaseTool.d.ts.map +1 -0
- package/dist/base/BaseTool.js +111 -0
- package/dist/base/BaseTool.js.map +1 -0
- package/dist/base/ToolRegistry.d.ts +141 -0
- package/dist/base/ToolRegistry.d.ts.map +1 -0
- package/dist/base/ToolRegistry.js +241 -0
- package/dist/base/ToolRegistry.js.map +1 -0
- package/dist/base/ToolResult.d.ts +63 -0
- package/dist/base/ToolResult.d.ts.map +1 -0
- package/dist/base/ToolResult.js +8 -0
- package/dist/base/ToolResult.js.map +1 -0
- package/dist/base/index.d.ts +10 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +8 -0
- package/dist/base/index.js.map +1 -0
- package/dist/implementations/addon/CreateArtifactTool.d.ts +221 -0
- package/dist/implementations/addon/CreateArtifactTool.d.ts.map +1 -0
- package/dist/implementations/addon/CreateArtifactTool.js +1042 -0
- package/dist/implementations/addon/CreateArtifactTool.js.map +1 -0
- package/dist/implementations/addon/FrameDiffCache.d.ts +166 -0
- package/dist/implementations/addon/FrameDiffCache.d.ts.map +1 -0
- package/dist/implementations/addon/FrameDiffCache.js +395 -0
- package/dist/implementations/addon/FrameDiffCache.js.map +1 -0
- package/dist/implementations/addon/H264StreamEncoder.d.ts +84 -0
- package/dist/implementations/addon/H264StreamEncoder.d.ts.map +1 -0
- package/dist/implementations/addon/H264StreamEncoder.js +203 -0
- package/dist/implementations/addon/H264StreamEncoder.js.map +1 -0
- package/dist/implementations/addon/HybridScreenshotManager.d.ts +197 -0
- package/dist/implementations/addon/HybridScreenshotManager.d.ts.map +1 -0
- package/dist/implementations/addon/HybridScreenshotManager.js +415 -0
- package/dist/implementations/addon/HybridScreenshotManager.js.map +1 -0
- package/dist/implementations/addon/InspectSandboxTool.d.ts +54 -0
- package/dist/implementations/addon/InspectSandboxTool.d.ts.map +1 -0
- package/dist/implementations/addon/InspectSandboxTool.js +226 -0
- package/dist/implementations/addon/InspectSandboxTool.js.map +1 -0
- package/dist/implementations/addon/InteractWithSandboxTool.d.ts +90 -0
- package/dist/implementations/addon/InteractWithSandboxTool.d.ts.map +1 -0
- package/dist/implementations/addon/InteractWithSandboxTool.js +367 -0
- package/dist/implementations/addon/InteractWithSandboxTool.js.map +1 -0
- package/dist/implementations/addon/KeyframeDetector.d.ts +140 -0
- package/dist/implementations/addon/KeyframeDetector.d.ts.map +1 -0
- package/dist/implementations/addon/KeyframeDetector.js +390 -0
- package/dist/implementations/addon/KeyframeDetector.js.map +1 -0
- package/dist/implementations/addon/ModifySandboxTool.d.ts +62 -0
- package/dist/implementations/addon/ModifySandboxTool.d.ts.map +1 -0
- package/dist/implementations/addon/ModifySandboxTool.js +266 -0
- package/dist/implementations/addon/ModifySandboxTool.js.map +1 -0
- package/dist/implementations/addon/ReactArtifactBuilder.d.ts +27 -0
- package/dist/implementations/addon/ReactArtifactBuilder.d.ts.map +1 -0
- package/dist/implementations/addon/ReactArtifactBuilder.js +198 -0
- package/dist/implementations/addon/ReactArtifactBuilder.js.map +1 -0
- package/dist/implementations/addon/SandboxEventBroadcaster.d.ts +143 -0
- package/dist/implementations/addon/SandboxEventBroadcaster.d.ts.map +1 -0
- package/dist/implementations/addon/SandboxEventBroadcaster.js +258 -0
- package/dist/implementations/addon/SandboxEventBroadcaster.js.map +1 -0
- package/dist/implementations/addon/SandboxIntrospectionTools.d.ts +77 -0
- package/dist/implementations/addon/SandboxIntrospectionTools.d.ts.map +1 -0
- package/dist/implementations/addon/SandboxIntrospectionTools.js +292 -0
- package/dist/implementations/addon/SandboxIntrospectionTools.js.map +1 -0
- package/dist/implementations/addon/SandboxViewServer.d.ts +127 -0
- package/dist/implementations/addon/SandboxViewServer.d.ts.map +1 -0
- package/dist/implementations/addon/SandboxViewServer.js +775 -0
- package/dist/implementations/addon/SandboxViewServer.js.map +1 -0
- package/dist/implementations/addon/ScreenStream.d.ts +149 -0
- package/dist/implementations/addon/ScreenStream.d.ts.map +1 -0
- package/dist/implementations/addon/ScreenStream.js +306 -0
- package/dist/implementations/addon/ScreenStream.js.map +1 -0
- package/dist/implementations/addon/StopSandboxTool.d.ts +61 -0
- package/dist/implementations/addon/StopSandboxTool.d.ts.map +1 -0
- package/dist/implementations/addon/StopSandboxTool.js +252 -0
- package/dist/implementations/addon/StopSandboxTool.js.map +1 -0
- package/dist/implementations/addon/TerminalSandbox.d.ts +111 -0
- package/dist/implementations/addon/TerminalSandbox.d.ts.map +1 -0
- package/dist/implementations/addon/TerminalSandbox.js +345 -0
- package/dist/implementations/addon/TerminalSandbox.js.map +1 -0
- package/dist/implementations/addon/VisualFeedbackBridge.d.ts +367 -0
- package/dist/implementations/addon/VisualFeedbackBridge.d.ts.map +1 -0
- package/dist/implementations/addon/VisualFeedbackBridge.js +888 -0
- package/dist/implementations/addon/VisualFeedbackBridge.js.map +1 -0
- package/dist/implementations/addon/WindowManager.d.ts +138 -0
- package/dist/implementations/addon/WindowManager.d.ts.map +1 -0
- package/dist/implementations/addon/WindowManager.js +276 -0
- package/dist/implementations/addon/WindowManager.js.map +1 -0
- package/dist/implementations/addon/index.d.ts +29 -0
- package/dist/implementations/addon/index.d.ts.map +1 -0
- package/dist/implementations/addon/index.js +29 -0
- package/dist/implementations/addon/index.js.map +1 -0
- package/dist/implementations/addon/injectables/reactIntrospection.d.ts +57 -0
- package/dist/implementations/addon/injectables/reactIntrospection.d.ts.map +1 -0
- package/dist/implementations/addon/injectables/reactIntrospection.js +480 -0
- package/dist/implementations/addon/injectables/reactIntrospection.js.map +1 -0
- package/dist/implementations/addon/terminal-client.html +253 -0
- package/dist/implementations/agent/PRAgentTool.d.ts +37 -0
- package/dist/implementations/agent/PRAgentTool.d.ts.map +1 -0
- package/dist/implementations/agent/PRAgentTool.js +257 -0
- package/dist/implementations/agent/PRAgentTool.js.map +1 -0
- package/dist/implementations/agent/TaskTool.d.ts +76 -0
- package/dist/implementations/agent/TaskTool.d.ts.map +1 -0
- package/dist/implementations/agent/TaskTool.js +424 -0
- package/dist/implementations/agent/TaskTool.js.map +1 -0
- package/dist/implementations/agent/index.d.ts +5 -0
- package/dist/implementations/agent/index.d.ts.map +1 -0
- package/dist/implementations/agent/index.js +3 -0
- package/dist/implementations/agent/index.js.map +1 -0
- package/dist/implementations/execution/BackgroundProcessRegistry.d.ts +68 -0
- package/dist/implementations/execution/BackgroundProcessRegistry.d.ts.map +1 -0
- package/dist/implementations/execution/BackgroundProcessRegistry.js +146 -0
- package/dist/implementations/execution/BackgroundProcessRegistry.js.map +1 -0
- package/dist/implementations/execution/BashOutputTool.d.ts +42 -0
- package/dist/implementations/execution/BashOutputTool.d.ts.map +1 -0
- package/dist/implementations/execution/BashOutputTool.js +168 -0
- package/dist/implementations/execution/BashOutputTool.js.map +1 -0
- package/dist/implementations/execution/CodeExecuteTool.d.ts +31 -0
- package/dist/implementations/execution/CodeExecuteTool.d.ts.map +1 -0
- package/dist/implementations/execution/CodeExecuteTool.js +127 -0
- package/dist/implementations/execution/CodeExecuteTool.js.map +1 -0
- package/dist/implementations/execution/KillShellTool.d.ts +37 -0
- package/dist/implementations/execution/KillShellTool.d.ts.map +1 -0
- package/dist/implementations/execution/KillShellTool.js +144 -0
- package/dist/implementations/execution/KillShellTool.js.map +1 -0
- package/dist/implementations/execution/SearchToolsTool.d.ts +32 -0
- package/dist/implementations/execution/SearchToolsTool.d.ts.map +1 -0
- package/dist/implementations/execution/SearchToolsTool.js +109 -0
- package/dist/implementations/execution/SearchToolsTool.js.map +1 -0
- package/dist/implementations/execution/ShellTool.d.ts +108 -0
- package/dist/implementations/execution/ShellTool.d.ts.map +1 -0
- package/dist/implementations/execution/ShellTool.js +546 -0
- package/dist/implementations/execution/ShellTool.js.map +1 -0
- package/dist/implementations/execution/WorkspaceManagerTool.d.ts +40 -0
- package/dist/implementations/execution/WorkspaceManagerTool.d.ts.map +1 -0
- package/dist/implementations/execution/WorkspaceManagerTool.js +370 -0
- package/dist/implementations/execution/WorkspaceManagerTool.js.map +1 -0
- package/dist/implementations/execution/index.d.ts +13 -0
- package/dist/implementations/execution/index.d.ts.map +1 -0
- package/dist/implementations/execution/index.js +13 -0
- package/dist/implementations/execution/index.js.map +1 -0
- package/dist/implementations/extensions/EndTurnTool.d.ts +62 -0
- package/dist/implementations/extensions/EndTurnTool.d.ts.map +1 -0
- package/dist/implementations/extensions/EndTurnTool.js +172 -0
- package/dist/implementations/extensions/EndTurnTool.js.map +1 -0
- package/dist/implementations/extensions/ResearchBacklogTool.d.ts +37 -0
- package/dist/implementations/extensions/ResearchBacklogTool.d.ts.map +1 -0
- package/dist/implementations/extensions/ResearchBacklogTool.js +102 -0
- package/dist/implementations/extensions/ResearchBacklogTool.js.map +1 -0
- package/dist/implementations/extensions/SkillTool.d.ts +108 -0
- package/dist/implementations/extensions/SkillTool.d.ts.map +1 -0
- package/dist/implementations/extensions/SkillTool.js +351 -0
- package/dist/implementations/extensions/SkillTool.js.map +1 -0
- package/dist/implementations/extensions/SlashCommandTool.d.ts +112 -0
- package/dist/implementations/extensions/SlashCommandTool.d.ts.map +1 -0
- package/dist/implementations/extensions/SlashCommandTool.js +315 -0
- package/dist/implementations/extensions/SlashCommandTool.js.map +1 -0
- package/dist/implementations/extensions/index.d.ts +14 -0
- package/dist/implementations/extensions/index.d.ts.map +1 -0
- package/dist/implementations/extensions/index.js +10 -0
- package/dist/implementations/extensions/index.js.map +1 -0
- package/dist/implementations/file/EditTool.d.ts +232 -0
- package/dist/implementations/file/EditTool.d.ts.map +1 -0
- package/dist/implementations/file/EditTool.js +707 -0
- package/dist/implementations/file/EditTool.js.map +1 -0
- package/dist/implementations/file/ReadFileTool.d.ts +49 -0
- package/dist/implementations/file/ReadFileTool.d.ts.map +1 -0
- package/dist/implementations/file/ReadFileTool.js +225 -0
- package/dist/implementations/file/ReadFileTool.js.map +1 -0
- package/dist/implementations/file/WriteBinaryTool.d.ts +21 -0
- package/dist/implementations/file/WriteBinaryTool.d.ts.map +1 -0
- package/dist/implementations/file/WriteBinaryTool.js +153 -0
- package/dist/implementations/file/WriteBinaryTool.js.map +1 -0
- package/dist/implementations/file/WriteFileTool.d.ts +41 -0
- package/dist/implementations/file/WriteFileTool.d.ts.map +1 -0
- package/dist/implementations/file/WriteFileTool.js +220 -0
- package/dist/implementations/file/WriteFileTool.js.map +1 -0
- package/dist/implementations/file/index.d.ts +8 -0
- package/dist/implementations/file/index.d.ts.map +1 -0
- package/dist/implementations/file/index.js +8 -0
- package/dist/implementations/file/index.js.map +1 -0
- package/dist/implementations/historical/GetConversationSegmentTool.d.ts +44 -0
- package/dist/implementations/historical/GetConversationSegmentTool.d.ts.map +1 -0
- package/dist/implementations/historical/GetConversationSegmentTool.js +220 -0
- package/dist/implementations/historical/GetConversationSegmentTool.js.map +1 -0
- package/dist/implementations/historical/ListCompactionBoundariesTool.d.ts +36 -0
- package/dist/implementations/historical/ListCompactionBoundariesTool.d.ts.map +1 -0
- package/dist/implementations/historical/ListCompactionBoundariesTool.js +174 -0
- package/dist/implementations/historical/ListCompactionBoundariesTool.js.map +1 -0
- package/dist/implementations/historical/ListSessionsTool.d.ts +38 -0
- package/dist/implementations/historical/ListSessionsTool.d.ts.map +1 -0
- package/dist/implementations/historical/ListSessionsTool.js +140 -0
- package/dist/implementations/historical/ListSessionsTool.js.map +1 -0
- package/dist/implementations/historical/LoadSessionTool.d.ts +39 -0
- package/dist/implementations/historical/LoadSessionTool.d.ts.map +1 -0
- package/dist/implementations/historical/LoadSessionTool.js +171 -0
- package/dist/implementations/historical/LoadSessionTool.js.map +1 -0
- package/dist/implementations/historical/RequestHistoricalContextTool.d.ts +46 -0
- package/dist/implementations/historical/RequestHistoricalContextTool.d.ts.map +1 -0
- package/dist/implementations/historical/RequestHistoricalContextTool.js +224 -0
- package/dist/implementations/historical/RequestHistoricalContextTool.js.map +1 -0
- package/dist/implementations/historical/SearchConversationHistoryTool.d.ts +51 -0
- package/dist/implementations/historical/SearchConversationHistoryTool.d.ts.map +1 -0
- package/dist/implementations/historical/SearchConversationHistoryTool.js +306 -0
- package/dist/implementations/historical/SearchConversationHistoryTool.js.map +1 -0
- package/dist/implementations/historical/index.d.ts +12 -0
- package/dist/implementations/historical/index.d.ts.map +1 -0
- package/dist/implementations/historical/index.js +12 -0
- package/dist/implementations/historical/index.js.map +1 -0
- package/dist/implementations/index.d.ts +16 -0
- package/dist/implementations/index.d.ts.map +1 -0
- package/dist/implementations/index.js +28 -0
- package/dist/implementations/index.js.map +1 -0
- package/dist/implementations/mcp/DiscoveredMcpTool.d.ts +58 -0
- package/dist/implementations/mcp/DiscoveredMcpTool.d.ts.map +1 -0
- package/dist/implementations/mcp/DiscoveredMcpTool.js +269 -0
- package/dist/implementations/mcp/DiscoveredMcpTool.js.map +1 -0
- package/dist/implementations/mcp/index.d.ts +9 -0
- package/dist/implementations/mcp/index.d.ts.map +1 -0
- package/dist/implementations/mcp/index.js +8 -0
- package/dist/implementations/mcp/index.js.map +1 -0
- package/dist/implementations/notebook/NotebookEditTool.d.ts +96 -0
- package/dist/implementations/notebook/NotebookEditTool.d.ts.map +1 -0
- package/dist/implementations/notebook/NotebookEditTool.js +390 -0
- package/dist/implementations/notebook/NotebookEditTool.js.map +1 -0
- package/dist/implementations/notebook/index.d.ts +7 -0
- package/dist/implementations/notebook/index.d.ts.map +1 -0
- package/dist/implementations/notebook/index.js +7 -0
- package/dist/implementations/notebook/index.js.map +1 -0
- package/dist/implementations/search/GlobTool.d.ts +73 -0
- package/dist/implementations/search/GlobTool.d.ts.map +1 -0
- package/dist/implementations/search/GlobTool.js +213 -0
- package/dist/implementations/search/GlobTool.js.map +1 -0
- package/dist/implementations/search/GrepTool.d.ts +102 -0
- package/dist/implementations/search/GrepTool.d.ts.map +1 -0
- package/dist/implementations/search/GrepTool.js +754 -0
- package/dist/implementations/search/GrepTool.js.map +1 -0
- package/dist/implementations/search/index.d.ts +6 -0
- package/dist/implementations/search/index.d.ts.map +1 -0
- package/dist/implementations/search/index.js +6 -0
- package/dist/implementations/search/index.js.map +1 -0
- package/dist/implementations/tmux/TmuxSessionTool.d.ts +82 -0
- package/dist/implementations/tmux/TmuxSessionTool.d.ts.map +1 -0
- package/dist/implementations/tmux/TmuxSessionTool.js +371 -0
- package/dist/implementations/tmux/TmuxSessionTool.js.map +1 -0
- package/dist/implementations/tmux/TmuxViewServer.d.ts +86 -0
- package/dist/implementations/tmux/TmuxViewServer.d.ts.map +1 -0
- package/dist/implementations/tmux/TmuxViewServer.js +480 -0
- package/dist/implementations/tmux/TmuxViewServer.js.map +1 -0
- package/dist/implementations/tmux/index.d.ts +6 -0
- package/dist/implementations/tmux/index.d.ts.map +1 -0
- package/dist/implementations/tmux/index.js +6 -0
- package/dist/implementations/tmux/index.js.map +1 -0
- package/dist/implementations/ui/AskUserQuestionTool.d.ts +77 -0
- package/dist/implementations/ui/AskUserQuestionTool.d.ts.map +1 -0
- package/dist/implementations/ui/AskUserQuestionTool.js +241 -0
- package/dist/implementations/ui/AskUserQuestionTool.js.map +1 -0
- package/dist/implementations/ui/ExitPlanModeTool.d.ts +44 -0
- package/dist/implementations/ui/ExitPlanModeTool.d.ts.map +1 -0
- package/dist/implementations/ui/ExitPlanModeTool.js +150 -0
- package/dist/implementations/ui/ExitPlanModeTool.js.map +1 -0
- package/dist/implementations/ui/TodoWriteTool.d.ts +59 -0
- package/dist/implementations/ui/TodoWriteTool.d.ts.map +1 -0
- package/dist/implementations/ui/TodoWriteTool.js +315 -0
- package/dist/implementations/ui/TodoWriteTool.js.map +1 -0
- package/dist/implementations/ui/index.d.ts +9 -0
- package/dist/implementations/ui/index.d.ts.map +1 -0
- package/dist/implementations/ui/index.js +9 -0
- package/dist/implementations/ui/index.js.map +1 -0
- package/dist/implementations/web/BrowseTool.d.ts +43 -0
- package/dist/implementations/web/BrowseTool.d.ts.map +1 -0
- package/dist/implementations/web/BrowseTool.js +181 -0
- package/dist/implementations/web/BrowseTool.js.map +1 -0
- package/dist/implementations/web/SandboxTransferTool.d.ts +30 -0
- package/dist/implementations/web/SandboxTransferTool.d.ts.map +1 -0
- package/dist/implementations/web/SandboxTransferTool.js +261 -0
- package/dist/implementations/web/SandboxTransferTool.js.map +1 -0
- package/dist/implementations/web/WebFetchTool.d.ts +93 -0
- package/dist/implementations/web/WebFetchTool.d.ts.map +1 -0
- package/dist/implementations/web/WebFetchTool.js +484 -0
- package/dist/implementations/web/WebFetchTool.js.map +1 -0
- package/dist/implementations/web/WebSearchTool.d.ts +53 -0
- package/dist/implementations/web/WebSearchTool.d.ts.map +1 -0
- package/dist/implementations/web/WebSearchTool.js +227 -0
- package/dist/implementations/web/WebSearchTool.js.map +1 -0
- package/dist/implementations/web/escalateDirective.d.ts +11 -0
- package/dist/implementations/web/escalateDirective.d.ts.map +1 -0
- package/dist/implementations/web/escalateDirective.js +20 -0
- package/dist/implementations/web/escalateDirective.js.map +1 -0
- package/dist/implementations/web/index.d.ts +10 -0
- package/dist/implementations/web/index.d.ts.map +1 -0
- package/dist/implementations/web/index.js +10 -0
- package/dist/implementations/web/index.js.map +1 -0
- package/dist/implementations/web/webBackends.d.ts +65 -0
- package/dist/implementations/web/webBackends.d.ts.map +1 -0
- package/dist/implementations/web/webBackends.js +430 -0
- package/dist/implementations/web/webBackends.js.map +1 -0
- package/dist/implementations/web/webFetchRequestInit.d.ts +9 -0
- package/dist/implementations/web/webFetchRequestInit.d.ts.map +1 -0
- package/dist/implementations/web/webFetchRequestInit.js +21 -0
- package/dist/implementations/web/webFetchRequestInit.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/ArtifactRegistry.d.ts +138 -0
- package/dist/utils/ArtifactRegistry.d.ts.map +1 -0
- package/dist/utils/ArtifactRegistry.js +259 -0
- package/dist/utils/ArtifactRegistry.js.map +1 -0
- package/dist/utils/ChromiumBrowserManager.d.ts +56 -0
- package/dist/utils/ChromiumBrowserManager.d.ts.map +1 -0
- package/dist/utils/ChromiumBrowserManager.js +243 -0
- package/dist/utils/ChromiumBrowserManager.js.map +1 -0
- package/dist/utils/FileUtils.d.ts +81 -0
- package/dist/utils/FileUtils.d.ts.map +1 -0
- package/dist/utils/FileUtils.js +148 -0
- package/dist/utils/FileUtils.js.map +1 -0
- package/dist/utils/GitPolicy.d.ts +70 -0
- package/dist/utils/GitPolicy.d.ts.map +1 -0
- package/dist/utils/GitPolicy.js +166 -0
- package/dist/utils/GitPolicy.js.map +1 -0
- package/dist/utils/GitUtils.d.ts +18 -0
- package/dist/utils/GitUtils.d.ts.map +1 -0
- package/dist/utils/GitUtils.js +62 -0
- package/dist/utils/GitUtils.js.map +1 -0
- package/dist/utils/SandboxRegistry.d.ts +110 -0
- package/dist/utils/SandboxRegistry.d.ts.map +1 -0
- package/dist/utils/SandboxRegistry.js +220 -0
- package/dist/utils/SandboxRegistry.js.map +1 -0
- package/dist/utils/SchemaValidator.d.ts +21 -0
- package/dist/utils/SchemaValidator.d.ts.map +1 -0
- package/dist/utils/SchemaValidator.js +67 -0
- package/dist/utils/SchemaValidator.js.map +1 -0
- package/dist/utils/SessionLock.d.ts +96 -0
- package/dist/utils/SessionLock.d.ts.map +1 -0
- package/dist/utils/SessionLock.js +276 -0
- package/dist/utils/SessionLock.js.map +1 -0
- package/dist/utils/SessionPersistence.d.ts +89 -0
- package/dist/utils/SessionPersistence.d.ts.map +1 -0
- package/dist/utils/SessionPersistence.js +244 -0
- package/dist/utils/SessionPersistence.js.map +1 -0
- package/dist/utils/TextUtils.d.ts +77 -0
- package/dist/utils/TextUtils.d.ts.map +1 -0
- package/dist/utils/TextUtils.js +112 -0
- package/dist/utils/TextUtils.js.map +1 -0
- package/dist/utils/TmuxCapture.d.ts +94 -0
- package/dist/utils/TmuxCapture.d.ts.map +1 -0
- package/dist/utils/TmuxCapture.js +131 -0
- package/dist/utils/TmuxCapture.js.map +1 -0
- package/dist/utils/TmuxManager.d.ts +65 -0
- package/dist/utils/TmuxManager.d.ts.map +1 -0
- package/dist/utils/TmuxManager.js +304 -0
- package/dist/utils/TmuxManager.js.map +1 -0
- package/dist/utils/autoResearchPlanGate.d.ts +10 -0
- package/dist/utils/autoResearchPlanGate.d.ts.map +1 -0
- package/dist/utils/autoResearchPlanGate.js +57 -0
- package/dist/utils/autoResearchPlanGate.js.map +1 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EditFile Tool Executor
|
|
3
|
+
*
|
|
4
|
+
* Performs exact string replacements within a file.
|
|
5
|
+
* Requires precise matching of old_string for safety.
|
|
6
|
+
*
|
|
7
|
+
* Adapted and simplified from Gemini CLI patterns
|
|
8
|
+
* - Removed: ModifiableTool interface, user approval, LLM-based correction
|
|
9
|
+
* - Kept: Core string replacement, occurrence validation, diff generation
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: Following the read-before-edit protocol, this tool requires a prior Read
|
|
12
|
+
* operation in the same session before allowing edits. This ensures the LLM
|
|
13
|
+
* has current file content and prevents edits based on stale context.
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import crypto from 'crypto';
|
|
18
|
+
import * as Diff from 'diff';
|
|
19
|
+
import { BaseTool } from '../../base/index.js';
|
|
20
|
+
import { SchemaValidator } from '../../utils/SchemaValidator.js';
|
|
21
|
+
import { makeRelative, shortenPath, fileExists, resolveFilePath, } from '../../utils/FileUtils.js';
|
|
22
|
+
import { safeLiteralReplace, countOccurrences, normalizeLineEndings, } from '../../utils/TextUtils.js';
|
|
23
|
+
/**
|
|
24
|
+
* Session-level tracking of files that have been read/edited.
|
|
25
|
+
* This enforces the read-before-edit protocol with timestamp-based staleness detection
|
|
26
|
+
* and content-based fingerprinting for smarter freshness checks.
|
|
27
|
+
*
|
|
28
|
+
* Key behavior (read-before-edit):
|
|
29
|
+
* - Files must be read before editing
|
|
30
|
+
* - After each edit, the file becomes "stale" and must be re-read
|
|
31
|
+
* - Content fingerprinting allows detecting if unrelated edits affect your target section
|
|
32
|
+
* - This ensures LLM always has current file state in evolving codebases
|
|
33
|
+
*/
|
|
34
|
+
export class FileReadTracker {
|
|
35
|
+
static fileReadTimestamps = new Map();
|
|
36
|
+
static fileEditTimestamps = new Map();
|
|
37
|
+
static editedSections = new Map();
|
|
38
|
+
// Phase 1: Section fingerprinting for content-based freshness detection
|
|
39
|
+
// Key: filePath, Value: Map of "startLine-endLine" -> SectionFingerprint
|
|
40
|
+
static sectionFingerprints = new Map();
|
|
41
|
+
// Phase 2: Consecutive edit tracking for brief read mode
|
|
42
|
+
static MAX_CONSECUTIVE_EDITS = 2;
|
|
43
|
+
static consecutiveEditCount = new Map();
|
|
44
|
+
static lastEditRegion = new Map();
|
|
45
|
+
/**
|
|
46
|
+
* Calculate SHA-256 hash of content, truncated to 16 characters
|
|
47
|
+
* This is efficient and provides sufficient uniqueness for content comparison
|
|
48
|
+
*/
|
|
49
|
+
static calculateSectionHash(content) {
|
|
50
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Store a fingerprint for a read section
|
|
54
|
+
* Called by ReadFileTool after successfully reading a section
|
|
55
|
+
*/
|
|
56
|
+
static storeSectionFingerprint(filePath, startLine, endLine, content) {
|
|
57
|
+
const key = `${startLine}-${endLine}`;
|
|
58
|
+
const fingerprint = {
|
|
59
|
+
startLine,
|
|
60
|
+
endLine,
|
|
61
|
+
contentHash: this.calculateSectionHash(content),
|
|
62
|
+
timestamp: Date.now()
|
|
63
|
+
};
|
|
64
|
+
let fileFingerprints = this.sectionFingerprints.get(filePath);
|
|
65
|
+
if (!fileFingerprints) {
|
|
66
|
+
fileFingerprints = new Map();
|
|
67
|
+
this.sectionFingerprints.set(filePath, fileFingerprints);
|
|
68
|
+
}
|
|
69
|
+
fileFingerprints.set(key, fingerprint);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Update fingerprint after a successful edit
|
|
73
|
+
* This keeps the fingerprint in sync with our known changes, enabling consecutive edits.
|
|
74
|
+
* The key insight: after WE edit the file, we know the new content is correct.
|
|
75
|
+
* We update the fingerprint so subsequent edits can verify no EXTERNAL changes occurred.
|
|
76
|
+
*/
|
|
77
|
+
static updateFingerprintAfterEdit(filePath, startLine, endLine, newContent) {
|
|
78
|
+
// Update all fingerprints that overlap with the edited region
|
|
79
|
+
const fileFingerprints = this.sectionFingerprints.get(filePath);
|
|
80
|
+
if (!fileFingerprints) {
|
|
81
|
+
// No existing fingerprints, create one for the edited region
|
|
82
|
+
this.storeSectionFingerprint(filePath, startLine, endLine, newContent);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Update existing fingerprints that overlap with edited region
|
|
86
|
+
// Also add the new edit region as a fingerprint
|
|
87
|
+
this.storeSectionFingerprint(filePath, startLine, endLine, newContent);
|
|
88
|
+
// For any overlapping fingerprints, update them with current file content
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
91
|
+
const lines = content.split('\n');
|
|
92
|
+
for (const [key, fingerprint] of fileFingerprints) {
|
|
93
|
+
// Check if this fingerprint overlaps with edited region
|
|
94
|
+
const overlaps = !(fingerprint.endLine <= startLine || fingerprint.startLine >= endLine);
|
|
95
|
+
if (overlaps) {
|
|
96
|
+
// Update this fingerprint with current content
|
|
97
|
+
const sectionContent = lines.slice(fingerprint.startLine, fingerprint.endLine).join('\n');
|
|
98
|
+
fingerprint.contentHash = this.calculateSectionHash(sectionContent);
|
|
99
|
+
fingerprint.timestamp = Date.now();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// File read error - leave fingerprints as-is
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if a previously read section still has the same content
|
|
109
|
+
* Returns true if the section's current content matches the stored fingerprint
|
|
110
|
+
*/
|
|
111
|
+
static isSectionFresh(filePath, startLine, endLine) {
|
|
112
|
+
const key = `${startLine}-${endLine}`;
|
|
113
|
+
const fileFingerprints = this.sectionFingerprints.get(filePath);
|
|
114
|
+
if (!fileFingerprints) {
|
|
115
|
+
return false; // No fingerprint stored
|
|
116
|
+
}
|
|
117
|
+
const storedFingerprint = fileFingerprints.get(key);
|
|
118
|
+
if (!storedFingerprint) {
|
|
119
|
+
return false; // No fingerprint for this section
|
|
120
|
+
}
|
|
121
|
+
// Read the current content of this section and compare hashes
|
|
122
|
+
try {
|
|
123
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
124
|
+
const lines = content.split('\n');
|
|
125
|
+
const sectionContent = lines.slice(startLine, endLine).join('\n');
|
|
126
|
+
const currentHash = this.calculateSectionHash(sectionContent);
|
|
127
|
+
return currentHash === storedFingerprint.contentHash;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false; // File read error - consider stale
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if a line range overlaps or is adjacent to another range
|
|
135
|
+
* Used for brief read mode to allow consecutive edits in same region
|
|
136
|
+
*/
|
|
137
|
+
static isRegionAdjacent(range1, range2, tolerance = 10) {
|
|
138
|
+
// Check if ranges overlap or are within tolerance lines of each other
|
|
139
|
+
const gap = Math.max(0, Math.max(range1.startLine, range2.startLine) -
|
|
140
|
+
Math.min(range1.endLine, range2.endLine));
|
|
141
|
+
return gap <= tolerance;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Mark a file as read at current timestamp
|
|
145
|
+
* Optionally store content fingerprint for section-based freshness detection
|
|
146
|
+
*/
|
|
147
|
+
static markAsRead(filePath, startLine, endLine, content) {
|
|
148
|
+
this.fileReadTimestamps.set(filePath, Date.now());
|
|
149
|
+
// Track which sections were read (for smart re-read suggestions)
|
|
150
|
+
if (startLine !== undefined && endLine !== undefined) {
|
|
151
|
+
const sections = this.editedSections.get(filePath) || [];
|
|
152
|
+
sections.push({ startLine, endLine });
|
|
153
|
+
this.editedSections.set(filePath, sections);
|
|
154
|
+
// Store content fingerprint if content is provided
|
|
155
|
+
if (content !== undefined) {
|
|
156
|
+
this.storeSectionFingerprint(filePath, startLine, endLine, content);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Reset consecutive edit count on fresh read
|
|
160
|
+
this.consecutiveEditCount.set(filePath, 0);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Mark a file as edited at current timestamp
|
|
164
|
+
* Tracks the edit region for consecutive edit allowance in brief read mode
|
|
165
|
+
*
|
|
166
|
+
* @param filePath Path to the file being edited
|
|
167
|
+
* @param startLine Optional start line of the edit (for region tracking)
|
|
168
|
+
* @param endLine Optional end line of the edit (for region tracking)
|
|
169
|
+
*/
|
|
170
|
+
static markAsEdited(filePath, startLine, endLine) {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
this.fileEditTimestamps.set(filePath, now);
|
|
173
|
+
// A successful edit proves the model knows the file content (old_string matched).
|
|
174
|
+
// Bump read timestamp so the file stays "fresh" — no forced re-read for the
|
|
175
|
+
// model's own edits. Only external modifications should trigger staleness.
|
|
176
|
+
this.fileReadTimestamps.set(filePath, now + 1);
|
|
177
|
+
// Track consecutive edit count for brief read mode
|
|
178
|
+
const currentCount = this.consecutiveEditCount.get(filePath) || 0;
|
|
179
|
+
this.consecutiveEditCount.set(filePath, currentCount + 1);
|
|
180
|
+
// Track the edit region for adjacent edit detection
|
|
181
|
+
if (startLine !== undefined && endLine !== undefined) {
|
|
182
|
+
this.lastEditRegion.set(filePath, { startLine, endLine });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if an edit should be allowed under brief read mode
|
|
187
|
+
* Returns true if:
|
|
188
|
+
* 1. We're under MAX_CONSECUTIVE_EDITS
|
|
189
|
+
* 2. The edit is in the same or adjacent region as previous edits
|
|
190
|
+
* 3. The section's content hash still matches (no external changes)
|
|
191
|
+
*/
|
|
192
|
+
static canSkipReRead(filePath, editStartLine, editEndLine) {
|
|
193
|
+
// Check consecutive edit count
|
|
194
|
+
const editCount = this.consecutiveEditCount.get(filePath) || 0;
|
|
195
|
+
if (editCount >= this.MAX_CONSECUTIVE_EDITS) {
|
|
196
|
+
return false; // Exceeded max consecutive edits
|
|
197
|
+
}
|
|
198
|
+
// If we have edit region info, check if this edit is in adjacent region
|
|
199
|
+
const lastRegion = this.lastEditRegion.get(filePath);
|
|
200
|
+
if (lastRegion && editStartLine !== undefined && editEndLine !== undefined) {
|
|
201
|
+
const newRegion = { startLine: editStartLine, endLine: editEndLine };
|
|
202
|
+
if (!this.isRegionAdjacent(lastRegion, newRegion)) {
|
|
203
|
+
return false; // Edit is in a different region
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Check if any stored fingerprint for this file still matches
|
|
207
|
+
const fileFingerprints = this.sectionFingerprints.get(filePath);
|
|
208
|
+
if (!fileFingerprints || fileFingerprints.size === 0) {
|
|
209
|
+
return false; // No fingerprints stored
|
|
210
|
+
}
|
|
211
|
+
// Verify at least one section's content hash still matches
|
|
212
|
+
for (const [key, fingerprint] of fileFingerprints) {
|
|
213
|
+
if (this.isSectionFresh(filePath, fingerprint.startLine, fingerprint.endLine)) {
|
|
214
|
+
return true; // Found a fresh section - content hasn't been externally modified
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return false; // No fresh sections found
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check if file has been read AND is still fresh (not edited since last read)
|
|
221
|
+
* Matches a standard coding CLI behavior: require re-read after each edit
|
|
222
|
+
*/
|
|
223
|
+
static hasBeenRead(filePath) {
|
|
224
|
+
const readTime = this.fileReadTimestamps.get(filePath);
|
|
225
|
+
if (!readTime) {
|
|
226
|
+
return false; // Never read
|
|
227
|
+
}
|
|
228
|
+
const editTime = this.fileEditTimestamps.get(filePath);
|
|
229
|
+
if (editTime && editTime > readTime) {
|
|
230
|
+
return false; // Edited after last read - stale!
|
|
231
|
+
}
|
|
232
|
+
return true; // Read and still fresh
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get suggested read parameters for re-reading after edit
|
|
236
|
+
* Returns the section around the last edit for efficient re-reading
|
|
237
|
+
*/
|
|
238
|
+
static getSuggestedReadParams(filePath) {
|
|
239
|
+
const sections = this.editedSections.get(filePath);
|
|
240
|
+
if (!sections || sections.length === 0) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
// Get last edited section
|
|
244
|
+
const lastSection = sections[sections.length - 1];
|
|
245
|
+
if (!lastSection) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
// Read with context: 10 lines before, the section, 10 lines after
|
|
249
|
+
const contextLines = 10;
|
|
250
|
+
const offset = Math.max(0, lastSection.startLine - contextLines);
|
|
251
|
+
const limit = (lastSection.endLine - lastSection.startLine) + (contextLines * 2);
|
|
252
|
+
return { offset, limit };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Find where a string appears in a file and return suggested read parameters
|
|
256
|
+
* This enables smart error messages that tell the model exactly where to read
|
|
257
|
+
*
|
|
258
|
+
* @param filePath Path to the file
|
|
259
|
+
* @param searchString The string to find (typically old_string from failed edit)
|
|
260
|
+
* @param contextLines Number of lines of context above/below (default: 10)
|
|
261
|
+
* @returns Suggested offset/limit, or null if string not found or file can't be read
|
|
262
|
+
*/
|
|
263
|
+
static findStringInFile(filePath, searchString, contextLines = 10) {
|
|
264
|
+
try {
|
|
265
|
+
// Read file synchronously (validation is sync)
|
|
266
|
+
const fs = require('fs');
|
|
267
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
268
|
+
const lines = content.split('\n');
|
|
269
|
+
// Find the line containing the start of the search string
|
|
270
|
+
// Handle multi-line strings by looking for the first line
|
|
271
|
+
const firstLineOfSearch = searchString.split('\n')[0];
|
|
272
|
+
for (let i = 0; i < lines.length; i++) {
|
|
273
|
+
if (lines[i]?.includes(firstLineOfSearch)) {
|
|
274
|
+
// Found it! Calculate how many lines the old_string spans
|
|
275
|
+
const searchLineCount = searchString.split('\n').length;
|
|
276
|
+
// Return offset with context above, limit covers string + context below
|
|
277
|
+
const offset = Math.max(0, i - contextLines);
|
|
278
|
+
const limit = searchLineCount + (contextLines * 2);
|
|
279
|
+
return {
|
|
280
|
+
offset,
|
|
281
|
+
limit,
|
|
282
|
+
lineNumber: i + 1 // 1-indexed for display
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return null; // String not found in file
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return null; // File read error
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Clear all session state
|
|
294
|
+
*/
|
|
295
|
+
static clearSession() {
|
|
296
|
+
this.fileReadTimestamps.clear();
|
|
297
|
+
this.fileEditTimestamps.clear();
|
|
298
|
+
this.editedSections.clear();
|
|
299
|
+
this.sectionFingerprints.clear();
|
|
300
|
+
this.consecutiveEditCount.clear();
|
|
301
|
+
this.lastEditRegion.clear();
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get list of files read in this session
|
|
305
|
+
*/
|
|
306
|
+
static getReadFiles() {
|
|
307
|
+
return Array.from(this.fileReadTimestamps.keys());
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Check if file is stale (edited after last read)
|
|
311
|
+
*/
|
|
312
|
+
static isStale(filePath) {
|
|
313
|
+
const readTime = this.fileReadTimestamps.get(filePath);
|
|
314
|
+
const editTime = this.fileEditTimestamps.get(filePath);
|
|
315
|
+
if (!readTime)
|
|
316
|
+
return false; // Not read yet, not stale
|
|
317
|
+
if (!editTime)
|
|
318
|
+
return false; // Never edited, not stale
|
|
319
|
+
return editTime > readTime; // Stale if edited after read
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Cross-agent staleness: of the files THIS session has read, which have
|
|
323
|
+
* changed on disk since they were read — by ANY writer (the user, another
|
|
324
|
+
* agent, or an external process)? Unlike `isStale` (which only sees this
|
|
325
|
+
* agent's own EditTool edits via `markAsEdited`), this compares each read
|
|
326
|
+
* file's CURRENT disk mtime against the read timestamp. mtime is bumped by
|
|
327
|
+
* every writer, so this catches uncommitted changes made outside this agent.
|
|
328
|
+
*
|
|
329
|
+
* Used by the harness to warn the model to re-read before editing when two
|
|
330
|
+
* agents (or a human + agent) share one working tree.
|
|
331
|
+
*
|
|
332
|
+
* @returns Files read this session that are now modified or deleted on disk.
|
|
333
|
+
*/
|
|
334
|
+
static getExternallyChangedFiles() {
|
|
335
|
+
const changed = [];
|
|
336
|
+
for (const [filePath, readTime] of this.fileReadTimestamps) {
|
|
337
|
+
try {
|
|
338
|
+
const mtimeMs = fs.statSync(filePath).mtimeMs;
|
|
339
|
+
// Small epsilon guard: only flag when the on-disk mtime is meaningfully
|
|
340
|
+
// newer than the moment we read it.
|
|
341
|
+
if (mtimeMs > readTime + 1) {
|
|
342
|
+
changed.push({ path: filePath, deleted: false });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// statSync throws if the file was deleted/moved since we read it.
|
|
347
|
+
changed.push({ path: filePath, deleted: true });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return changed;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Phase 3: Suggest optimal read parameters for multiple edit targets
|
|
354
|
+
* When the model plans multiple edits, this calculates a single read that covers all targets
|
|
355
|
+
*
|
|
356
|
+
* @param filePath Path to the file
|
|
357
|
+
* @param editTargets Array of strings to search for (old_string values for planned edits)
|
|
358
|
+
* @param contextLines Number of context lines above/below (default: 10)
|
|
359
|
+
* @returns Suggested offset/limit that covers all targets, or null if not all targets found
|
|
360
|
+
*/
|
|
361
|
+
static suggestBatchRead(filePath, editTargets, contextLines = 10) {
|
|
362
|
+
if (editTargets.length === 0) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
367
|
+
const lines = content.split('\n');
|
|
368
|
+
// Find line numbers for all edit targets
|
|
369
|
+
const coverage = [];
|
|
370
|
+
let minLine = Infinity;
|
|
371
|
+
let maxLine = -Infinity;
|
|
372
|
+
for (const target of editTargets) {
|
|
373
|
+
const firstLineOfSearch = target.split('\n')[0];
|
|
374
|
+
if (!firstLineOfSearch)
|
|
375
|
+
continue;
|
|
376
|
+
for (let i = 0; i < lines.length; i++) {
|
|
377
|
+
if (lines[i]?.includes(firstLineOfSearch)) {
|
|
378
|
+
const targetLineCount = target.split('\n').length;
|
|
379
|
+
const startLine = i;
|
|
380
|
+
const endLine = i + targetLineCount;
|
|
381
|
+
minLine = Math.min(minLine, startLine);
|
|
382
|
+
maxLine = Math.max(maxLine, endLine);
|
|
383
|
+
coverage.push({ target: firstLineOfSearch.substring(0, 40), lineNumber: i + 1 });
|
|
384
|
+
break; // Found this target, move to next
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// If we didn't find all targets, return null
|
|
389
|
+
if (coverage.length !== editTargets.length) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
// Calculate bounding box with context
|
|
393
|
+
const offset = Math.max(0, minLine - contextLines);
|
|
394
|
+
const limit = (maxLine - minLine) + (contextLines * 2);
|
|
395
|
+
return { offset, limit, coverage };
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return null; // File read error
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get the consecutive edit count for a file
|
|
403
|
+
* Useful for displaying in error messages
|
|
404
|
+
*/
|
|
405
|
+
static getConsecutiveEditCount(filePath) {
|
|
406
|
+
return this.consecutiveEditCount.get(filePath) || 0;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Get the max allowed consecutive edits
|
|
410
|
+
*/
|
|
411
|
+
static getMaxConsecutiveEdits() {
|
|
412
|
+
return this.MAX_CONSECUTIVE_EDITS;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* EditFile Tool Executor
|
|
417
|
+
*
|
|
418
|
+
* Features:
|
|
419
|
+
* - Exact string replacement (no fuzzy matching)
|
|
420
|
+
* - Single or multiple occurrence replacement
|
|
421
|
+
* - Validates unique match (unless replace_all is true)
|
|
422
|
+
* - Generates diff for display
|
|
423
|
+
* - Security: prevents path traversal
|
|
424
|
+
*/
|
|
425
|
+
export class EditTool extends BaseTool {
|
|
426
|
+
config;
|
|
427
|
+
constructor(config) {
|
|
428
|
+
super('Edit', 'Edit', `Performs exact string replacements in files. By default, replaces a single occurrence (requires unique match). Set replace_all to true to replace all occurrences.
|
|
429
|
+
|
|
430
|
+
CRITICAL REQUIREMENTS:
|
|
431
|
+
1. file_path MUST be an absolute path
|
|
432
|
+
2. old_string MUST be the EXACT literal text to replace (including all whitespace, indentation, newlines)
|
|
433
|
+
3. new_string MUST be the EXACT literal text to replace old_string with
|
|
434
|
+
4. For single replacements: old_string must match exactly ONE location in the file
|
|
435
|
+
5. For multiple replacements: set replace_all to true
|
|
436
|
+
|
|
437
|
+
IMPORTANT: Include sufficient context (3+ lines before/after) in old_string to ensure unique matching.`, {
|
|
438
|
+
type: 'object',
|
|
439
|
+
properties: {
|
|
440
|
+
file_path: {
|
|
441
|
+
type: 'string',
|
|
442
|
+
description: "The absolute path to the file to modify. Must start with '/'.",
|
|
443
|
+
},
|
|
444
|
+
old_string: {
|
|
445
|
+
type: 'string',
|
|
446
|
+
description: 'The exact literal text to replace. For single replacements, include at least 3 lines of context before and after to ensure unique matching. Must match exactly (including whitespace and indentation).',
|
|
447
|
+
},
|
|
448
|
+
new_string: {
|
|
449
|
+
type: 'string',
|
|
450
|
+
description: 'The exact literal text to replace old_string with. Provide the EXACT text with proper indentation and formatting.',
|
|
451
|
+
},
|
|
452
|
+
replace_all: {
|
|
453
|
+
type: 'boolean',
|
|
454
|
+
description: 'If true, replaces all occurrences of old_string. If false (default), requires exactly one match.',
|
|
455
|
+
},
|
|
456
|
+
expected_replacements: {
|
|
457
|
+
type: 'number',
|
|
458
|
+
description: 'Optional: Number of replacements expected. Defaults to 1 for single replacements. Edit will fail if actual count does not match expected count. Use this to validate your edit affects the expected number of locations.',
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
required: ['file_path', 'old_string', 'new_string'],
|
|
462
|
+
});
|
|
463
|
+
this.config = config;
|
|
464
|
+
}
|
|
465
|
+
validateToolParams(params) {
|
|
466
|
+
// Schema validation
|
|
467
|
+
const schemaError = SchemaValidator.validate(this.parameterSchema, params);
|
|
468
|
+
if (schemaError) {
|
|
469
|
+
return schemaError;
|
|
470
|
+
}
|
|
471
|
+
// Normalize and resolve path (with smart fallback for doubled directory names)
|
|
472
|
+
let filePath = resolveFilePath(params.file_path, this.config.workingDirectory);
|
|
473
|
+
// Path validation: allow any absolute path (matching standard absolute-path behavior).
|
|
474
|
+
if (!path.isAbsolute(filePath)) {
|
|
475
|
+
return `File path must resolve to an absolute path: ${filePath}`;
|
|
476
|
+
}
|
|
477
|
+
// Update params with resolved path
|
|
478
|
+
params.file_path = filePath;
|
|
479
|
+
// Special case: empty old_string means create new file
|
|
480
|
+
if (params.old_string === '' && !fileExists(filePath)) {
|
|
481
|
+
return null; // Valid: creating new file
|
|
482
|
+
}
|
|
483
|
+
// MANDATORY READ-BEFORE-EDIT PROTOCOL (read-before-edit standard)
|
|
484
|
+
// Ensure file has been read in this session before allowing edits
|
|
485
|
+
// Phase 2: Brief read mode allows up to MAX_CONSECUTIVE_EDITS in same region if content unchanged
|
|
486
|
+
if (params.old_string !== '' && !FileReadTracker.hasBeenRead(filePath)) {
|
|
487
|
+
const relativePath = makeRelative(filePath, this.config.workingDirectory);
|
|
488
|
+
// Check if file is stale (edited after last read)
|
|
489
|
+
const isStale = FileReadTracker.isStale(filePath);
|
|
490
|
+
if (isStale) {
|
|
491
|
+
// Phase 2: Brief read mode - check if we can allow this edit without re-read
|
|
492
|
+
// Conditions: consecutive edit count < MAX, same region, content hash unchanged
|
|
493
|
+
const stringLocation = FileReadTracker.findStringInFile(filePath, params.old_string);
|
|
494
|
+
if (stringLocation) {
|
|
495
|
+
const editStartLine = stringLocation.lineNumber - 1; // 0-indexed
|
|
496
|
+
const editEndLine = editStartLine + params.old_string.split('\n').length;
|
|
497
|
+
if (FileReadTracker.canSkipReRead(filePath, editStartLine, editEndLine)) {
|
|
498
|
+
// Brief read mode: Allow edit without re-read
|
|
499
|
+
// The content fingerprint confirms no external changes, and we're under the limit
|
|
500
|
+
return null; // Allow the edit
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// File was edited since last read and doesn't qualify for brief read mode
|
|
504
|
+
// Provide smart suggestion for re-read
|
|
505
|
+
const suggestedParams = FileReadTracker.getSuggestedReadParams(filePath);
|
|
506
|
+
if (suggestedParams) {
|
|
507
|
+
const editCount = FileReadTracker.getConsecutiveEditCount(filePath);
|
|
508
|
+
const maxEdits = FileReadTracker.getMaxConsecutiveEdits();
|
|
509
|
+
const briefModeMsg = editCount >= maxEdits
|
|
510
|
+
? ` (Reached max ${maxEdits} consecutive edits without re-read.)`
|
|
511
|
+
: '';
|
|
512
|
+
return `File has been edited since you last read it.${briefModeMsg} You must re-read the file to see the current state before making another edit. Use: read tool with file_path: "${relativePath}", offset: ${suggestedParams.offset}, limit: ${suggestedParams.limit} to see the recently edited section with context.`;
|
|
513
|
+
}
|
|
514
|
+
// Second try: find where their old_string appears and suggest that location
|
|
515
|
+
if (stringLocation) {
|
|
516
|
+
return `File has been edited since you last read it. You must re-read the file to see the current state before making another edit.
|
|
517
|
+
|
|
518
|
+
Your edit target appears around line ${stringLocation.lineNumber}. Use:
|
|
519
|
+
read(file_path: "${relativePath}", offset: ${stringLocation.offset}, limit: ${stringLocation.limit})
|
|
520
|
+
|
|
521
|
+
This covers lines ${stringLocation.offset + 1}-${stringLocation.offset + stringLocation.limit} where your edit target appears.`;
|
|
522
|
+
}
|
|
523
|
+
return `File has been edited since you last read it. You must re-read the file to see the current state before making another edit. Use the read tool with file_path: "${relativePath}"`;
|
|
524
|
+
}
|
|
525
|
+
// Never read before - search for where the old_string appears and suggest targeted read
|
|
526
|
+
const stringLocation = FileReadTracker.findStringInFile(filePath, params.old_string);
|
|
527
|
+
if (stringLocation) {
|
|
528
|
+
return `You must read the file before editing it.
|
|
529
|
+
|
|
530
|
+
Your edit target appears around line ${stringLocation.lineNumber}. Use:
|
|
531
|
+
read(file_path: "${relativePath}", offset: ${stringLocation.offset}, limit: ${stringLocation.limit})
|
|
532
|
+
|
|
533
|
+
This covers lines ${stringLocation.offset + 1}-${stringLocation.offset + stringLocation.limit} where your edit target appears.`;
|
|
534
|
+
}
|
|
535
|
+
// Fallback: couldn't find the string, suggest reading the whole file
|
|
536
|
+
return `You must read the file before editing it. Use the read tool first to see the current file content (file_path: "${relativePath}"), then call edit with the exact text you want to replace. This ensures you have the current file state and prevents edits based on stale or assumed content.`;
|
|
537
|
+
}
|
|
538
|
+
// Check file exists for edits
|
|
539
|
+
if (params.old_string !== '' && !fileExists(filePath)) {
|
|
540
|
+
return `File not found: ${filePath}. Use write tool to create new files.`;
|
|
541
|
+
}
|
|
542
|
+
// If old_string is empty but file exists, error
|
|
543
|
+
if (params.old_string === '' && fileExists(filePath)) {
|
|
544
|
+
return `Cannot create file that already exists: ${filePath}`;
|
|
545
|
+
}
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
getDescription(params) {
|
|
549
|
+
if (!params || !params.file_path) {
|
|
550
|
+
return 'Edit file';
|
|
551
|
+
}
|
|
552
|
+
const relativePath = makeRelative(params.file_path, this.config.workingDirectory);
|
|
553
|
+
const shortened = shortenPath(relativePath);
|
|
554
|
+
// Special case: creating new file
|
|
555
|
+
if (params.old_string === '') {
|
|
556
|
+
return `Create ${shortened}`;
|
|
557
|
+
}
|
|
558
|
+
// Same old and new strings
|
|
559
|
+
if (params.old_string === params.new_string) {
|
|
560
|
+
return `No changes to ${shortened}`;
|
|
561
|
+
}
|
|
562
|
+
// Show snippet of change
|
|
563
|
+
const oldSnippet = (params.old_string.split('\n')[0] || '').substring(0, 30) +
|
|
564
|
+
(params.old_string.length > 30 ? '...' : '');
|
|
565
|
+
const newSnippet = (params.new_string.split('\n')[0] || '').substring(0, 30) +
|
|
566
|
+
(params.new_string.length > 30 ? '...' : '');
|
|
567
|
+
return `${shortened}: ${oldSnippet} => ${newSnippet}`;
|
|
568
|
+
}
|
|
569
|
+
async execute(params, signal, updateOutput) {
|
|
570
|
+
const startTime = Date.now();
|
|
571
|
+
// Validate parameters
|
|
572
|
+
const validationError = this.validateToolParams(params);
|
|
573
|
+
if (validationError) {
|
|
574
|
+
return this.createErrorResult(validationError);
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
// Special case: creating new file
|
|
578
|
+
if (params.old_string === '' && !fileExists(params.file_path)) {
|
|
579
|
+
return await this.createNewFile(params, startTime, updateOutput);
|
|
580
|
+
}
|
|
581
|
+
// Read current file content
|
|
582
|
+
let currentContent = await fs.promises.readFile(params.file_path, 'utf-8');
|
|
583
|
+
// Normalize line endings to LF
|
|
584
|
+
currentContent = normalizeLineEndings(currentContent);
|
|
585
|
+
// Count occurrences
|
|
586
|
+
const occurrences = countOccurrences(currentContent, params.old_string);
|
|
587
|
+
// Determine expected count
|
|
588
|
+
const expectedReplacements = params.expected_replacements ?? (params.replace_all ? occurrences : 1);
|
|
589
|
+
// Validate occurrence count
|
|
590
|
+
if (occurrences === 0) {
|
|
591
|
+
return this.createErrorResult(`Failed to edit: could not find the string to replace in ${params.file_path}. ` +
|
|
592
|
+
`The exact text in old_string was not found. ` +
|
|
593
|
+
`Ensure you're matching whitespace and indentation precisely.`);
|
|
594
|
+
}
|
|
595
|
+
if (!params.replace_all && occurrences > 1) {
|
|
596
|
+
return this.createErrorResult(`Failed to edit: found ${occurrences} occurrences but expected exactly 1. ` +
|
|
597
|
+
`Either set replace_all to true or include more context in old_string to make it unique.`);
|
|
598
|
+
}
|
|
599
|
+
// Validate expected replacements if specified
|
|
600
|
+
if (params.expected_replacements !== undefined && occurrences !== expectedReplacements) {
|
|
601
|
+
return this.createErrorResult(`Failed to edit: found ${occurrences} occurrences but expected ${expectedReplacements}. ` +
|
|
602
|
+
`The actual count does not match the expected count. ` +
|
|
603
|
+
`Either update expected_replacements or verify old_string is correct.`);
|
|
604
|
+
}
|
|
605
|
+
// Perform safe literal replacement (handles $ escape sequences correctly)
|
|
606
|
+
const newContent = safeLiteralReplace(currentContent, params.old_string, params.new_string);
|
|
607
|
+
// Write updated content
|
|
608
|
+
await fs.promises.writeFile(params.file_path, newContent, 'utf-8');
|
|
609
|
+
// Calculate edit region for consecutive edit tracking
|
|
610
|
+
// Find the line number where old_string starts
|
|
611
|
+
const lines = currentContent.split('\n');
|
|
612
|
+
let editStartLine = 0;
|
|
613
|
+
const firstLineOfOldString = params.old_string.split('\n')[0] || '';
|
|
614
|
+
for (let i = 0; i < lines.length; i++) {
|
|
615
|
+
if (lines[i]?.includes(firstLineOfOldString)) {
|
|
616
|
+
editStartLine = i;
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const editEndLine = editStartLine + params.new_string.split('\n').length;
|
|
621
|
+
// Mark file as edited with region info (enables brief read mode tracking)
|
|
622
|
+
FileReadTracker.markAsEdited(params.file_path, editStartLine, editEndLine);
|
|
623
|
+
// Update fingerprint to reflect our edit - this enables consecutive edits
|
|
624
|
+
// by keeping the fingerprint in sync with our known changes
|
|
625
|
+
const newLines = newContent.split('\n');
|
|
626
|
+
const editedSectionContent = newLines.slice(editStartLine, editEndLine).join('\n');
|
|
627
|
+
FileReadTracker.updateFingerprintAfterEdit(params.file_path, editStartLine, editEndLine, editedSectionContent);
|
|
628
|
+
// Generate diff for display
|
|
629
|
+
const fileName = path.basename(params.file_path);
|
|
630
|
+
const diff = Diff.createPatch(fileName, currentContent, newContent, 'Current', 'Proposed', { context: 3 });
|
|
631
|
+
// Format success message
|
|
632
|
+
const relativePath = makeRelative(params.file_path, this.config.workingDirectory);
|
|
633
|
+
const displayContent = `Modified ${relativePath} (${occurrences} replacement${occurrences > 1 ? 's' : ''})`;
|
|
634
|
+
// Stream output if callback provided
|
|
635
|
+
if (updateOutput) {
|
|
636
|
+
updateOutput(displayContent);
|
|
637
|
+
}
|
|
638
|
+
return this.createSuccessResult(`Successfully modified file: ${params.file_path} (${occurrences} replacement${occurrences > 1 ? 's' : ''}).`, {
|
|
639
|
+
executionTime: Date.now() - startTime,
|
|
640
|
+
resourcesUsed: {
|
|
641
|
+
files: [params.file_path],
|
|
642
|
+
},
|
|
643
|
+
fileStats: {
|
|
644
|
+
path: relativePath,
|
|
645
|
+
occurrences,
|
|
646
|
+
operation: 'edit',
|
|
647
|
+
},
|
|
648
|
+
diff, // Include diff in metadata for potential UI display
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
// Handle write errors
|
|
653
|
+
if (error.code === 'EACCES') {
|
|
654
|
+
return this.createErrorResult(`Permission denied: ${params.file_path}`);
|
|
655
|
+
}
|
|
656
|
+
if (error.code === 'ENOSPC') {
|
|
657
|
+
return this.createErrorResult('No space left on device');
|
|
658
|
+
}
|
|
659
|
+
if (error.code === 'EROFS') {
|
|
660
|
+
return this.createErrorResult('Read-only file system');
|
|
661
|
+
}
|
|
662
|
+
return this.createErrorResult(`Failed to edit file: ${error.message}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Creates a new file (when old_string is empty)
|
|
667
|
+
* @private
|
|
668
|
+
*/
|
|
669
|
+
async createNewFile(params, startTime, updateOutput) {
|
|
670
|
+
try {
|
|
671
|
+
// Ensure parent directory exists
|
|
672
|
+
const dirPath = path.dirname(params.file_path);
|
|
673
|
+
if (!fs.existsSync(dirPath)) {
|
|
674
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
675
|
+
}
|
|
676
|
+
// Write file
|
|
677
|
+
await fs.promises.writeFile(params.file_path, params.new_string, 'utf-8');
|
|
678
|
+
// Get file stats
|
|
679
|
+
const stats = await fs.promises.stat(params.file_path);
|
|
680
|
+
const fileSize = stats.size;
|
|
681
|
+
const lineCount = params.new_string.split('\n').length;
|
|
682
|
+
// Format for display
|
|
683
|
+
const relativePath = makeRelative(params.file_path, this.config.workingDirectory);
|
|
684
|
+
const displayContent = `Created ${relativePath} (${lineCount} lines, ${fileSize} bytes)`;
|
|
685
|
+
// Stream output if callback provided
|
|
686
|
+
if (updateOutput) {
|
|
687
|
+
updateOutput(displayContent);
|
|
688
|
+
}
|
|
689
|
+
return this.createSuccessResult(`Created new file: ${params.file_path} with provided content.`, {
|
|
690
|
+
executionTime: Date.now() - startTime,
|
|
691
|
+
resourcesUsed: {
|
|
692
|
+
files: [params.file_path],
|
|
693
|
+
},
|
|
694
|
+
fileStats: {
|
|
695
|
+
path: relativePath,
|
|
696
|
+
size: fileSize,
|
|
697
|
+
lines: lineCount,
|
|
698
|
+
operation: 'create',
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
catch (error) {
|
|
703
|
+
return this.createErrorResult(`Failed to create file: ${error.message}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
//# sourceMappingURL=EditTool.js.map
|