Add personal data tools, 2nd Brain trust toggle, and research agents

Personal Data Tools: native Calendar, Reminders, Contacts (hidden pending
Apple TCC fix in beta), and Location & Maps access via EventKit, Contacts
framework, and MapKit. Write actions (create event/reminder, complete
reminder) gate through an approval sheet. Four hardened-runtime entitlements
added to oAI.entitlements; Info.plist usage strings added for all services.
Personal Data section shows a β badge while Contacts is hidden.

2nd Brain always-trust: inline toggle on the Agent Skills row for the skill
named "2nd Brain" skips the bash approval dialog when the command contains
.brain_helper.py, gated by three runtime checks in MCPService.

Research agents: spawn_research_agents tool runs up to 5 concurrent read-only
sub-agents (read_file, list_directory, search_files, web_search — no write,
no bash, no nesting). Bounded by maxConcurrentAgents setting (default 3) and
a hard ceiling of 8 tasks. Added items field to Tool.Function.Parameters.Property
for JSON Schema array support; wired into AnthropicProvider.convertParametersToDict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 14:04:47 +02:00
parent 454cef4193
commit 40c5f25517
17 changed files with 11041 additions and 8443 deletions
+85
View File
@@ -27,6 +27,18 @@ import Foundation
import os
import Security
/// Kill switch for the Personal Data tools (Calendar/Reminders/Contacts/Location & Maps).
/// Flip `isHiddenPendingAppleFix` back to `false` once Apple fixes the macOS 27 beta TCC bug
/// (filed with Apple). Each flag hides the relevant UI and forces the `*Enabled` getter to
/// return `false` regardless of the persisted DB value no code deleted, just inert.
enum PersonalDataTools {
/// Hides the entire Personal Data section. Flip to `false` once all four services work.
static let isHiddenPendingAppleFix = false
/// Hides only the Contacts row. Contacts TCC still broken under hardened runtime on
/// macOS 27 beta 2 while Calendar/Reminders/Location are fixed. Flip to `false` once fixed.
static let isContactsHiddenPendingAppleFix = true
}
@Observable
class SettingsService {
static let shared = SettingsService()
@@ -577,6 +589,37 @@ class SettingsService {
}
}
// MARK: - Research Agents Settings
/// When true, the AI can call `spawn_research_agents` to run multiple read-only
/// sub-agents in parallel. Each sub-agent is its own full chain of model calls, so
/// this can noticeably increase cost opt-in, off by default.
var agentsEnabled: Bool {
get { cache["agentsEnabled"] == "true" }
set {
cache["agentsEnabled"] = String(newValue)
DatabaseService.shared.setSetting(key: "agentsEnabled", value: String(newValue))
}
}
var maxConcurrentAgents: Int {
get { cache["maxConcurrentAgents"].flatMap(Int.init) ?? 3 }
set {
cache["maxConcurrentAgents"] = String(newValue)
DatabaseService.shared.setSetting(key: "maxConcurrentAgents", value: String(newValue))
}
}
/// When true (and an active "2nd Brain" Agent Skill is installed), bash commands that
/// invoke the 2nd Brain helper script skip the approval dialog entirely.
var trustSecondBrainSkill: Bool {
get { cache["trustSecondBrainSkill"] == "true" }
set {
cache["trustSecondBrainSkill"] = String(newValue)
DatabaseService.shared.setSetting(key: "trustSecondBrainSkill", value: String(newValue))
}
}
var bashWorkingDirectory: String {
get { cache["bashWorkingDirectory"] ?? "~" }
set {
@@ -593,6 +636,48 @@ class SettingsService {
}
}
// MARK: - Personal Data Settings (Calendar/Reminders/Contacts/Location/Maps)
var calendarEnabled: Bool {
get { !PersonalDataTools.isHiddenPendingAppleFix && cache["calendarEnabled"] == "true" }
set {
cache["calendarEnabled"] = String(newValue)
DatabaseService.shared.setSetting(key: "calendarEnabled", value: String(newValue))
}
}
var remindersEnabled: Bool {
get { !PersonalDataTools.isHiddenPendingAppleFix && cache["remindersEnabled"] == "true" }
set {
cache["remindersEnabled"] = String(newValue)
DatabaseService.shared.setSetting(key: "remindersEnabled", value: String(newValue))
}
}
var contactsEnabled: Bool {
get { !PersonalDataTools.isHiddenPendingAppleFix && !PersonalDataTools.isContactsHiddenPendingAppleFix && cache["contactsEnabled"] == "true" }
set {
cache["contactsEnabled"] = String(newValue)
DatabaseService.shared.setSetting(key: "contactsEnabled", value: String(newValue))
}
}
var locationMapsEnabled: Bool {
get { !PersonalDataTools.isHiddenPendingAppleFix && cache["locationMapsEnabled"] == "true" }
set {
cache["locationMapsEnabled"] = String(newValue)
DatabaseService.shared.setSetting(key: "locationMapsEnabled", value: String(newValue))
}
}
var personalDataRequireApproval: Bool {
get { cache["personalDataRequireApproval"].map { $0 == "true" } ?? true }
set {
cache["personalDataRequireApproval"] = String(newValue)
DatabaseService.shared.setSetting(key: "personalDataRequireApproval", value: String(newValue))
}
}
// MARK: - Paperless-NGX Settings
var paperlessEnabled: Bool {