Trigger folder access prompts in permission checks; add lead welcome
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user