Trigger folder access prompts in permission checks; add lead welcome

This commit is contained in:
Ian Gulliver
2026-10-22 08:01:59 -07:00
parent e1eeaad925
commit a3d1852f45
13 changed files with 227 additions and 149 deletions
+6
View File
@@ -41,5 +41,11 @@
</dict>
<key>NSAppleEventsUsageDescription</key>
<string>byre's agents drive Orion on this Mac through AppleScript when a task asks them to.</string>
<key>NSDesktopFolderUsageDescription</key>
<string>byre's agents read and write files on your Desktop when a task asks them to.</string>
<key>NSDocumentsFolderUsageDescription</key>
<string>byre's agents read and write files in Documents when a task asks them to.</string>
<key>NSDownloadsFolderUsageDescription</key>
<string>byre's agents read and write files in Downloads when a task asks them to.</string>
</dict>
</plist>
+2 -1
View File
@@ -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,
+28
View File
@@ -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()
+2 -1
View File
@@ -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
+5
View File
@@ -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":
+21 -2
View File
@@ -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) {
+2 -2
View File
@@ -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))
+1 -1
View File
@@ -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)