Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.35.0 - May 19, 2025
### Added
- Launched Agent Mode. Copilot will automatically use multiple requests to edit files, run terminal commands, and fix errors.
- Introduced Model Context Protocol (MCP) support in Agent Mode, allowing you to configure MCP tools to extend capabilities.

### Changed
- Added a button to enable/disable referencing current file in conversations
- Added an animated progress icon in the response section
- Refined onboarding experience with updated instruction screens and welcome views
- Improved conversation reliability with extended timeout limits for agent requests

### Fixed
- Addressed critical error handling issues in core functionality
- Resolved UI inconsistencies with chat interface padding adjustments
- Implemented custom certificate handling using system environment variables `NODE_EXTRA_CA_CERTS` and `NODE_TLS_REJECT_UNAUTHORIZED`, fixing network access issues

## 0.34.0 - April 29, 2025
### Added
- Added support for new models in Chat: OpenAI GPT-4.1, o3 and o4-mini, Gemini 2.5 Pro
Expand Down
19 changes: 11 additions & 8 deletions Copilot for Xcode/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SharedUIComponents
import UpdateChecker
import XPCShared
import HostAppActivator
import ComposableArchitecture

struct VisualEffect: NSViewRepresentable {
func makeNSView(context: Self.Context) -> NSView { return NSVisualEffectView() }
Expand Down Expand Up @@ -192,14 +193,16 @@ struct CopilotForXcodeApp: App {
}

var body: some Scene {
Settings {
TabContainer()
.frame(minWidth: 800, minHeight: 600)
.background(VisualEffect().ignoresSafeArea())
.environment(\.updateChecker, UpdateChecker(
hostBundle: Bundle.main,
checkerDelegate: AppUpdateCheckerDelegate()
))
WithPerceptionTracking {
Settings {
TabContainer()
.frame(minWidth: 800, minHeight: 600)
.background(VisualEffect().ignoresSafeArea())
.environment(\.updateChecker, UpdateChecker(
hostBundle: Bundle.main,
checkerDelegate: AppUpdateCheckerDelegate()
))
}
}
}
}
Expand Down
21 changes: 14 additions & 7 deletions Core/Sources/ChatService/ChatService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
public func stopReceivingMessage() async {
if let activeRequestId = activeRequestId {
do {
try await conversationProvider?.stopReceivingMessage(activeRequestId)
try await conversationProvider?.stopReceivingMessage(activeRequestId, workspaceURL: getWorkspaceURL())
} catch {
print("Failed to cancel ongoing request with WDT: \(activeRequestId)")
}
Expand All @@ -393,7 +393,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
await memory.clearHistory()
if let activeRequestId = activeRequestId {
do {
try await conversationProvider?.stopReceivingMessage(activeRequestId)
try await conversationProvider?.stopReceivingMessage(activeRequestId, workspaceURL: getWorkspaceURL())
} catch {
print("Failed to cancel ongoing request with WDT: \(activeRequestId)")
}
Expand Down Expand Up @@ -491,13 +491,20 @@ public final class ChatService: ChatServiceType, ObservableObject {
try await send(UUID().uuidString, content: templateProcessor.process(sendingMessageImmediately), skillSet: [], references: [])
}
}


public func getWorkspaceURL() -> URL? {
guard !chatTabInfo.workspacePath.isEmpty else {
return nil
}
return URL(fileURLWithPath: chatTabInfo.workspacePath)
}

public func upvote(_ id: String, _ rating: ConversationRating) async {
try? await conversationProvider?.rateConversation(turnId: id, rating: rating)
try? await conversationProvider?.rateConversation(turnId: id, rating: rating, workspaceURL: getWorkspaceURL())
}

public func downvote(_ id: String, _ rating: ConversationRating) async {
try? await conversationProvider?.rateConversation(turnId: id, rating: rating)
try? await conversationProvider?.rateConversation(turnId: id, rating: rating, workspaceURL: getWorkspaceURL())
}

public func copyCode(_ id: String) async {
Expand Down Expand Up @@ -725,7 +732,7 @@ public final class ChatService: ChatServiceType, ObservableObject {

do {
if let conversationId = conversationId {
try await conversationProvider?.createTurn(with: conversationId, request: request)
try await conversationProvider?.createTurn(with: conversationId, request: request, workspaceURL: getWorkspaceURL())
} else {
var requestWithTurns = request

Expand All @@ -738,7 +745,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
requestWithTurns.turns = turns
}

try await conversationProvider?.createConversation(requestWithTurns)
try await conversationProvider?.createConversation(requestWithTurns, workspaceURL: getWorkspaceURL())
}
} catch {
resetOngoingRequest()
Expand Down
2 changes: 1 addition & 1 deletion Core/Sources/ConversationTab/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ struct Chat {
var fileEditMap: OrderedDictionary<URL, FileEdit> = [:]
var diffViewerController: DiffViewWindowController? = nil
var isAgentMode: Bool = AppState.shared.isAgentModeEnabled()
var workspaceURL: URL? = nil
enum Field: String, Hashable {
case textField
case fileSearchBar
Expand Down Expand Up @@ -564,4 +565,3 @@ private actor TimedDebounceFunction {
await block()
}
}

2 changes: 1 addition & 1 deletion Core/Sources/ConversationTab/ChatPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ struct ChatPanelInputArea: View {
}
)
.onAppear() {
allFiles = ContextUtils.getFilesInActiveWorkspace()
allFiles = ContextUtils.getFilesInActiveWorkspace(workspaceURL: chat.workspaceURL)
}
}

Expand Down
6 changes: 5 additions & 1 deletion Core/Sources/ConversationTab/ContextUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import Workspace

public struct ContextUtils {

public static func getFilesInActiveWorkspace() -> [FileReference] {
public static func getFilesInActiveWorkspace(workspaceURL: URL?) -> [FileReference] {
if let workspaceURL = workspaceURL, let info = WorkspaceFile.getWorkspaceInfo(workspaceURL: workspaceURL) {
return WorkspaceFile.getFilesInActiveWorkspace(workspaceURL: info.workspaceURL, workspaceRootURL: info.projectURL)
}

guard let workspaceURL = XcodeInspector.shared.realtimeActiveWorkspaceURL,
let workspaceRootURL = XcodeInspector.shared.realtimeActiveProjectURL else {
return []
Expand Down
4 changes: 2 additions & 2 deletions Core/Sources/ConversationTab/ConversationTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public class ConversationTab: ChatTab {

let service = ChatService.service(for: info)
self.service = service
chat = .init(initialState: .init(), reducer: { Chat(service: service) })
chat = .init(initialState: .init(workspaceURL: service.getWorkspaceURL()), reducer: { Chat(service: service) })
super.init(store: store)

// Start to observe changes of Chat Message
Expand All @@ -128,7 +128,7 @@ public class ConversationTab: ChatTab {
@MainActor
public init(service: ChatService, store: StoreOf<ChatTabItem>, with chatTabInfo: ChatTabInfo) {
self.service = service
chat = .init(initialState: .init(), reducer: { Chat(service: service) })
chat = .init(initialState: .init(workspaceURL: service.getWorkspaceURL()), reducer: { Chat(service: service) })
super.init(store: store)
}

Expand Down
37 changes: 24 additions & 13 deletions Core/Sources/HostApp/MCPConfigView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SwiftUI
import Toast
import ConversationServiceProvider
import GitHubCopilotService
import ComposableArchitecture

struct MCPConfigView: View {
@State private var mcpConfig: String = ""
Expand All @@ -16,20 +17,24 @@ struct MCPConfigView: View {
@State private var fileMonitorTask: Task<Void, Error>? = nil
@Environment(\.colorScheme) var colorScheme

private static var lastSyncTimestamp: Date? = nil

var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
MCPIntroView()
MCPToolsListView()
}
.padding(20)
.onAppear {
setupConfigFilePath()
startMonitoringConfigFile()
refreshConfiguration(())
}
.onDisappear {
stopMonitoringConfigFile()
WithPerceptionTracking {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
MCPIntroView()
MCPToolsListView()
}
.padding(20)
.onAppear {
setupConfigFilePath()
startMonitoringConfigFile()
refreshConfiguration(())
}
.onDisappear {
stopMonitoringConfigFile()
}
}
}
}
Expand Down Expand Up @@ -145,6 +150,12 @@ struct MCPConfigView: View {
}

func refreshConfiguration(_: Any) {
if MCPConfigView.lastSyncTimestamp == lastModificationDate {
return
}

MCPConfigView.lastSyncTimestamp = lastModificationDate

let fileURL = URL(fileURLWithPath: configFilePath)
if let jsonString = readAndValidateJSON(from: fileURL) {
UserDefaults.shared.set(jsonString, for: \.gitHubCopilotMCPConfig)
Expand Down
7 changes: 5 additions & 2 deletions Core/Sources/HostApp/MCPSettings/MCPIntroView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ struct MCPIntroView: View {
"my-mcp-server": {
"type": "stdio",
"command": "my-command",
"args": []
"args": [],
"env": {
"TOKEN": "my_token"
}
}
}
}
Expand Down Expand Up @@ -75,7 +78,7 @@ struct MCPIntroView: View {
.overlay(
RoundedRectangle(cornerRadius: 4)
.inset(by: 0.5)
.stroke(Color(red: 0.9, green: 0.9, blue: 0.9), lineWidth: 1)
.stroke(Color("GroupBoxStrokeColor"), lineWidth: 1)
)
}

Expand Down
Loading