diff --git a/Core/Sources/ChatService/ChatService.swift b/Core/Sources/ChatService/ChatService.swift index 0aa5a5a8..3f65d49b 100644 --- a/Core/Sources/ChatService/ChatService.swift +++ b/Core/Sources/ChatService/ChatService.swift @@ -229,15 +229,12 @@ public final class ChatService: ChatServiceType, ObservableObject { } public func updateToolCallStatus(toolCallId: String, status: AgentToolCall.ToolCallStatus, payload: Any? = nil) { - if status == .cancelled { - resetOngoingRequest(with: .cancelled) - return - } - // Send the tool call result back to the server - if let toolCallRequest = self.pendingToolCallRequests[toolCallId], status == .accepted { + if let toolCallRequest = self.pendingToolCallRequests[toolCallId], status == .accepted || status == .cancelled { self.pendingToolCallRequests.removeValue(forKey: toolCallId) - let toolResult = LanguageModelToolConfirmationResult(result: .Accept) + let toolResult = LanguageModelToolConfirmationResult( + result: status == .accepted ? .Accept : .Dismiss + ) let jsonResult = try? JSONEncoder().encode(toolResult) let jsonValue = (try? JSONDecoder().decode(JSONValue.self, from: jsonResult ?? Data())) ?? JSONValue.null toolCallRequest.completion( diff --git a/Core/Sources/ConversationTab/Chat.swift b/Core/Sources/ConversationTab/Chat.swift index eca31802..19e195be 100644 --- a/Core/Sources/ConversationTab/Chat.swift +++ b/Core/Sources/ConversationTab/Chat.swift @@ -784,9 +784,12 @@ struct Chat { let chatContext: ChatContext = .from(message, projectURL: projectURL) state.editor.setContext(chatContext, for: mode) state.editorMode = mode + let isReceivingMessage = service.isReceivingMessage return .run { send in - await send(.stopRespondingButtonTapped) + if isReceivingMessage { + await send(.stopRespondingButtonTapped) + } } } diff --git a/Core/Sources/ConversationTab/ChatPanel.swift b/Core/Sources/ConversationTab/ChatPanel.swift index d111fdfc..0cc83479 100644 --- a/Core/Sources/ConversationTab/ChatPanel.swift +++ b/Core/Sources/ConversationTab/ChatPanel.swift @@ -43,6 +43,7 @@ public struct ChatPanel: View { if let _ = chat.history.last?.followUp { ChatFollowUp(chat: chat) .scaledPadding(.vertical, 8) + .scaledPadding(.horizontal, 16) .dimWithExitEditMode(chat) } } @@ -50,11 +51,12 @@ public struct ChatPanel: View { if chat.fileEditMap.count > 0 { WorkingSetView(chat: chat) .dimWithExitEditMode(chat) + .scaledPadding(.horizontal, 16) } ChatPanelInputArea(chat: chat, r: r, editorMode: .input) - .chatPanelInputAreaPadding(.input) .dimWithExitEditMode(chat) + .scaledPadding(.horizontal, 16) } .scaledPadding(.vertical, 12) .background(Color.chatWindowBackgroundColor) @@ -183,7 +185,7 @@ struct ChatPanelMessages: View { } } .listStyle(.plain) - .padding(.horizontal, -8) + .scaledPadding(.leading, 8) .listRowBackground(EmptyView()) .modify { view in if #available(macOS 13.0, *) { @@ -406,6 +408,7 @@ struct ChatHistory: View { // check point CheckPoint(chat: chat, messageId: message.id) .padding(.vertical, 8) + .padding(.trailing, 8) } } @@ -413,6 +416,7 @@ struct ChatHistory: View { if message.id == pendingCheckpointMessageId { CheckPoint(chat: chat, messageId: message.id) .padding(.vertical, 8) + .padding(.trailing, 8) } } .dimWithExitEditMode( @@ -445,6 +449,7 @@ struct ChatHistoryItem: View { requestType: message.requestType ) .scaledPadding(.leading, chat.editorMode.isEditingUserMessage && chat.editorMode.editingUserMessageId == message.id ? 0 : 20) + .scaledPadding(.trailing, 8) case .assistant: BotMessage( message: message, diff --git a/Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift b/Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift index 228ad965..937bc5bd 100644 --- a/Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift +++ b/Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift @@ -302,9 +302,13 @@ struct ModelPicker: View { HStack(spacing: 0) { // Custom segmented control with color change ChatModePicker(chatMode: $chatMode, onScopeChange: switchModelsForScope) - .onAppear() { + .onAppear { updateAgentPicker() } + .onReceive( + NotificationCenter.default.publisher(for: .gitHubCopilotChatModeDidChange)) { _ in + updateAgentPicker() + } if chatMode == "Agent" { mcpButton diff --git a/Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift b/Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift index 5d57b03d..e2182eda 100644 --- a/Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift +++ b/Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift @@ -82,7 +82,7 @@ struct RunInTerminalToolView: View { toolView } - .padding(8) + .scaledPadding(8) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) @@ -123,7 +123,7 @@ struct RunInTerminalToolView: View { Text(command!) .textSelection(.enabled) .scaledFont(size: chatFontSize, design: .monospaced) - .padding(8) + .scaledPadding(8) .frame(maxWidth: .infinity, alignment: .leading) .foregroundStyle(codeForegroundColor) .background(codeBackgroundColor) @@ -147,19 +147,23 @@ struct RunInTerminalToolView: View { .frame(maxWidth: .infinity, alignment: .leading) HStack { - Button("Cancel") { + Button(action: { chat.send(.toolCallCancelled(tool.id)) + }) { + Text("Skip") + .scaledFont(.body) } - .scaledFont(.body) - - Button("Continue") { + + Button(action: { chat.send(.toolCallAccepted(tool.id)) + }) { + Text("Allow") + .scaledFont(.body) } - .scaledFont(.body) .buttonStyle(BorderedProminentButtonStyle()) } .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 4) + .scaledPadding(.top, 4) } } } diff --git a/Core/Sources/ConversationTab/ViewExtension.swift b/Core/Sources/ConversationTab/ViewExtension.swift index 1b4bd992..181f3cbd 100644 --- a/Core/Sources/ConversationTab/ViewExtension.swift +++ b/Core/Sources/ConversationTab/ViewExtension.swift @@ -84,18 +84,6 @@ extension View { self.hoverForeground(isHovered: isHovered, defaultColor: .secondary) } - // MARK: - Chat Panel Input Area - func chatPanelInputAreaPadding(_ mode: Chat.EditorMode) -> some View { - var trailingPadding: CGFloat - switch mode { - case .input: - trailingPadding = 16 - case .editUserMessage: - trailingPadding = 8 - } - return self.padding(.trailing, trailingPadding) - } - // MARK: - Editor Mode /// Dims the view when in edit mode and provides tap/keyboard exit functionality diff --git a/Core/Sources/ConversationTab/Views/BotMessage.swift b/Core/Sources/ConversationTab/Views/BotMessage.swift index 97974f2e..d951f589 100644 --- a/Core/Sources/ConversationTab/Views/BotMessage.swift +++ b/Core/Sources/ConversationTab/Views/BotMessage.swift @@ -387,7 +387,7 @@ private struct TurnStatusView: View { } private var cancelStatus: some View { - statusView(icon: "slash.circle", iconColor: .secondary, text: "Cancelled") + statusView(icon: "slash.circle", iconColor: .secondary, text: "Stopped") } private var errorStatus: some View { diff --git a/Core/Sources/ConversationTab/Views/ChatPanelInputArea/InputAreaTextEditor.swift b/Core/Sources/ConversationTab/Views/ChatPanelInputArea/InputAreaTextEditor.swift index ae635e8d..c9addc8b 100644 --- a/Core/Sources/ConversationTab/Views/ChatPanelInputArea/InputAreaTextEditor.swift +++ b/Core/Sources/ConversationTab/Views/ChatPanelInputArea/InputAreaTextEditor.swift @@ -211,7 +211,7 @@ struct InputAreaTextEditor: View { } .overlay { RoundedRectangle(cornerRadius: 6) - .stroke(Color(nsColor: .controlColor), lineWidth: 1) + .stroke(.quaternary, lineWidth: 1) } .background { if isEditorActive { @@ -452,6 +452,14 @@ struct InputAreaTextEditor: View { @ViewBuilder func makeCurrentEditorView(_ ref: ConversationFileReference) -> some View { + let toggleTrailingPadding: CGFloat = { + if #available(macOS 26.0, *) { + return 8 + } else { + return 4 + } + }() + HStack(spacing: 0) { makeContextFileNameView(url: ref.url, isCurrentEditor: true, selection: ref.selection) @@ -459,7 +467,7 @@ struct InputAreaTextEditor: View { .toggleStyle(SwitchToggleStyle(tint: .blue)) .controlSize(.mini) .frame(width: 34) - .padding(.trailing, 4) + .padding(.trailing, toggleTrailingPadding) .onChange(of: isCurrentEditorContextEnabled) { newValue in enableCurrentEditorContext = newValue } diff --git a/Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift b/Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift index 27c3d590..39c8ccc4 100644 --- a/Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift +++ b/Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift @@ -39,7 +39,6 @@ struct ProgressToolCalls: View { RunInTerminalToolView(tool: tool, chat: chat) } else if tool.invokeParams != nil && tool.status == .waitForConfirmation { ToolConfirmationView(tool: tool, chat: chat) - .scaledPadding(8) } else { ToolStatusItemView(tool: tool) } @@ -64,20 +63,26 @@ struct ToolConfirmationView: View { .frame(maxWidth: .infinity, alignment: .leading) HStack { - Button("Cancel") { + Button(action: { chat.send(.toolCallCancelled(tool.id)) + }) { + Text("Skip") + .scaledFont(.body) } - .scaledFont(.body) - - Button("Continue") { + + Button(action: { chat.send(.toolCallAccepted(tool.id)) + }) { + Text("Allow") + .scaledFont(.body) } .buttonStyle(BorderedProminentButtonStyle()) - .scaledFont(.body) + } .frame(maxWidth: .infinity, alignment: .leading) .scaledPadding(.top, 4) } + .scaledPadding(8) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) diff --git a/Core/Sources/ConversationTab/Views/UserMessage.swift b/Core/Sources/ConversationTab/Views/UserMessage.swift index 7f32e179..4b8a22e3 100644 --- a/Core/Sources/ConversationTab/Views/UserMessage.swift +++ b/Core/Sources/ConversationTab/Views/UserMessage.swift @@ -112,7 +112,6 @@ private struct MessageInputArea: View { editorMode: editorMode ) .frame(maxWidth: .infinity) - .chatPanelInputAreaPadding(editorMode) } } diff --git a/Core/Sources/SuggestionWidget/ChatWindow/ChatHistoryView.swift b/Core/Sources/SuggestionWidget/ChatWindow/ChatHistoryView.swift index c5b6428e..bb3747c6 100644 --- a/Core/Sources/SuggestionWidget/ChatWindow/ChatHistoryView.swift +++ b/Core/Sources/SuggestionWidget/ChatWindow/ChatHistoryView.swift @@ -17,7 +17,7 @@ struct ChatHistoryView: View { var body: some View { WithPerceptionTracking { - VStack(alignment: .center, spacing: 8) { + VStack(alignment: .center, spacing: 0) { Header(isChatHistoryVisible: $isChatHistoryVisible) .scaledFrame(height: 32) .scaledPadding(.leading, 12) @@ -28,6 +28,7 @@ struct ChatHistoryView: View { ChatHistorySearchBarView(searchText: $searchText) .scaledPadding(.leading, 12) .scaledPadding(.trailing, 8) + .scaledPadding(.vertical, 8) ItemView(store: store, searchText: $searchText, isChatHistoryVisible: $isChatHistoryVisible) .scaledPadding(.leading, 12) @@ -222,6 +223,7 @@ struct ChatHistoryItemView: View { .padding(.horizontal, 12) } .frame(maxHeight: .infinity) + .contentShape(Rectangle()) .onHover(perform: { isHovered = $0 }) diff --git a/Core/Sources/SuggestionWidget/ChatWindowView.swift b/Core/Sources/SuggestionWidget/ChatWindowView.swift index e0fdda85..665ccd70 100644 --- a/Core/Sources/SuggestionWidget/ChatWindowView.swift +++ b/Core/Sources/SuggestionWidget/ChatWindowView.swift @@ -72,7 +72,6 @@ struct ChatView: View { Divider() ChatTabContainer(store: store) - .scaledPadding(.horizontal, 16) .frame(maxWidth: .infinity, maxHeight: .infinity) } } diff --git a/Docs/Images/document-folder-permission-request.png b/Docs/Images/document-folder-permission-request.png new file mode 100644 index 00000000..1d512ae4 Binary files /dev/null and b/Docs/Images/document-folder-permission-request.png differ diff --git a/Docs/Images/screen-record-permission-request.png b/Docs/Images/screen-record-permission-request.png new file mode 100644 index 00000000..0b3eeb25 Binary files /dev/null and b/Docs/Images/screen-record-permission-request.png differ diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 68be3768..eee16478 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -12,6 +12,8 @@ common issues: - [Extension Permission](#extension-permission) - Allows GitHub Copilot to integrate with Xcode - [Accessibility Permission](#accessibility-permission) - Enables real-time code suggestions - [Background Permission](#background-permission) - Allows extension to connect with host app + - [Files & Folders Permission](#files--folders-permission) - Allows GitHub Copilot for Xcode to access files and folders + - [Screen & System Audio Recording Permission](#screen--system-audio-recording-permission-optional) (Optional) - Allow GitHub Copilot for Xcode to capture screen when using Copilot Vision Please note that GitHub Copilot for Xcode may not work properly if any necessary permissions are missing. @@ -30,7 +32,8 @@ Or you can navigate to the permission manually depending on your OS version: | macOS | Location | | :--- | :--- | -| 15 | System Settings > General > Login Items > Extensions > Xcode Source Editor | +| 26 | System Settings > General > Login Items & Extensions > Extensions > By Category > Xcode Source Editor | +| 15 | System Settings > General > Login Items & Extensions > Extensions > Xcode Source Editor | | 13 & 14 | System Settings > Privacy & Security > Extensions > Xcode Source Editor | | 12 | System Preferences > Extensions | @@ -74,15 +77,57 @@ Please ensure that this permission is enabled. You can manually navigate to the | macOS | Location | | :--- | :--- | +| 26 | System Settings > General > Login Items & Extensions > App Background Activity | | 15 | System Settings > General > Login Items & Extensions > Allow in the Background | | 13 & 14 | System Settings > General > Login Items > Allow in the Background | Ensure that "GitHub Copilot for Xcode" is enabled in the list of allowed background items. Without this permission, the extension may not be able to properly communicate with the host app, which can result in inconsistent behavior or reduced functionality. +## Files & Folders Permission + +GitHub Copilot for Xcode needs permission to read your project’s files so it can: + +- Use actual file contents as contextual grounding when you ask questions in Ask and Agent mode (instead of generic language-only answers) +- Safely apply or preview multi-file edits in Agent modes (e.g. refactors, adding tests, updating related types) +- Improve precision by leveraging nearby code, patterns, and naming conventions + +

+ Files & Folders Permission +

+ +When first prompted macOS shows a dialog asking to allow access to folders. Click "Allow". +If you clicked "Don't Allow" or nothing appears: + +| macOS | Location | +| :--- | :--- | +| 13 & 14 & 15 & 26 | System Settings > Privacy & Security > Files and Folders | +| 12 | System Preferences > Security & Privacy > Privacy > Files and Folders | + +In the list, expand `GitHub Copilot for Xcode` and enable the toggles for any relevant locations (e.g. “Documents” if your repositories live there). If your code is elsewhere (e.g. `~/Developer`), macOS may instead prompt dynamically the next time Copilot tries to read those paths—accept when prompted. + +## Screen & System Audio Recording Permission (Optional) + +This permission is only needed if you choose to use Copilot Vision (screen-based context capture). + +Copilot does NOT require screen recording for standard inline suggestions, chat, or agent operations. + +

+ Screen & System Audio Recording Permission +

+ +This permission is typically granted automatically when you first use Copilot Vision and try to capture screen in GitHub Copilot for Xcode. You can also manually navigate to the background permission setting based on your macOS version: + +| macOS | Location | +| :--- | :--- | +| 14 & 15 & 26 | System Settings > Privacy & Security > Screen & System Audio Recording | +| 13 | System Settings > Privacy & Security > Screen Recording | +| 12 | System Preferences > Security & Privacy > Privacy > Screen Recording | + +Check `GitHub Copilot for Xcode` and restart the app. ## Logs -Logs can be found in `~/Library/Logs/GitHubCopilot/` the most recent log file +Logs can be found in `~/Library/Logs/GitHubCopilot/`. The most recent log file is: ```