From deff2408c7a81f97587c244de9fd7b030e415e31 Mon Sep 17 00:00:00 2001 From: Chuck Jones Date: Thu, 31 Jul 2025 00:27:24 -0400 Subject: [PATCH] Release 0.40.0 --- .gitmodules | 3 + CHANGELOG.md | 4 + .../ModelPicker/ChatModePicker.swift | 106 ++++++++++++------ Core/Sources/HostApp/TabContainer.swift | 38 ++++++- Core/Sources/Service/XPCService.swift | 9 ++ HelloGitHub | 1 + ReleaseNotes.md | 3 +- Server/package-lock.json | 8 +- Server/package.json | 2 +- .../Services/FeatureFlagNotifier.swift | 21 +++- .../XPCShared/XPCExtensionService.swift | 20 ++++ .../XPCShared/XPCServiceProtocol.swift | 2 + 12 files changed, 168 insertions(+), 49 deletions(-) create mode 100644 .gitmodules create mode 160000 HelloGitHub diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..a0b1c635 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "HelloGitHub"] + path = HelloGitHub + url = https://github.com/521xueweihan/HelloGitHub diff --git a/CHANGELOG.md b/CHANGELOG.md index 414c8e6d..cce07a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.40.0 - July 24, 2025 +### Added +- Support disabling Agent mode when it's disabled by policy. + ## 0.39.0 - July 23, 2025 ### Fixed - Performance: Fixed a freezing issue in 'Add Context' view when opening large projects. diff --git a/Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift b/Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift index 5e61b4c0..559a6d9d 100644 --- a/Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift +++ b/Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift @@ -1,63 +1,95 @@ import SwiftUI import Persist import ConversationServiceProvider +import GitHubCopilotService +import Combine public extension Notification.Name { static let gitHubCopilotChatModeDidChange = Notification .Name("com.github.CopilotForXcode.ChatModeDidChange") } +public enum ChatMode: String { + case Ask = "Ask" + case Agent = "Agent" +} + public struct ChatModePicker: View { @Binding var chatMode: String @Environment(\.colorScheme) var colorScheme + @State var isAgentModeFFEnabled: Bool + @State private var cancellables = Set() var onScopeChange: (PromptTemplateScope) -> Void public init(chatMode: Binding, onScopeChange: @escaping (PromptTemplateScope) -> Void = { _ in }) { self._chatMode = chatMode self.onScopeChange = onScopeChange + self.isAgentModeFFEnabled = FeatureFlagNotifierImpl.shared.featureFlags.agent_mode != false + } + + private func setChatMode(mode: ChatMode) { + chatMode = mode.rawValue + AppState.shared.setSelectedChatMode(mode.rawValue) + onScopeChange(mode == .Ask ? .chatPanel : .agentPanel) + NotificationCenter.default.post( + name: .gitHubCopilotChatModeDidChange, + object: nil + ) + } + + private func subscribeToFeatureFlagsDidChangeEvent() { + FeatureFlagNotifierImpl.shared.featureFlagsDidChange.sink(receiveValue: { (featureFlags) in + isAgentModeFFEnabled = featureFlags.agent_mode ?? true + }) + .store(in: &cancellables) } public var body: some View { - HStack(spacing: -1) { - ModeButton( - title: "Ask", - isSelected: chatMode == "Ask", - activeBackground: colorScheme == .dark ? Color.white.opacity(0.25) : Color.white, - activeTextColor: Color.primary, - inactiveTextColor: Color.primary.opacity(0.5), - action: { - chatMode = "Ask" - AppState.shared.setSelectedChatMode("Ask") - onScopeChange(.chatPanel) - NotificationCenter.default.post( - name: .gitHubCopilotChatModeDidChange, - object: nil + VStack { + if isAgentModeFFEnabled { + HStack(spacing: -1) { + ModeButton( + title: "Ask", + isSelected: chatMode == "Ask", + activeBackground: colorScheme == .dark ? Color.white.opacity(0.25) : Color.white, + activeTextColor: Color.primary, + inactiveTextColor: Color.primary.opacity(0.5), + action: { + setChatMode(mode: .Ask) + } ) - } - ) - - ModeButton( - title: "Agent", - isSelected: chatMode == "Agent", - activeBackground: Color.blue, - activeTextColor: Color.white, - inactiveTextColor: Color.primary.opacity(0.5), - action: { - chatMode = "Agent" - AppState.shared.setSelectedChatMode("Agent") - onScopeChange(.agentPanel) - NotificationCenter.default.post( - name: .gitHubCopilotChatModeDidChange, - object: nil + + ModeButton( + title: "Agent", + isSelected: chatMode == "Agent", + activeBackground: Color.blue, + activeTextColor: Color.white, + inactiveTextColor: Color.primary.opacity(0.5), + action: { + setChatMode(mode: .Agent) + } ) } - ) + .padding(1) + .frame(height: 20, alignment: .topLeading) + .background(.primary.opacity(0.1)) + .cornerRadius(5) + .padding(4) + .help("Set Mode") + } else { + EmptyView() + } + } + .task { + subscribeToFeatureFlagsDidChangeEvent() + if !isAgentModeFFEnabled { + setChatMode(mode: .Ask) + } + } + .onChange(of: isAgentModeFFEnabled) { newAgentModeFFEnabled in + if !newAgentModeFFEnabled { + setChatMode(mode: .Ask) + } } - .padding(1) - .frame(height: 20, alignment: .topLeading) - .background(.primary.opacity(0.1)) - .cornerRadius(5) - .padding(4) - .help("Set Mode") } } diff --git a/Core/Sources/HostApp/TabContainer.swift b/Core/Sources/HostApp/TabContainer.swift index 3a4bb494..546b0d0a 100644 --- a/Core/Sources/HostApp/TabContainer.swift +++ b/Core/Sources/HostApp/TabContainer.swift @@ -5,6 +5,9 @@ import LaunchAgentManager import SwiftUI import Toast import UpdateChecker +import Client +import Logger +import Combine @MainActor public let hostAppStore: StoreOf = .init(initialState: .init(), reducer: { HostApp() }) @@ -13,6 +16,7 @@ public struct TabContainer: View { let store: StoreOf @ObservedObject var toastController: ToastController @State private var tabBarItems = [TabBarItem]() + @State private var isAgentModeFFEnabled = true @Binding var tag: Int public init() { @@ -32,6 +36,19 @@ public struct TabContainer: View { set: { store.send(.setActiveTab($0)) } ) } + + private func updateAgentModeFeatureFlag() async { + do { + let service = try getService() + let featureFlags = try await service.getCopilotFeatureFlags() + isAgentModeFFEnabled = featureFlags?.agent_mode ?? true + if hostAppStore.activeTabIndex == 2 && !isAgentModeFFEnabled { + hostAppStore.send(.setActiveTab(0)) + } + } catch { + Logger.client.error("Failed to get copilot feature flags: \(error)") + } + } public var body: some View { WithPerceptionTracking { @@ -51,11 +68,13 @@ public struct TabContainer: View { title: "Advanced", image: "gearshape.2.fill" ) - MCPConfigView().tabBarItem( - tag: 2, - title: "MCP", - image: "wrench.and.screwdriver.fill" - ) + if isAgentModeFFEnabled { + MCPConfigView().tabBarItem( + tag: 2, + title: "MCP", + image: "wrench.and.screwdriver.fill" + ) + } } .environment(\.tabBarTabTag, tag) .frame(minHeight: 400) @@ -70,7 +89,16 @@ public struct TabContainer: View { } .onAppear { store.send(.appear) + Task { + await updateAgentModeFeatureFlag() + } } + .onReceive(DistributedNotificationCenter.default() + .publisher(for: .gitHubCopilotFeatureFlagsDidChange)) { _ in + Task { + await updateAgentModeFeatureFlag() + } + } } } } diff --git a/Core/Sources/Service/XPCService.swift b/Core/Sources/Service/XPCService.swift index 84ce30e5..0297224a 100644 --- a/Core/Sources/Service/XPCService.swift +++ b/Core/Sources/Service/XPCService.swift @@ -308,6 +308,15 @@ public class XPCService: NSObject, XPCServiceProtocol { } } + // MARK: - FeatureFlags + public func getCopilotFeatureFlags( + withReply reply: @escaping (Data?) -> Void + ) { + let featureFlags = FeatureFlagNotifierImpl.shared.featureFlags + let data = try? JSONEncoder().encode(featureFlags) + reply(data) + } + // MARK: - Auth public func signOutAllGitHubCopilotService() { Task { @MainActor in diff --git a/HelloGitHub b/HelloGitHub new file mode 160000 index 00000000..a1c5cbd1 --- /dev/null +++ b/HelloGitHub @@ -0,0 +1 @@ +Subproject commit a1c5cbd1b7e07bdbf041a46a92120bd0618501a8 diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 1b6907ec..75211dae 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,8 +1,9 @@ -### GitHub Copilot for Xcode 0.39.0 +### GitHub Copilot for Xcode 0.40.0 **🚀 Highlights** * Performance: Fixed a freezing issue in 'Add Context' view when opening large projects. +* Support disabling Agent mode when it's disabled by policy. **🛠️ Bug Fixes** diff --git a/Server/package-lock.json b/Server/package-lock.json index f8500448..99e43b7c 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -8,7 +8,7 @@ "name": "@github/copilot-xcode", "version": "0.0.1", "dependencies": { - "@github/copilot-language-server": "^1.347.0", + "@github/copilot-language-server": "^1.348.0", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", "monaco-editor": "0.52.2" @@ -36,9 +36,9 @@ } }, "node_modules/@github/copilot-language-server": { - "version": "1.347.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.347.0.tgz", - "integrity": "sha512-ygDQhnRkoKD+9jIUNTRrB9F0hP6N6jJUy+TSFtSsge5lNC2P/ntWyCFkEcrVnXcvewG7dHj8U9RRAExEeg8FgQ==", + "version": "1.348.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.348.0.tgz", + "integrity": "sha512-CV1+hU9I29GXrZKwdRj2x7ur47IAoqa56FWwnkI/Cvs0BdTTrLigJlOseeFCQ1bglnIyr6ZLFCduBahDtqR1AQ==", "license": "https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features", "dependencies": { "vscode-languageserver-protocol": "^3.17.5" diff --git a/Server/package.json b/Server/package.json index 46892e24..4c37672f 100644 --- a/Server/package.json +++ b/Server/package.json @@ -7,7 +7,7 @@ "build": "webpack" }, "dependencies": { - "@github/copilot-language-server": "^1.347.0", + "@github/copilot-language-server": "^1.348.0", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", "monaco-editor": "0.52.2" diff --git a/Tool/Sources/GitHubCopilotService/Services/FeatureFlagNotifier.swift b/Tool/Sources/GitHubCopilotService/Services/FeatureFlagNotifier.swift index 2f0949c1..3061a48e 100644 --- a/Tool/Sources/GitHubCopilotService/Services/FeatureFlagNotifier.swift +++ b/Tool/Sources/GitHubCopilotService/Services/FeatureFlagNotifier.swift @@ -1,11 +1,29 @@ import Combine import SwiftUI +public extension Notification.Name { + static let gitHubCopilotFeatureFlagsDidChange = Notification + .Name("com.github.CopilotForXcode.CopilotFeatureFlagsDidChange") +} + +public enum ExperimentValue: Hashable, Codable { + case string(String) + case number(Double) + case boolean(Bool) + case stringArray([String]) +} + +public typealias ActiveExperimentForFeatureFlags = [String: ExperimentValue] + public struct FeatureFlags: Hashable, Codable { public var rt: Bool public var sn: Bool public var chat: Bool + public var ic: Bool + public var pc: Bool public var xc: Bool? + public var ae: ActiveExperimentForFeatureFlags + public var agent_mode: Bool? } public protocol FeatureFlagNotifier { @@ -19,7 +37,7 @@ public class FeatureFlagNotifierImpl: FeatureFlagNotifier { public static let shared = FeatureFlagNotifierImpl() public var featureFlagsDidChange: PassthroughSubject - init(featureFlags: FeatureFlags = FeatureFlags(rt: false, sn: false, chat: true), + init(featureFlags: FeatureFlags = FeatureFlags(rt: false, sn: false, chat: true, ic: true, pc: true, ae: [:]), featureFlagsDidChange: PassthroughSubject = PassthroughSubject()) { self.featureFlags = featureFlags self.featureFlagsDidChange = featureFlagsDidChange @@ -31,6 +49,7 @@ public class FeatureFlagNotifierImpl: FeatureFlagNotifier { DispatchQueue.main.async { [weak self] in guard let self else { return } self.featureFlagsDidChange.send(self.featureFlags) + DistributedNotificationCenter.default().post(name: .gitHubCopilotFeatureFlagsDidChange, object: nil) } } } diff --git a/Tool/Sources/XPCShared/XPCExtensionService.swift b/Tool/Sources/XPCShared/XPCExtensionService.swift index bcf82c19..5b1d7953 100644 --- a/Tool/Sources/XPCShared/XPCExtensionService.swift +++ b/Tool/Sources/XPCShared/XPCExtensionService.swift @@ -392,6 +392,26 @@ extension XPCExtensionService { } } + @XPCServiceActor + public func getCopilotFeatureFlags() async throws -> FeatureFlags? { + return try await withXPCServiceConnected { + service, continuation in + service.getCopilotFeatureFlags { data in + guard let data else { + continuation.resume(nil) + return + } + + do { + let tools = try JSONDecoder().decode(FeatureFlags.self, from: data) + continuation.resume(tools) + } catch { + continuation.reject(error) + } + } + } + } + @XPCServiceActor public func signOutAllGitHubCopilotService() async throws { return try await withXPCServiceConnected { diff --git a/Tool/Sources/XPCShared/XPCServiceProtocol.swift b/Tool/Sources/XPCShared/XPCServiceProtocol.swift index dbc64f4d..5552ea38 100644 --- a/Tool/Sources/XPCShared/XPCServiceProtocol.swift +++ b/Tool/Sources/XPCShared/XPCServiceProtocol.swift @@ -24,6 +24,8 @@ public protocol XPCServiceProtocol { func getXcodeInspectorData(withReply reply: @escaping (Data?, Error?) -> Void) func getAvailableMCPServerToolsCollections(withReply reply: @escaping (Data?) -> Void) func updateMCPServerToolsStatus(tools: Data) + + func getCopilotFeatureFlags(withReply reply: @escaping (Data?) -> Void) func signOutAllGitHubCopilotService() func getXPCServiceAuthStatus(withReply reply: @escaping (Data?) -> Void)