Skip to content

Commit d1f7de3

Browse files
committed
Release 0.38.0
1 parent 9788b5c commit d1f7de3

File tree

6 files changed

+66
-50
lines changed

6 files changed

+66
-50
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 0.38.0 - June 30, 2025
9+
### Added
10+
- Support for Claude 4 in Chat.
11+
- Support for Copilot Vision (image attachments).
12+
- Support for remote MCP servers.
13+
14+
### Changed
15+
- Automatically suggests a title for conversations created in agent mode.
16+
- Improved restoration of MCP tool status after Copilot restarts.
17+
- Reduced duplication of MCP server instances.
18+
19+
### Fixed
20+
- Switching accounts now correctly refreshes the auth token and models.
21+
- Fixed file create/edit issues in agent mode.
22+
823
## 0.37.0 - June 18, 2025
924
### Added
1025
- **Advanced** settings: Added option to configure **Custom Instructions** for GitHub Copilot during chat sessions.

Core/Sources/ChatService/ToolCalls/CreateFileTool.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,26 @@ public class CreateFileTool: ICopilotTool {
2525

2626
guard !FileManager.default.fileExists(atPath: filePath)
2727
else {
28+
Logger.client.info("CreateFileTool: File already exists at \(filePath)")
2829
completeResponse(request, status: .error, response: "File already exists at \(filePath)", completion: completion)
2930
return true
3031
}
3132

3233
do {
34+
// Create intermediate directories if they don't exist
35+
let parentDirectory = fileURL.deletingLastPathComponent()
36+
try FileManager.default.createDirectory(at: parentDirectory, withIntermediateDirectories: true, attributes: nil)
3337
try content.write(to: fileURL, atomically: true, encoding: .utf8)
3438
} catch {
39+
Logger.client.error("CreateFileTool: Failed to write content to file at \(filePath): \(error)")
3540
completeResponse(request, status: .error, response: "Failed to write content to file: \(error)", completion: completion)
3641
return true
3742
}
3843

3944
guard FileManager.default.fileExists(atPath: filePath),
4045
let writtenContent = try? String(contentsOf: fileURL, encoding: .utf8)
4146
else {
47+
Logger.client.info("CreateFileTool: Failed to verify file creation at \(filePath)")
4248
completeResponse(request, status: .error, response: "Failed to verify file creation.", completion: completion)
4349
return true
4450
}

Core/Sources/SuggestionWidget/ChatPanelWindow.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ final class ChatPanelWindow: NSWindow {
8282
}
8383

8484
private func setInitialFrame() {
85-
let frame = UpdateLocationStrategy.getChatPanelFrame(isAttachedToXcodeEnabled: false)
85+
let frame = UpdateLocationStrategy.getChatPanelFrame()
8686
setFrame(frame, display: false, animate: true)
8787
}
8888

Core/Sources/SuggestionWidget/WidgetPositionStrategy.swift

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -320,46 +320,38 @@ enum UpdateLocationStrategy {
320320
return selectionFrame
321321
}
322322

323-
static func getChatPanelFrame(
324-
isAttachedToXcodeEnabled: Bool = false,
325-
xcodeApp: XcodeAppInstanceInspector? = nil
326-
) -> CGRect {
327-
let screen = NSScreen.main ?? NSScreen.screens.first!
328-
return getChatPanelFrame(screen, isAttachedToXcodeEnabled: isAttachedToXcodeEnabled, xcodeApp: xcodeApp)
329-
}
330-
331-
static func getChatPanelFrame(
332-
_ screen: NSScreen,
333-
isAttachedToXcodeEnabled: Bool = false,
334-
xcodeApp: XcodeAppInstanceInspector? = nil
335-
) -> CGRect {
323+
static func getChatPanelFrame(_ screen: NSScreen? = nil) -> CGRect {
324+
let screen = screen ?? NSScreen.main ?? NSScreen.screens.first!
325+
336326
let visibleScreenFrame = screen.visibleFrame
337327

338328
// Default Frame
339-
var width = min(Style.panelWidth, visibleScreenFrame.width * 0.3)
340-
var height = visibleScreenFrame.height
341-
var x = visibleScreenFrame.maxX - width
342-
var y = visibleScreenFrame.minY
329+
let width = min(Style.panelWidth, visibleScreenFrame.width * 0.3)
330+
let height = visibleScreenFrame.height
331+
let x = visibleScreenFrame.maxX - width
332+
let y = visibleScreenFrame.minY
343333

344-
if isAttachedToXcodeEnabled,
345-
let latestActiveXcode = xcodeApp ?? XcodeInspector.shared.latestActiveXcode,
346-
let xcodeWindow = latestActiveXcode.appElement.focusedWindow,
347-
let xcodeScreen = latestActiveXcode.appScreen,
348-
let xcodeRect = xcodeWindow.rect,
349-
let mainDisplayScreen = NSScreen.screens.first(where: { $0.frame.origin == .zero }) // The main display should exist
350-
{
351-
let minWidth = Style.minChatPanelWidth
352-
let visibleXcodeScreenFrame = xcodeScreen.visibleFrame
353-
354-
width = max(visibleXcodeScreenFrame.maxX - xcodeRect.maxX, minWidth)
355-
height = xcodeRect.height
356-
x = visibleXcodeScreenFrame.maxX - width
357-
358-
// AXUIElement coordinates: Y=0 at top-left
359-
// NSWindow coordinates: Y=0 at bottom-left
360-
y = mainDisplayScreen.frame.maxY - xcodeRect.maxY + mainDisplayScreen.frame.minY
334+
return CGRect(x: x, y: y, width: width, height: height)
335+
}
336+
337+
static func getAttachedChatPanelFrame(_ screen: NSScreen, workspaceWindowElement: AXUIElement) -> CGRect {
338+
guard let xcodeScreen = workspaceWindowElement.maxIntersectionScreen,
339+
let xcodeRect = workspaceWindowElement.rect,
340+
let mainDisplayScreen = NSScreen.screens.first(where: { $0.frame.origin == .zero })
341+
else {
342+
return getChatPanelFrame()
361343
}
362344

345+
let minWidth = Style.minChatPanelWidth
346+
let visibleXcodeScreenFrame = xcodeScreen.visibleFrame
347+
348+
let width = max(visibleXcodeScreenFrame.maxX - xcodeRect.maxX, minWidth)
349+
let height = xcodeRect.height
350+
let x = visibleXcodeScreenFrame.maxX - width
351+
352+
// AXUIElement coordinates: Y=0 at top-left
353+
// NSWindow coordinates: Y=0 at bottom-left
354+
let y = mainDisplayScreen.frame.maxY - xcodeRect.maxY + mainDisplayScreen.frame.minY
363355

364356
return CGRect(x: x, y: y, width: width, height: height)
365357
}

Core/Sources/SuggestionWidget/WidgetWindowsController.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ extension WidgetWindowsController {
349349

350350
// Generate a default location when no workspace is opened
351351
private func generateDefaultLocation() -> WidgetLocation {
352-
let chatPanelFrame = UpdateLocationStrategy.getChatPanelFrame(isAttachedToXcodeEnabled: false)
352+
let chatPanelFrame = UpdateLocationStrategy.getChatPanelFrame()
353353

354354
return WidgetLocation(
355355
widgetFrame: .zero,
@@ -459,7 +459,8 @@ extension WidgetWindowsController {
459459
guard let currentXcodeApp = (await currentXcodeApp),
460460
let currentFocusedWindow = currentXcodeApp.appElement.focusedWindow,
461461
let currentXcodeScreen = currentXcodeApp.appScreen,
462-
let currentXcodeRect = currentFocusedWindow.rect
462+
let currentXcodeRect = currentFocusedWindow.rect,
463+
let notif = notif
463464
else { return }
464465

465466
if let previousXcodeApp = (await previousXcodeApp),
@@ -472,16 +473,13 @@ extension WidgetWindowsController {
472473
let isAttachedToXcodeEnabled = UserDefaults.shared.value(for: \.autoAttachChatToXcode)
473474
guard isAttachedToXcodeEnabled else { return }
474475

475-
if let notif = notif {
476-
let dialogIdentifiers = ["open_quickly", "alert"]
477-
if dialogIdentifiers.contains(notif.element.identifier) { return }
478-
}
476+
guard notif.element.isXcodeWorkspaceWindow else { return }
479477

480478
let state = store.withState { $0 }
481479
if state.chatPanelState.isPanelDisplayed && !windows.chatPanelWindow.isWindowHidden {
482-
var frame = UpdateLocationStrategy.getChatPanelFrame(
483-
isAttachedToXcodeEnabled: true,
484-
xcodeApp: currentXcodeApp
480+
var frame = UpdateLocationStrategy.getAttachedChatPanelFrame(
481+
NSScreen.main ?? NSScreen.screens.first!,
482+
workspaceWindowElement: notif.element
485483
)
486484

487485
let screenMaxX = currentXcodeScreen.visibleFrame.maxX

ReleaseNotes.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
### GitHub Copilot for Xcode 0.37.0
1+
### GitHub Copilot for Xcode 0.38.0
22

33
**🚀 Highlights**
44

5-
* **Advanced** settings: Added option to configure **Custom Instructions** for GitHub Copilot during chat sessions.
6-
* **Advanced** settings: Added option to keep the chat window automatically attached to Xcode.
7-
* Added support for dragging-and-dropping files into the chat panel to provide context.
5+
* Support for Claude 4 in Chat.
6+
* Support for Copilot Vision (image attachments).
7+
* Support for remote MCP servers.
8+
9+
**💪 Improvements**
10+
* Automatically suggests a title for conversations created in agent mode.
11+
* Improved restoration of MCP tool status after Copilot restarts.
12+
* Reduced duplication of MCP server instances.
813

914
**🛠️ Bug Fixes**
1015

11-
* "Add Context" menu didn’t show files in workspaces organized with Xcode’s group feature.
12-
* Chat didn’t respond when the workspace was in a system folder (like Desktop, Downloads, or Documents) and access permission hadn’t been granted.
16+
* Switching accounts now correctly refreshes the auth token and models.
17+
* Fixed file create/edit issues in agent mode.

0 commit comments

Comments
 (0)