From a3d1852f45372ab185604c01e5d4f788382b41e3 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Thu, 22 Oct 2026 08:01:59 -0700 Subject: [PATCH] Trigger folder access prompts in permission checks; add lead welcome --- api/openapi.yaml | 2 +- docs/operations.md | 2 +- hands/Sources/byre-hands/Check.swift | 19 +- internal/agents/hands.go | 4 +- internal/api/api.gen.go | 275 ++++++++++++++------------- mac/Info.plist | 6 + mac/Sources/Byre/API.swift | 3 +- mac/Sources/Byre/Chat.swift | 28 +++ mac/Sources/Byre/Dialogs.swift | 3 +- mac/Sources/Byre/MarkdownView.swift | 5 + mac/Sources/Byre/Phone.swift | 23 ++- mac/Sources/Byre/Server.swift | 4 +- mac/Sources/Byre/Settings.swift | 2 +- 13 files changed, 227 insertions(+), 149 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index c0c1d2d..8c4c8ac 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1367,7 +1367,7 @@ paths: /api/admin/permissions: post: operationId: checkPermissions - description: Tests the service's macOS privacy grants by running byre-hands' check, which asks macOS for any grant not yet decided. Waits while macOS shows an Automation prompt, up to 50 seconds. + description: Tests the service's macOS privacy grants by running byre-hands' check, which asks macOS for any grant not yet decided. Waits while macOS shows its prompts (Automation, and access to the Desktop, Documents, Downloads and iCloud Drive folders), up to 2 minutes. responses: "200": description: One result per grant. diff --git a/docs/operations.md b/docs/operations.md index 9a956f1..5fe1b8a 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -120,7 +120,7 @@ byre never signs with a key on the machine it runs on: every git command it runs - **Orion.** Orion ignores key events that carry only a Unicode string, so typing posts each ASCII character as its key code. Some apps list `confirm` but ignore it, Orion's address bar among them; send Return instead. `do JavaScript` needs Orion's Develop → "Allow JavaScript from Apple Events". - **Chrome.** Chrome refuses remote debugging on its default profile, so byre's Chrome is its own profile (`~/Library/Application Support/byre/chrome`), a separate Chrome beside any the human runs, and it keeps its logins: the human signs in there once. Chrome comes from `brew install --cask google-chrome`; macOS's "downloaded from the Internet" prompt on its first launch must be answered Open (or `osascript -e 'tell application "System Events" to tell process "CoreServicesUIAgent" to click button "Open" of window 1'`), and until it is, Chrome cannot accept debugging commands. - **Chrome's trust boundary.** Use Chrome's loopback debugging port with byre's separate persistent profile and Chrome's default browser-origin checks; never add `--remote-allow-origins=*`. Other local OS accounts can reach that debugger and control its logged-in sessions. This is an accepted risk on the personal Macs byre runs on, as is access to the app's local preview forwards; do not add account-isolating brokers for these listeners. Chrome must be launched with `--remote-debugging-port=0` so it records its port and browser WebSocket path in the profile’s `DevToolsActivePort` file. After switching from the pipe or fixed-port transport, quit the old byre-profile Chrome once so the next tool call can start it with that discovery file. The saved profile and logins stay in place. -- **Permissions.** Agents' processes run as byre's descendants, so macOS privacy controls (TCC) hold byre responsible for them: the Screen Recording, Accessibility and Automation grants in System Settings → Privacy & Security are byre's, made by clicking at the Mac's screen, and each app scripted for the first time asks for its own Automation grant. Without Accessibility an untrusted process just sees no windows and no menu bar. Accessibility can list byre switched off; only its switch being on counts. TCC keys grants on the signing identifier and team, and every build is signed with Developer ID, so grants carry over from build to build. On a client Mac the grants belong to byre's app; `NSAppleEventsUsageDescription` in `mac/Info.plist` is what lets macOS ask for the Automation grant. `byre-hands check` prints the grants of the process that runs it; the Server and Settings tabs run it. +- **Permissions.** Agents' processes run as byre's descendants, so macOS privacy controls (TCC) hold byre responsible for them: the Screen Recording, Accessibility and Automation grants in System Settings → Privacy & Security are byre's, made by clicking at the Mac's screen, and each app scripted for the first time asks for its own Automation grant. Without Accessibility an untrusted process just sees no windows and no menu bar. Accessibility can list byre switched off; only its switch being on counts. TCC keys grants on the signing identifier and team, and every build is signed with Developer ID, so grants carry over from build to build. On a client Mac the grants belong to byre's app; `NSAppleEventsUsageDescription` in `mac/Info.plist` is what lets macOS ask for the Automation grant. The Desktop, Documents, Downloads and iCloud Drive folders each ask for their own Files & Folders grant the first time a process byre is responsible for reads them. `byre-hands check` prints the grants of the process that runs it, listing each of those folders so their prompts come up during the check rather than mid-turn; the Server and Settings tabs run it. ## Terminals diff --git a/hands/Sources/byre-hands/Check.swift b/hands/Sources/byre-hands/Check.swift index 2635bbb..4ce2fcd 100644 --- a/hands/Sources/byre-hands/Check.swift +++ b/hands/Sources/byre-hands/Check.swift @@ -56,9 +56,26 @@ func checkOrion() -> [String: String] { } } +func checkFolder(_ name: String, _ path: String) -> [String: String] { + let dir = (NSHomeDirectory() as NSString).appendingPathComponent(path) + if !FileManager.default.fileExists(atPath: dir) { + return permission(name, "unchecked", "\(dir) doesn't exist.", "FilesAndFolders") + } + do { + let entries = try FileManager.default.contentsOfDirectory(atPath: dir) + return permission(name, "granted", "Listed \(dir) (entries: \(entries.count)).", "FilesAndFolders") + } catch { + return permission(name, "missing", "Listing \(dir) failed (\(error.localizedDescription)). Switch \(name) on under byre in Files & Folders.", "FilesAndFolders") + } +} + func runCheck() { Task.detached { - let rows = [await checkScreenRecording(), checkAccessibility(), checkOrion()] + let rows = [ + await checkScreenRecording(), checkAccessibility(), checkOrion(), + checkFolder("Desktop Folder", "Desktop"), checkFolder("Documents Folder", "Documents"), + checkFolder("Downloads Folder", "Downloads"), checkFolder("iCloud Drive", "Library/Mobile Documents/com~apple~CloudDocs"), + ] let out = try! JSONSerialization.data(withJSONObject: rows, options: .withoutEscapingSlashes) FileHandle.standardOutput.write(out + Data("\n".utf8)) exit(0) diff --git a/internal/agents/hands.go b/internal/agents/hands.go index 3cc761b..5c5ce23 100644 --- a/internal/agents/hands.go +++ b/internal/agents/hands.go @@ -70,14 +70,14 @@ func (m *Manager) CheckPermissions(ctx context.Context) ([]api.Permission, error if err != nil { return nil, err } - ctx, cancel := context.WithTimeout(ctx, 50*time.Second) + ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) defer cancel() cmd := exec.CommandContext(ctx, bin, "check") stderr := strings.Builder{} cmd.Stderr = &stderr out, err := cmd.Output() if ctx.Err() == context.DeadlineExceeded { - return nil, fmt.Errorf("byre-hands check didn't finish within 50s; answer macOS's prompt on the server's Mac and check again") + return nil, fmt.Errorf("byre-hands check didn't finish within 2 minutes; answer macOS's prompts on the server's Mac and check again") } if err != nil { return nil, fmt.Errorf("byre-hands check: %w: %s", err, strings.TrimSpace(stderr.String())) diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 6a2a54a..a99829d 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -5007,143 +5007,144 @@ var swaggerSpec = []string{ "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", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/mac/Info.plist b/mac/Info.plist index bba3d31..62a2249 100644 --- a/mac/Info.plist +++ b/mac/Info.plist @@ -41,5 +41,11 @@ NSAppleEventsUsageDescription byre's agents drive Orion on this Mac through AppleScript when a task asks them to. + NSDesktopFolderUsageDescription + byre's agents read and write files on your Desktop when a task asks them to. + NSDocumentsFolderUsageDescription + byre's agents read and write files in Documents when a task asks them to. + NSDownloadsFolderUsageDescription + byre's agents read and write files in Downloads when a task asks them to. diff --git a/mac/Sources/Byre/API.swift b/mac/Sources/Byre/API.swift index 68bcbb9..410c314 100644 --- a/mac/Sources/Byre/API.swift +++ b/mac/Sources/Byre/API.swift @@ -112,9 +112,10 @@ final class API: Sendable { let session: URLSession let client: Client - init(origin: URL) { + init(origin: URL, timeout: TimeInterval = 60) { self.origin = origin let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = timeout config.httpAdditionalHeaders = [ "Byre-Build": Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String, "Byre-Client": clientID, diff --git a/mac/Sources/Byre/Chat.swift b/mac/Sources/Byre/Chat.swift index 3161b0f..36e35fc 100644 --- a/mac/Sources/Byre/Chat.swift +++ b/mac/Sources/Byre/Chat.swift @@ -6,6 +6,26 @@ enum ChatKind: Equatable { case task(String?) } +#if os(macOS) +private let leadPlaces = "Tasks open on the right; [switch between them](byre-app://switcher) with ⌘K. Their questions, finished work and reviews wait for you there." +private let leadSettings = "pick the default model for new tasks, turn on notifications, and check this Mac's permissions so agents can see and control it and read your Desktop, Documents and Downloads. The [Server](byre-app://tab?name=server) tab checks the same for the server's agents." +#else +private let leadPlaces = "Tasks live in the Tasks tab, with the ones waiting on you marked; [find or start one](byre-app://switcher) any time." +private let leadSettings = "pick the default model for new tasks, turn on notifications, and choose this server's window color." +#endif + +private let leadMore = "As you work, the lead keeps [Notes](byre-app://tab?name=notes) on how you like things done, [Workflows](byre-app://tab?name=workflows) run several agents on one job, such as a review, and [Status](byre-app://tab?name=status) shows the turns running now and how much of each vendor's limits you've used." + +private let leadWelcome = """ +**Welcome to byre.** This is your lead, the agent you talk to about all your work. Tell it what you want done and it starts tasks: each one is an agent working on its own branch of a project, which you merge when you're happy with it. Ask it to add a project first, giving the path of a git repository on the server. + +\(leadPlaces) + +\(leadMore) + +Start in [Settings](byre-app://tab?name=settings): \(leadSettings) +""" + struct AttentionGroup: Identifiable { let id: String let label: String @@ -157,6 +177,10 @@ final class ChatModel { kind == .lead && store.leadHasOlder() } + var welcome: Bool { + kind == .lead && !loading && !hasOlder && builder.root.rows.isEmpty && builder.pending.isEmpty + } + func start() { if started { return @@ -790,6 +814,10 @@ private struct TranscriptScroll: View { PendingView(pending: p, model: model) .padding(.top, 12) } + if model.welcome { + MarkdownView(blocks: parseMarkdown(leadWelcome)) + .padding(.top, 12) + } } } .scrollTargetLayout() diff --git a/mac/Sources/Byre/Dialogs.swift b/mac/Sources/Byre/Dialogs.swift index 1f0046c..b48b2bb 100644 --- a/mac/Sources/Byre/Dialogs.swift +++ b/mac/Sources/Byre/Dialogs.swift @@ -34,7 +34,7 @@ struct OverlayAlert: Identifiable { let content: AnyView } -enum LeftTab { +enum LeftTab: String { case lead, status, notes, workflows, server, settings } @@ -51,6 +51,7 @@ final class Overlays { var switcher: SwitcherModel? var leftTab = LeftTab.lead var leadFocus = 0 + var tabFocus = 0 var projectFocus = false @ObservationIgnored var pickerFrame: CGRect = .zero @ObservationIgnored var menuHover = false diff --git a/mac/Sources/Byre/MarkdownView.swift b/mac/Sources/Byre/MarkdownView.swift index c38d951..60ba638 100644 --- a/mac/Sources/Byre/MarkdownView.swift +++ b/mac/Sources/Byre/MarkdownView.swift @@ -66,6 +66,11 @@ func openAppLink(_ url: URL, _ store: Store, _ overlays: Overlays) -> OpenURLAct let taskId = q["task"] ?? "" store.route("#/task/\(taskId)") store.openPage(.preview(taskId: taskId, port: Int(q["port"] ?? "") ?? 0)) + case "tab": + overlays.leftTab = LeftTab(rawValue: q["name"] ?? "") ?? .lead + overlays.tabFocus += 1 + case "switcher": + overlays.openSwitcher(store) case "todo": store.startTodo(q["project"] ?? "", q["title"] ?? "", discuss: q["discuss"] == "1", overlays) case "suggest": diff --git a/mac/Sources/Byre/Phone.swift b/mac/Sources/Byre/Phone.swift index a72affc..48112dd 100644 --- a/mac/Sources/Byre/Phone.swift +++ b/mac/Sources/Byre/Phone.swift @@ -56,6 +56,7 @@ struct PhoneView: View { @Environment(Overlays.self) private var overlays @ViewState private var tab = PhoneTab.lead @ViewState private var path: [PhoneRoute] = [] + @ViewState private var moreRoute = MoreRoute.status var body: some View { VStack(spacing: 0) { @@ -73,7 +74,7 @@ struct PhoneView: View { } } shown(.more) { - MoreScreen() + MoreScreen(route: $moreRoute) } } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -104,6 +105,24 @@ struct PhoneView: View { .onChange(of: overlays.leadFocus) { tab = .lead } + .onChange(of: overlays.tabFocus) { + switch overlays.leftTab { + case .lead, .server: + tab = .lead + case .status: + tab = .more + moreRoute = .status + case .notes: + tab = .more + moreRoute = .notes + case .workflows: + tab = .more + moreRoute = .workflows + case .settings: + tab = .more + moreRoute = .settings + } + } .onChange(of: tab == .tasks && !path.isEmpty, initial: true) { _, shown in store.paneFocus.setShown(shown) } @@ -368,7 +387,7 @@ private func dismissKeyboard() { } private struct MoreScreen: View { - @ViewState private var route = MoreRoute.status + @Binding var route: MoreRoute var body: some View { VStack(spacing: 0) { diff --git a/mac/Sources/Byre/Server.swift b/mac/Sources/Byre/Server.swift index 7d29ff1..46c1d61 100644 --- a/mac/Sources/Byre/Server.swift +++ b/mac/Sources/Byre/Server.swift @@ -409,11 +409,11 @@ struct ServerView: View { Text("Agents' permissions") .font(Fonts.font(13, weight: 700)) .padding(.top, 16) - Text("The macOS privacy grants the server's agents use to see and control this Mac. Checking asks macOS for any that are missing.") + Text("The macOS privacy grants the server's agents use to see and control this Mac and read its protected folders. Checking asks macOS for any that are missing.") .foregroundStyle(Palette.muted) .fixedSize(horizontal: false, vertical: true) PermissionsCheck { - try await API(origin: origin).client.checkPermissions().ok.body.json + try await API(origin: origin, timeout: 150).client.checkPermissions().ok.body.json } Text("Usage commands") .font(Fonts.font(13, weight: 700)) diff --git a/mac/Sources/Byre/Settings.swift b/mac/Sources/Byre/Settings.swift index 56bbb77..de4099b 100644 --- a/mac/Sources/Byre/Settings.swift +++ b/mac/Sources/Byre/Settings.swift @@ -56,7 +56,7 @@ struct SettingsView: View { SyncState() #if os(macOS) heading("This Mac's permissions") - Text("The macOS privacy grants byre's app needs for agents to see and control this Mac. Checking asks macOS for any that are missing.") + Text("The macOS privacy grants byre's app needs for agents to see and control this Mac and read its protected folders. Checking asks macOS for any that are missing.") .foregroundStyle(Palette.muted) .fixedSize(horizontal: false, vertical: true) PermissionsCheck(run: checkLocalPermissions)